mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 01:03:38 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into calvin
This commit is contained in:
commit
6c75daa027
44 changed files with 1172 additions and 518 deletions
42
examples/example/hmd/ipdScalingTest.js
Normal file
42
examples/example/hmd/ipdScalingTest.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/10/04
|
||||
// Copyright 2013-2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
IPDScalingTest = function() {
|
||||
// Switch every 5 seconds between normal IPD and 0 IPD (in seconds)
|
||||
this.UPDATE_INTERVAL = 10.0;
|
||||
this.lastUpdateInterval = 0;
|
||||
this.scaled = false;
|
||||
|
||||
var that = this;
|
||||
Script.scriptEnding.connect(function() {
|
||||
that.onCleanup();
|
||||
});
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
that.lastUpdateInterval += deltaTime;
|
||||
if (that.lastUpdateInterval >= that.UPDATE_INTERVAL) {
|
||||
that.onUpdate(that.lastUpdateInterval);
|
||||
that.lastUpdateInterval = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
IPDScalingTest.prototype.onCleanup = function() {
|
||||
HMD.setIPDScale(1.0);
|
||||
}
|
||||
|
||||
IPDScalingTest.prototype.onUpdate = function(deltaTime) {
|
||||
this.scaled = !this.scaled;
|
||||
if (this.scaled) {
|
||||
HMD.ipdScale = 0.0;
|
||||
} else {
|
||||
HMD.ipdScale = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
new IPDScalingTest();
|
103
examples/example/hmd/pickerTest.js
Normal file
103
examples/example/hmd/pickerTest.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
Script.include("../../libraries/utils.js");
|
||||
|
||||
|
||||
PickerTest = function() {
|
||||
// Switch every 5 seconds between normal IPD and 0 IPD (in seconds)
|
||||
this.UPDATE_INTERVAL = 10.0;
|
||||
this.lastUpdateInterval = 0;
|
||||
|
||||
this.ballId = Overlays.addOverlay("sphere", {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
color: { red: 0, green: 255, blue: 0 },
|
||||
size: 0.1,
|
||||
solid: true,
|
||||
alpha: 1.0,
|
||||
visible: true,
|
||||
});
|
||||
|
||||
this.ballId2 = Overlays.addOverlay("sphere", {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
size: 0.05,
|
||||
solid: true,
|
||||
alpha: 1.0,
|
||||
visible: true,
|
||||
});
|
||||
|
||||
var that = this;
|
||||
Script.scriptEnding.connect(function() {
|
||||
that.onCleanup();
|
||||
});
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
that.lastUpdateInterval += deltaTime;
|
||||
if (that.lastUpdateInterval >= that.UPDATE_INTERVAL) {
|
||||
that.onUpdate(that.lastUpdateInterval);
|
||||
that.lastUpdateInterval = 0;
|
||||
}
|
||||
});
|
||||
|
||||
Controller.mousePressEvent.connect(function(event) {
|
||||
that.onMousePress(event);
|
||||
});
|
||||
|
||||
Controller.mouseMoveEvent.connect(function(event) {
|
||||
that.onMouseMove(event);
|
||||
});
|
||||
|
||||
Controller.mouseReleaseEvent.connect(function(event) {
|
||||
that.onMouseRelease(event);
|
||||
});
|
||||
};
|
||||
|
||||
PickerTest.prototype.onCleanup = function() {
|
||||
Overlays.deleteOverlay(this.ballId)
|
||||
Overlays.deleteOverlay(this.ballId2)
|
||||
}
|
||||
|
||||
PickerTest.prototype.updateOverlays = function() {
|
||||
var pickRay = Camera.computePickRay(this.x, this.y);
|
||||
var origin = pickRay.origin;
|
||||
var direction = pickRay.direction;
|
||||
var position = Vec3.sum(origin, direction)
|
||||
Overlays.editOverlay(this.ballId, {
|
||||
position: position
|
||||
});
|
||||
|
||||
Overlays.editOverlay(this.ballId2, {
|
||||
position: origin
|
||||
});
|
||||
}
|
||||
|
||||
PickerTest.prototype.onUpdate = function(deltaTime) {
|
||||
if (this.clicked) {
|
||||
this.updateOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
PickerTest.prototype.onMousePress = function(event) {
|
||||
if (event.button !== "LEFT") {
|
||||
return
|
||||
}
|
||||
this.clicked = true;
|
||||
this.x = event.x;
|
||||
this.y = event.y;
|
||||
this.updateOverlays();
|
||||
}
|
||||
|
||||
PickerTest.prototype.onMouseRelease = function(event) {
|
||||
if (event.button !== "LEFT") {
|
||||
return
|
||||
}
|
||||
this.clicked = false;
|
||||
}
|
||||
|
||||
PickerTest.prototype.onMouseMove = function(event) {
|
||||
if (this.clicked) {
|
||||
this.x = event.x;
|
||||
this.y = event.y;
|
||||
this.updateOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
var PickerTest = new PickerTest();
|
|
@ -1,12 +1,10 @@
|
|||
//
|
||||
// createHoop.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by James B. Pollack on 9/29/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is a script that creates a persistent basketball hoop with a working collision hull. Feel free to move it.
|
||||
// Run basketball.js to make a basketball.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
152
examples/toys/basketball/createRack.js
Normal file
152
examples/toys/basketball/createRack.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
// createRack.js
|
||||
//
|
||||
// Created by James B. Pollack @imgntn on 10/5/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is a script that creates a persistent basketball rack.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
Script.include("../../libraries/utils.js");
|
||||
|
||||
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var basketballURL = HIFI_PUBLIC_BUCKET + "models/content/basketball2.fbx";
|
||||
var collisionSoundURL = HIFI_PUBLIC_BUCKET + "sounds/basketball/basketball.wav";
|
||||
var rackURL = HIFI_PUBLIC_BUCKET + "models/basketball_hoop/basketball_rack.fbx";
|
||||
var rackCollisionHullURL = HIFI_PUBLIC_BUCKET + "models/basketball_hoop/rack_collision_hull.obj";
|
||||
var NUMBER_OF_BALLS = 4;
|
||||
var DIAMETER = 0.30;
|
||||
var RESET_DISTANCE = 1;
|
||||
var MINIMUM_MOVE_LENGTH = 0.05;
|
||||
|
||||
var GRABBABLE_DATA_KEY = "grabbableKey";
|
||||
|
||||
var rackStartPosition =
|
||||
Vec3.sum(MyAvatar.position,
|
||||
Vec3.multiplyQbyV(MyAvatar.orientation, {
|
||||
x: 0,
|
||||
y: 0.0,
|
||||
z: -2
|
||||
}));
|
||||
|
||||
var rack = Entities.addEntity({
|
||||
name: 'Basketball Rack',
|
||||
type: "Model",
|
||||
modelURL: rackURL,
|
||||
position: rackStartPosition,
|
||||
shapeType: 'compound',
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
linearDamping: 1,
|
||||
dimensions: {
|
||||
x: 0.4,
|
||||
y: 1.37,
|
||||
z: 1.73
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
ignoreForCollisions: false,
|
||||
collisionSoundURL: collisionSoundURL,
|
||||
compoundShapeURL: rackCollisionHullURL,
|
||||
// scriptURL: rackScriptURL
|
||||
});
|
||||
|
||||
|
||||
setEntityCustomData(GRABBABLE_DATA_KEY, rack, {
|
||||
grabbable: false
|
||||
});
|
||||
|
||||
var nonCollidingBalls = [];
|
||||
var collidingBalls = [];
|
||||
var originalBallPositions = [];
|
||||
|
||||
function createCollidingBalls() {
|
||||
var position = rackStartPosition;
|
||||
|
||||
var i;
|
||||
for (i = 0; i < NUMBER_OF_BALLS; i++) {
|
||||
var ballPosition = {
|
||||
x: position.x,
|
||||
y: position.y + DIAMETER * 2,
|
||||
z: position.z + (DIAMETER) - (DIAMETER * i)
|
||||
};
|
||||
|
||||
var collidingBall = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: 'Colliding Basketball',
|
||||
shapeType: 'Sphere',
|
||||
position: ballPosition,
|
||||
dimensions: {
|
||||
x: DIAMETER,
|
||||
y: DIAMETER,
|
||||
z: DIAMETER
|
||||
},
|
||||
restitution: 1.0,
|
||||
linearDamping: 0.00001,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
ignoreForCollisions: false,
|
||||
modelURL: basketballURL,
|
||||
});
|
||||
|
||||
collidingBalls.push(collidingBall);
|
||||
originalBallPositions.push(position);
|
||||
}
|
||||
}
|
||||
|
||||
function testBallDistanceFromStart() {
|
||||
var resetCount = 0;
|
||||
collidingBalls.forEach(function(ball, index) {
|
||||
var currentPosition = Entities.getEntityProperties(ball, "position").position;
|
||||
var originalPosition = originalBallPositions[index];
|
||||
var distance = Vec3.subtract(originalPosition, currentPosition);
|
||||
var length = Vec3.length(distance);
|
||||
if (length > RESET_DISTANCE) {
|
||||
Script.setTimeout(function() {
|
||||
var newPosition = Entities.getEntityProperties(ball, "position").position;
|
||||
var moving = Vec3.length(Vec3.subtract(currentPosition, newPosition));
|
||||
if (moving < MINIMUM_MOVE_LENGTH) {
|
||||
resetCount++;
|
||||
if (resetCount === NUMBER_OF_BALLS) {
|
||||
deleteCollidingBalls();
|
||||
createCollidingBalls();
|
||||
}
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteEntity(entityID) {
|
||||
if (entityID === rack) {
|
||||
deleteCollidingBalls();
|
||||
Script.clearInterval(distanceCheckInterval);
|
||||
Entities.deletingEntity.disconnect(deleteEntity);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteCollidingBalls() {
|
||||
while (collidingBalls.length > 0) {
|
||||
Entities.deleteEntity(collidingBalls.pop());
|
||||
}
|
||||
}
|
||||
|
||||
createCollidingBalls();
|
||||
Entities.deletingEntity.connect(deleteEntity);
|
||||
|
||||
var distanceCheckInterval = Script.setInterval(testBallDistanceFromStart, 1000);
|
||||
|
||||
function atEnd() {
|
||||
Script.clearInterval(distanceCheckInterval);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(atEnd);
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// basketball.js
|
||||
// createSingleBasketball.js
|
||||
// examples
|
||||
//
|
||||
// Created by Philip Rosedale on August 20, 2015
|
||||
|
@ -17,34 +17,39 @@ var collisionSoundURL = HIFI_PUBLIC_BUCKET + "sounds/basketball/basketball.wav";
|
|||
|
||||
var basketball = null;
|
||||
var originalPosition = null;
|
||||
var hasMoved = false;
|
||||
var hasMoved = false;
|
||||
|
||||
var GRAVITY = -9.8;
|
||||
var DISTANCE_IN_FRONT_OF_ME = 1.0;
|
||||
var START_MOVE = 0.01;
|
||||
var DIAMETER = 0.30;
|
||||
var DIAMETER = 0.30;
|
||||
|
||||
function makeBasketball() {
|
||||
function makeBasketball() {
|
||||
var position = Vec3.sum(MyAvatar.position,
|
||||
Vec3.multiplyQbyV(MyAvatar.orientation,
|
||||
{ x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME }));
|
||||
Vec3.multiplyQbyV(MyAvatar.orientation, {
|
||||
x: 0,
|
||||
y: 0.0,
|
||||
z: -DISTANCE_IN_FRONT_OF_ME
|
||||
}));
|
||||
var rotation = Quat.multiply(MyAvatar.orientation,
|
||||
Quat.fromPitchYawRollDegrees(0, -90, 0));
|
||||
Quat.fromPitchYawRollDegrees(0, -90, 0));
|
||||
basketball = Entities.addEntity({
|
||||
type: "Model",
|
||||
position: position,
|
||||
rotation: rotation,
|
||||
dimensions: { x: DIAMETER,
|
||||
y: DIAMETER,
|
||||
z: DIAMETER },
|
||||
collisionsWillMove: true,
|
||||
collisionSoundURL: collisionSoundURL,
|
||||
modelURL: basketballURL,
|
||||
restitution: 1.0,
|
||||
linearDamping: 0.00001,
|
||||
shapeType: "sphere"
|
||||
});
|
||||
originalPosition = position;
|
||||
type: "Model",
|
||||
position: position,
|
||||
rotation: rotation,
|
||||
dimensions: {
|
||||
x: DIAMETER,
|
||||
y: DIAMETER,
|
||||
z: DIAMETER
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
collisionSoundURL: collisionSoundURL,
|
||||
modelURL: basketballURL,
|
||||
restitution: 1.0,
|
||||
linearDamping: 0.00001,
|
||||
shapeType: "sphere"
|
||||
});
|
||||
originalPosition = position;
|
||||
}
|
||||
|
||||
function update() {
|
||||
|
@ -55,28 +60,33 @@ function update() {
|
|||
var moved = Vec3.length(Vec3.subtract(originalPosition, newProperties.position));
|
||||
if (!hasMoved && (moved > START_MOVE)) {
|
||||
hasMoved = true;
|
||||
Entities.editEntity(basketball, { gravity: {x: 0, y: GRAVITY, z: 0 }});
|
||||
Entities.editEntity(basketball, {
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: GRAVITY,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
var MAX_DISTANCE = 10.0;
|
||||
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, newProperties.position));
|
||||
if (distance > MAX_DISTANCE) {
|
||||
deleteStuff();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
deleteStuff();
|
||||
}
|
||||
|
||||
function deleteStuff() {
|
||||
function deleteStuff() {
|
||||
if (basketball != null) {
|
||||
Entities.deleteEntity(basketball);
|
||||
basketball = null;
|
||||
hasMoved = false;
|
||||
hasMoved = false;
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
|
@ -306,6 +306,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<DesktopScriptingInterface>();
|
||||
DependencyManager::set<EntityScriptingInterface>();
|
||||
DependencyManager::set<WindowScriptingInterface>();
|
||||
DependencyManager::set<HMDScriptingInterface>();
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
DependencyManager::set<SpeechRecognizer>();
|
||||
#endif
|
||||
|
@ -1166,9 +1167,11 @@ void Application::paintGL() {
|
|||
// right eye. There are FIXMEs in the relevant plugins
|
||||
_myCamera.setProjection(displayPlugin->getProjection(Mono, _myCamera.getProjection()));
|
||||
renderArgs._context->enableStereo(true);
|
||||
mat4 eyeViews[2];
|
||||
mat4 eyeOffsets[2];
|
||||
mat4 eyeProjections[2];
|
||||
auto baseProjection = renderArgs._viewFrustum->getProjection();
|
||||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
float IPDScale = hmdInterface->getIPDScale();
|
||||
// FIXME we probably don't need to set the projection matrix every frame,
|
||||
// only when the display plugin changes (or in non-HMD modes when the user
|
||||
// changes the FOV manually, which right now I don't think they can.
|
||||
|
@ -1177,14 +1180,24 @@ void Application::paintGL() {
|
|||
// applied to the avatar, so we need to get the difference between the head
|
||||
// pose applied to the avatar and the per eye pose, and use THAT as
|
||||
// the per-eye stereo matrix adjustment.
|
||||
mat4 eyePose = displayPlugin->getEyePose(eye);
|
||||
mat4 eyeToHead = displayPlugin->getEyeToHeadTransform(eye);
|
||||
// Grab the translation
|
||||
vec3 eyeOffset = glm::vec3(eyeToHead[3]);
|
||||
// Apply IPD scaling
|
||||
mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale);
|
||||
eyeOffsets[eye] = eyeOffsetTransform;
|
||||
|
||||
// Tell the plugin what pose we're using to render. In this case we're just using the
|
||||
// unmodified head pose because the only plugin that cares (the Oculus plugin) uses it
|
||||
// for rotational timewarp. If we move to support positonal timewarp, we need to
|
||||
// ensure this contains the full pose composed with the eye offsets.
|
||||
mat4 headPose = displayPlugin->getHeadPose();
|
||||
mat4 eyeView = glm::inverse(eyePose) * headPose;
|
||||
eyeViews[eye] = eyeView;
|
||||
displayPlugin->setEyeRenderPose(eye, headPose);
|
||||
|
||||
eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection);
|
||||
});
|
||||
renderArgs._context->setStereoProjections(eyeProjections);
|
||||
renderArgs._context->setStereoViews(eyeViews);
|
||||
renderArgs._context->setStereoViews(eyeOffsets);
|
||||
}
|
||||
displaySide(&renderArgs, _myCamera);
|
||||
renderArgs._context->enableStereo(false);
|
||||
|
@ -3175,14 +3188,13 @@ int Application::getBoundaryLevelAdjust() const {
|
|||
}
|
||||
|
||||
PickRay Application::computePickRay(float x, float y) const {
|
||||
glm::vec2 size = getCanvasSize();
|
||||
x /= size.x;
|
||||
y /= size.y;
|
||||
vec2 pickPoint{ x, y };
|
||||
PickRay result;
|
||||
if (isHMDMode()) {
|
||||
getApplicationCompositor().computeHmdPickRay(glm::vec2(x, y), result.origin, result.direction);
|
||||
getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction);
|
||||
} else {
|
||||
getViewFrustum()->computePickRay(x, y, result.origin, result.direction);
|
||||
pickPoint /= getCanvasSize();
|
||||
getViewFrustum()->computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -3930,7 +3942,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
|
||||
scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
||||
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
||||
|
||||
|
@ -4740,19 +4752,25 @@ mat4 Application::getEyeProjection(int eye) const {
|
|||
|
||||
mat4 Application::getEyePose(int eye) const {
|
||||
if (isHMDMode()) {
|
||||
return getActiveDisplayPlugin()->getEyePose((Eye)eye);
|
||||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
float IPDScale = hmdInterface->getIPDScale();
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
mat4 headPose = displayPlugin->getHeadPose();
|
||||
mat4 eyeToHead = displayPlugin->getEyeToHeadTransform((Eye)eye);
|
||||
{
|
||||
vec3 eyeOffset = glm::vec3(eyeToHead[3]);
|
||||
// Apply IPD scaling
|
||||
mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale);
|
||||
eyeToHead[3] = vec4(eyeOffset, 1.0);
|
||||
}
|
||||
return eyeToHead * headPose;
|
||||
}
|
||||
|
||||
return mat4();
|
||||
}
|
||||
|
||||
mat4 Application::getEyeOffset(int eye) const {
|
||||
if (isHMDMode()) {
|
||||
mat4 identity;
|
||||
return getActiveDisplayPlugin()->getView((Eye)eye, identity);
|
||||
}
|
||||
|
||||
return mat4();
|
||||
// FIXME invert?
|
||||
return getActiveDisplayPlugin()->getEyeToHeadTransform((Eye)eye);
|
||||
}
|
||||
|
||||
mat4 Application::getHMDSensorPose() const {
|
||||
|
|
|
@ -16,6 +16,9 @@ PluginContainerProxy::PluginContainerProxy() {
|
|||
Plugin::setContainer(this);
|
||||
}
|
||||
|
||||
PluginContainerProxy::~PluginContainerProxy() {
|
||||
}
|
||||
|
||||
bool PluginContainerProxy::isForeground() {
|
||||
return qApp->isForeground() && !qApp->getWindow()->isMinimized();
|
||||
}
|
||||
|
@ -151,3 +154,7 @@ void PluginContainerProxy::showDisplayPluginsTools() {
|
|||
QGLWidget* PluginContainerProxy::getPrimarySurface() {
|
||||
return qApp->_glWidget;
|
||||
}
|
||||
|
||||
const DisplayPlugin* PluginContainerProxy::getActiveDisplayPlugin() const {
|
||||
return qApp->getActiveDisplayPlugin();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
class PluginContainerProxy : public QObject, PluginContainer {
|
||||
Q_OBJECT
|
||||
PluginContainerProxy();
|
||||
virtual ~PluginContainerProxy();
|
||||
virtual void addMenu(const QString& menuName) override;
|
||||
virtual void removeMenu(const QString& menuName) override;
|
||||
virtual QAction* addMenuItem(const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override;
|
||||
|
@ -23,6 +24,8 @@ class PluginContainerProxy : public QObject, PluginContainer {
|
|||
virtual void requestReset() override;
|
||||
virtual QGLWidget* getPrimarySurface() override;
|
||||
virtual bool isForeground() override;
|
||||
virtual const DisplayPlugin* getActiveDisplayPlugin() const override;
|
||||
|
||||
QRect _savedGeometry{ 10, 120, 800, 600 };
|
||||
|
||||
friend class Application;
|
||||
|
|
|
@ -1342,11 +1342,13 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
|
|||
if (qApp->isHMDMode()) {
|
||||
glm::vec3 cameraPosition = qApp->getCamera()->getPosition();
|
||||
|
||||
glm::mat4 leftEyePose = qApp->getActiveDisplayPlugin()->getEyePose(Eye::Left);
|
||||
glm::vec3 leftEyePosition = glm::vec3(leftEyePose[3]);
|
||||
glm::mat4 rightEyePose = qApp->getActiveDisplayPlugin()->getEyePose(Eye::Right);
|
||||
glm::vec3 rightEyePosition = glm::vec3(rightEyePose[3]);
|
||||
glm::mat4 headPose = qApp->getActiveDisplayPlugin()->getHeadPose();
|
||||
glm::mat4 leftEyePose = qApp->getActiveDisplayPlugin()->getEyeToHeadTransform(Eye::Left);
|
||||
leftEyePose = leftEyePose * headPose;
|
||||
glm::vec3 leftEyePosition = glm::vec3(leftEyePose[3]);
|
||||
glm::mat4 rightEyePose = qApp->getActiveDisplayPlugin()->getEyeToHeadTransform(Eye::Right);
|
||||
rightEyePose = rightEyePose * headPose;
|
||||
glm::vec3 rightEyePosition = glm::vec3(rightEyePose[3]);
|
||||
glm::vec3 headPosition = glm::vec3(headPose[3]);
|
||||
|
||||
getHead()->renderLookAts(renderArgs,
|
||||
|
|
|
@ -13,14 +13,43 @@
|
|||
|
||||
#include <QtScript/QScriptContext>
|
||||
|
||||
#include <avatar/AvatarManager.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "display-plugins/DisplayPlugin.h"
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include "Application.h"
|
||||
|
||||
HMDScriptingInterface& HMDScriptingInterface::getInstance() {
|
||||
static HMDScriptingInterface sharedInstance;
|
||||
return sharedInstance;
|
||||
HMDScriptingInterface::HMDScriptingInterface() {
|
||||
}
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
|
||||
glm::vec3 hudIntersection;
|
||||
auto instance = DependencyManager::get<HMDScriptingInterface>();
|
||||
if (instance->getHUDLookAtPosition3D(hudIntersection)) {
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
glm::vec3 sphereCenter = myAvatar->getDefaultEyePosition();
|
||||
glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * (hudIntersection - sphereCenter);
|
||||
glm::quat rotation = ::rotationBetween(glm::vec3(0.0f, 0.0f, -1.0f), direction);
|
||||
glm::vec3 eulers = ::safeEulerAngles(rotation);
|
||||
return qScriptValueFromValue<glm::vec2>(engine, qApp->getApplicationCompositor()
|
||||
.sphericalToOverlay(glm::vec2(eulers.y, -eulers.x)));
|
||||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine) {
|
||||
glm::vec3 result;
|
||||
auto instance = DependencyManager::get<HMDScriptingInterface>();
|
||||
if (instance->getHUDLookAtPosition3D(result)) {
|
||||
return qScriptValueFromValue<glm::vec3>(engine, result);
|
||||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::toggleMagnifier() {
|
||||
qApp->getApplicationCompositor().toggleMagnifier();
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::getMagnifier() const {
|
||||
return qApp->getApplicationCompositor().hasMagnifier();
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::getHUDLookAtPosition3D(glm::vec3& result) const {
|
||||
|
@ -34,43 +63,3 @@ bool HMDScriptingInterface::getHUDLookAtPosition3D(glm::vec3& result) const {
|
|||
|
||||
return compositor.calculateRayUICollisionPoint(position, direction, result);
|
||||
}
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
|
||||
|
||||
glm::vec3 hudIntersection;
|
||||
|
||||
if ((&HMDScriptingInterface::getInstance())->getHUDLookAtPosition3D(hudIntersection)) {
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
glm::vec3 sphereCenter = myAvatar->getDefaultEyePosition();
|
||||
glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * (hudIntersection - sphereCenter);
|
||||
glm::quat rotation = ::rotationBetween(glm::vec3(0.0f, 0.0f, -1.0f), direction);
|
||||
glm::vec3 eulers = ::safeEulerAngles(rotation);
|
||||
return qScriptValueFromValue<glm::vec2>(engine, qApp->getApplicationCompositor()
|
||||
.sphericalToOverlay(glm::vec2(eulers.y, -eulers.x)));
|
||||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine) {
|
||||
glm::vec3 result;
|
||||
if ((&HMDScriptingInterface::getInstance())->getHUDLookAtPosition3D(result)) {
|
||||
return qScriptValueFromValue<glm::vec3>(engine, result);
|
||||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
float HMDScriptingInterface::getIPD() const {
|
||||
return qApp->getActiveDisplayPlugin()->getIPD();
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::toggleMagnifier() {
|
||||
qApp->getApplicationCompositor().toggleMagnifier();
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::getMagnifier() const {
|
||||
return qApp->getApplicationCompositor().hasMagnifier();
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::isHMDMode() const {
|
||||
return qApp->isHMDMode();
|
||||
}
|
||||
|
|
|
@ -13,22 +13,19 @@
|
|||
#define hifi_HMDScriptingInterface_h
|
||||
|
||||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
class QScriptContext;
|
||||
class QScriptEngine;
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <display-plugins/AbstractHMDScriptingInterface.h>
|
||||
|
||||
class HMDScriptingInterface : public QObject {
|
||||
|
||||
class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool magnifier READ getMagnifier)
|
||||
Q_PROPERTY(bool active READ isHMDMode)
|
||||
Q_PROPERTY(float ipd READ getIPD)
|
||||
|
||||
public:
|
||||
static HMDScriptingInterface& getInstance();
|
||||
|
||||
HMDScriptingInterface();
|
||||
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);
|
||||
static QScriptValue getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
|
@ -36,11 +33,7 @@ public slots:
|
|||
void toggleMagnifier();
|
||||
|
||||
private:
|
||||
HMDScriptingInterface() = default;
|
||||
bool getMagnifier() const;
|
||||
bool isHMDMode() const;
|
||||
float getIPD() const;
|
||||
|
||||
bool getMagnifier() const;
|
||||
bool getHUDLookAtPosition3D(glm::vec3& result) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
@ -285,7 +286,10 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int
|
|||
|
||||
mat4 camMat;
|
||||
_cameraBaseTransform.getMatrix(camMat);
|
||||
camMat = camMat * qApp->getEyePose(eye);
|
||||
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
||||
auto headPose = displayPlugin->getHeadPose();
|
||||
auto eyeToHead = displayPlugin->getEyeToHeadTransform((Eye)eye);
|
||||
camMat = (headPose * eyeToHead) * camMat;
|
||||
batch.setViewportTransform(renderArgs->_viewport);
|
||||
batch.setViewTransform(camMat);
|
||||
|
||||
|
@ -346,31 +350,28 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int
|
|||
|
||||
|
||||
void ApplicationCompositor::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const {
|
||||
cursorPos *= qApp->getCanvasSize();
|
||||
const glm::vec2 projection = screenToSpherical(cursorPos);
|
||||
const glm::vec2 projection = overlayToSpherical(cursorPos);
|
||||
// The overlay space orientation of the mouse coordinates
|
||||
const glm::quat orientation(glm::vec3(-projection.y, projection.x, 0.0f));
|
||||
// FIXME We now have the direction of the ray FROM THE DEFAULT HEAD POSE.
|
||||
// Now we need to account for the actual camera position relative to the overlay
|
||||
glm::vec3 overlaySpaceDirection = glm::normalize(orientation * IDENTITY_FRONT);
|
||||
const glm::quat cursorOrientation(glm::vec3(-projection.y, projection.x, 0.0f));
|
||||
|
||||
// The orientation and position of the HEAD, not the overlay
|
||||
glm::vec3 worldSpaceHeadPosition = qApp->getCamera()->getPosition();
|
||||
glm::quat worldSpaceOrientation = qApp->getCamera()->getOrientation();
|
||||
|
||||
// We need the RAW camera orientation and position, because this is what the overlay is
|
||||
// rendered relative to
|
||||
glm::vec3 overlayPosition = qApp->getCamera()->getPosition();
|
||||
glm::quat overlayOrientation = qApp->getCamera()->getRotation();
|
||||
auto headPose = qApp->getHMDSensorPose();
|
||||
auto headOrientation = glm::quat_cast(headPose);
|
||||
auto headTranslation = extractTranslation(headPose);
|
||||
|
||||
auto overlayOrientation = worldSpaceOrientation * glm::inverse(headOrientation);
|
||||
auto overlayPosition = worldSpaceHeadPosition - (overlayOrientation * headTranslation);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StandingHMDSensorMode)) {
|
||||
overlayPosition = _modelTransform.getTranslation();
|
||||
overlayOrientation = _modelTransform.getRotation();
|
||||
}
|
||||
|
||||
// Intersection UI overlay space
|
||||
glm::vec3 worldSpaceDirection = overlayOrientation * overlaySpaceDirection;
|
||||
glm::vec3 worldSpaceIntersection = (glm::normalize(worldSpaceDirection) * _oculusUIRadius) + overlayPosition;
|
||||
glm::vec3 worldSpaceHeadPosition = (overlayOrientation * extractTranslation(qApp->getHMDSensorPose())) + overlayPosition;
|
||||
|
||||
// Intersection in world space
|
||||
glm::vec3 worldSpaceIntersection = ((overlayOrientation * (cursorOrientation * Vectors::FRONT)) * _oculusUIRadius) + overlayPosition;
|
||||
|
||||
origin = worldSpaceHeadPosition;
|
||||
direction = glm::normalize(worldSpaceIntersection - worldSpaceHeadPosition);
|
||||
}
|
||||
|
@ -682,7 +683,6 @@ glm::vec2 ApplicationCompositor::screenToSpherical(const glm::vec2& screenPos) {
|
|||
result.y = (screenPos.y / screenSize.y - 0.5f);
|
||||
result.x *= MOUSE_YAW_RANGE;
|
||||
result.y *= MOUSE_PITCH_RANGE;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -701,13 +701,13 @@ glm::vec2 ApplicationCompositor::sphericalToOverlay(const glm::vec2& sphericalP
|
|||
result /= _textureFov;
|
||||
result.x /= _textureAspectRatio;
|
||||
result += 0.5f;
|
||||
result *= qApp->getCanvasSize();
|
||||
result *= qApp->getUiSize();
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 ApplicationCompositor::overlayToSpherical(const glm::vec2& overlayPos) const {
|
||||
glm::vec2 result = overlayPos;
|
||||
result /= qApp->getCanvasSize();
|
||||
result /= qApp->getUiSize();
|
||||
result -= 0.5f;
|
||||
result *= _textureFov;
|
||||
result.x *= _textureAspectRatio;
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/10/04
|
||||
// Copyright 2013-2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AbstractHMDScriptingInterface.h"
|
||||
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#include "DisplayPlugin.h"
|
||||
#include <plugins/PluginContainer.h>
|
||||
#include <OVR_CAPI_Keys.h>
|
||||
|
||||
static Setting::Handle<float> IPD_SCALE_HANDLE("hmd.ipdScale", 1.0f);
|
||||
|
||||
AbstractHMDScriptingInterface::AbstractHMDScriptingInterface() {
|
||||
_IPDScale = IPD_SCALE_HANDLE.get();
|
||||
}
|
||||
|
||||
float AbstractHMDScriptingInterface::getIPD() const {
|
||||
return PluginContainer::getInstance().getActiveDisplayPlugin()->getIPD();
|
||||
}
|
||||
|
||||
float AbstractHMDScriptingInterface::getEyeHeight() const {
|
||||
// FIXME update the display plugin interface to expose per-plugin settings
|
||||
return OVR_DEFAULT_EYE_HEIGHT;
|
||||
}
|
||||
|
||||
float AbstractHMDScriptingInterface::getPlayerHeight() const {
|
||||
// FIXME update the display plugin interface to expose per-plugin settings
|
||||
return OVR_DEFAULT_PLAYER_HEIGHT;
|
||||
}
|
||||
|
||||
float AbstractHMDScriptingInterface::getIPDScale() const {
|
||||
return _IPDScale;
|
||||
}
|
||||
|
||||
void AbstractHMDScriptingInterface::setIPDScale(float IPDScale) {
|
||||
IPDScale = glm::clamp(IPDScale, -1.0f, 3.0f);
|
||||
if (IPDScale != _IPDScale) {
|
||||
_IPDScale = IPDScale;
|
||||
IPD_SCALE_HANDLE.set(IPDScale);
|
||||
emit IPDScaleChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractHMDScriptingInterface::isHMDMode() const {
|
||||
return PluginContainer::getInstance().getActiveDisplayPlugin()->isHmd();
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/10/04
|
||||
// Copyright 2013-2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_AbstractHMDScriptingInterface_h
|
||||
#define hifi_AbstractHMDScriptingInterface_h
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
class AbstractHMDScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool active READ isHMDMode)
|
||||
Q_PROPERTY(float ipd READ getIPD)
|
||||
Q_PROPERTY(float eyeHeight READ getEyeHeight)
|
||||
Q_PROPERTY(float playerHeight READ getPlayerHeight)
|
||||
Q_PROPERTY(float ipdScale READ getIPDScale WRITE setIPDScale NOTIFY IPDScaleChanged)
|
||||
|
||||
public:
|
||||
AbstractHMDScriptingInterface();
|
||||
float getIPD() const;
|
||||
float getEyeHeight() const;
|
||||
float getPlayerHeight() const;
|
||||
float getIPDScale() const;
|
||||
void setIPDScale(float ipdScale);
|
||||
bool isHMDMode() const;
|
||||
|
||||
signals:
|
||||
void IPDScaleChanged();
|
||||
|
||||
private:
|
||||
float _IPDScale{ 1.0 };
|
||||
};
|
||||
|
||||
#endif // hifi_AbstractHMDScriptingInterface_h
|
|
@ -46,6 +46,8 @@ void for_each_eye(F f, FF ff) {
|
|||
|
||||
class QWindow;
|
||||
|
||||
#define AVERAGE_HUMAN_IPD 0.064f
|
||||
|
||||
class DisplayPlugin : public Plugin {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -107,21 +109,22 @@ public:
|
|||
return baseProjection;
|
||||
}
|
||||
|
||||
virtual glm::mat4 getView(Eye eye, const glm::mat4& baseView) const {
|
||||
return glm::inverse(getEyePose(eye)) * baseView;
|
||||
}
|
||||
|
||||
// HMD specific methods
|
||||
// TODO move these into another class?
|
||||
virtual glm::mat4 getEyePose(Eye eye) const {
|
||||
static const glm::mat4 pose; return pose;
|
||||
virtual glm::mat4 getEyeToHeadTransform(Eye eye) const {
|
||||
static const glm::mat4 transform; return transform;
|
||||
}
|
||||
|
||||
virtual glm::mat4 getHeadPose() const {
|
||||
static const glm::mat4 pose; return pose;
|
||||
}
|
||||
|
||||
virtual float getIPD() const { return 0.0f; }
|
||||
// Needed for timewarp style features
|
||||
virtual void setEyeRenderPose(Eye eye, const glm::mat4& pose) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
virtual float getIPD() const { return AVERAGE_HUMAN_IPD; }
|
||||
|
||||
virtual void abandonCalibration() {}
|
||||
virtual void resetSensors() {}
|
||||
|
|
|
@ -19,7 +19,6 @@ void OculusBaseDisplayPlugin::preRender() {
|
|||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrFrameTiming ftiming = ovr_GetFrameTiming(_hmd, _frameIndex);
|
||||
_trackingState = ovr_GetTrackingState(_hmd, ftiming.DisplayMidpointSeconds);
|
||||
ovr_CalcEyePoses(_trackingState.HeadPose.ThePose, _eyeOffsets, _eyePoses);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -33,14 +32,19 @@ void OculusBaseDisplayPlugin::resetSensors() {
|
|||
#endif
|
||||
}
|
||||
|
||||
glm::mat4 OculusBaseDisplayPlugin::getEyePose(Eye eye) const {
|
||||
return toGlm(_eyePoses[eye]);
|
||||
glm::mat4 OculusBaseDisplayPlugin::getEyeToHeadTransform(Eye eye) const {
|
||||
return glm::translate(mat4(), toGlm(_eyeOffsets[eye]));
|
||||
}
|
||||
|
||||
glm::mat4 OculusBaseDisplayPlugin::getHeadPose() const {
|
||||
return toGlm(_trackingState.HeadPose.ThePose);
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::setEyeRenderPose(Eye eye, const glm::mat4& pose) {
|
||||
_eyePoses[eye] = ovrPoseFromGlm(pose);
|
||||
}
|
||||
|
||||
|
||||
bool OculusBaseDisplayPlugin::isSupported() const {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
|
@ -151,9 +155,9 @@ void OculusBaseDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sce
|
|||
}
|
||||
|
||||
float OculusBaseDisplayPlugin::getIPD() const {
|
||||
float result = 0.0f;
|
||||
float result = OVR_DEFAULT_IPD;
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
result = ovr_GetFloat(_hmd, OVR_KEY_IPD, OVR_DEFAULT_IPD);
|
||||
result = ovr_GetFloat(_hmd, OVR_KEY_IPD, result);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,9 @@ public:
|
|||
virtual glm::uvec2 getRecommendedRenderSize() const override final;
|
||||
virtual glm::uvec2 getRecommendedUiSize() const override final { return uvec2(1920, 1080); }
|
||||
virtual void resetSensors() override final;
|
||||
virtual glm::mat4 getEyePose(Eye eye) const override final;
|
||||
virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override final;
|
||||
virtual glm::mat4 getHeadPose() const override final;
|
||||
virtual void setEyeRenderPose(Eye eye, const glm::mat4& pose) override final;
|
||||
virtual float getIPD() const override final;
|
||||
|
||||
protected:
|
||||
|
@ -39,6 +40,7 @@ protected:
|
|||
|
||||
protected:
|
||||
ovrPosef _eyePoses[2];
|
||||
ovrVector3f _eyeOffsets[2];
|
||||
|
||||
mat4 _eyeProjections[3];
|
||||
mat4 _compositeEyeProjections[2];
|
||||
|
@ -50,13 +52,12 @@ protected:
|
|||
ovrHmd _hmd;
|
||||
float _ipd{ OVR_DEFAULT_IPD };
|
||||
ovrEyeRenderDesc _eyeRenderDescs[2];
|
||||
ovrVector3f _eyeOffsets[2];
|
||||
ovrFovPort _eyeFovs[2];
|
||||
ovrHmdDesc _hmdDesc;
|
||||
ovrLayerEyeFov _sceneLayer;
|
||||
ovrHmdDesc _hmdDesc;
|
||||
ovrLayerEyeFov _sceneLayer;
|
||||
#endif
|
||||
#if (OVR_MAJOR_VERSION == 7)
|
||||
ovrGraphicsLuid _luid;
|
||||
ovrGraphicsLuid _luid;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -79,3 +79,11 @@ inline ovrQuatf ovrFromGlm(const glm::quat & q) {
|
|||
return{ q.x, q.y, q.z, q.w };
|
||||
}
|
||||
|
||||
inline ovrPosef ovrPoseFromGlm(const glm::mat4 & m) {
|
||||
glm::vec3 translation = glm::vec3(m[3]) / m[3].w;
|
||||
glm::quat orientation = glm::quat_cast(m);
|
||||
ovrPosef result;
|
||||
result.Orientation = ovrFromGlm(orientation);
|
||||
result.Position = ovrFromGlm(translation);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -59,11 +59,11 @@ void OculusLegacyDisplayPlugin::resetSensors() {
|
|||
#endif
|
||||
}
|
||||
|
||||
glm::mat4 OculusLegacyDisplayPlugin::getEyePose(Eye eye) const {
|
||||
glm::mat4 OculusLegacyDisplayPlugin::getEyeToHeadTransform(Eye eye) const {
|
||||
#if (OVR_MAJOR_VERSION == 5)
|
||||
return toGlm(_eyePoses[eye]);
|
||||
#else
|
||||
return WindowOpenGLDisplayPlugin::getEyePose(eye);
|
||||
return WindowOpenGLDisplayPlugin::getEyeToHeadTransform(eye);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
virtual glm::uvec2 getRecommendedRenderSize() const override;
|
||||
virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); }
|
||||
virtual void resetSensors() override;
|
||||
virtual glm::mat4 getEyePose(Eye eye) const override;
|
||||
virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override;
|
||||
virtual glm::mat4 getHeadPose() const override;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -160,8 +160,8 @@ void OpenVrDisplayPlugin::resetSensors() {
|
|||
_sensorResetMat = glm::inverse(cancelOutRollAndPitch(_trackedDevicePoseMat4[0]));
|
||||
}
|
||||
|
||||
glm::mat4 OpenVrDisplayPlugin::getEyePose(Eye eye) const {
|
||||
return getHeadPose() * _eyesData[eye]._eyeOffset;
|
||||
glm::mat4 OpenVrDisplayPlugin::getEyeToHeadTransform(Eye eye) const {
|
||||
return _eyesData[eye]._eyeOffset;
|
||||
}
|
||||
|
||||
glm::mat4 OpenVrDisplayPlugin::getHeadPose() const {
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override;
|
||||
virtual void resetSensors() override;
|
||||
|
||||
virtual glm::mat4 getEyePose(Eye eye) const override;
|
||||
virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override;
|
||||
virtual glm::mat4 getHeadPose() const override;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -61,10 +61,6 @@ glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProje
|
|||
return eyeProjection;
|
||||
}
|
||||
|
||||
glm::mat4 StereoDisplayPlugin::getEyePose(Eye eye) const {
|
||||
return mat4();
|
||||
}
|
||||
|
||||
std::vector<QAction*> _screenActions;
|
||||
void StereoDisplayPlugin::activate() {
|
||||
auto screens = qApp->screens();
|
||||
|
|
|
@ -21,7 +21,14 @@ public:
|
|||
|
||||
virtual float getRecommendedAspectRatio() const override;
|
||||
virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override;
|
||||
virtual glm::mat4 getEyePose(Eye eye) const override;
|
||||
|
||||
// NOTE, because Stereo displays don't include head tracking, and therefore
|
||||
// can't include roll or pitch, the eye separation is embedded into the projection
|
||||
// matrix. However, this eliminates the possibility of easily mainpulating
|
||||
// the IPD at the Application level, the way we now allow with HMDs.
|
||||
// If that becomes an issue then we'll need to break up the functionality similar
|
||||
// to the HMD plugins.
|
||||
// virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override;
|
||||
|
||||
protected:
|
||||
void updateScreen();
|
||||
|
|
|
@ -521,7 +521,7 @@ std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(PingType_t pingTy
|
|||
pingPacket->writePrimitive(pingType);
|
||||
pingPacket->writePrimitive(usecTimestampNow());
|
||||
|
||||
return std::move(pingPacket);
|
||||
return pingPacket;
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacket> LimitedNodeList::constructPingReplyPacket(NLPacket& pingPacket) {
|
||||
|
@ -536,7 +536,7 @@ std::unique_ptr<NLPacket> LimitedNodeList::constructPingReplyPacket(NLPacket& pi
|
|||
replyPacket->writePrimitive(timeFromOriginalPing);
|
||||
replyPacket->writePrimitive(usecTimestampNow());
|
||||
|
||||
return std::move(replyPacket);
|
||||
return replyPacket;
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacket> LimitedNodeList::constructICEPingPacket(PingType_t pingType, const QUuid& iceID) {
|
||||
|
@ -546,7 +546,7 @@ std::unique_ptr<NLPacket> LimitedNodeList::constructICEPingPacket(PingType_t pin
|
|||
icePingPacket->write(iceID.toRfc4122());
|
||||
icePingPacket->writePrimitive(pingType);
|
||||
|
||||
return std::move(icePingPacket);
|
||||
return icePingPacket;
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacket> LimitedNodeList::constructICEPingReplyPacket(NLPacket& pingPacket, const QUuid& iceID) {
|
||||
|
|
|
@ -52,15 +52,14 @@ Connection::~Connection() {
|
|||
}
|
||||
|
||||
void Connection::stopSendQueue() {
|
||||
if (_sendQueue) {
|
||||
if (auto sendQueue = _sendQueue.release()) {
|
||||
// grab the send queue thread so we can wait on it
|
||||
QThread* sendQueueThread = _sendQueue->thread();
|
||||
QThread* sendQueueThread = sendQueue->thread();
|
||||
|
||||
// tell the send queue to stop and be deleted
|
||||
|
||||
_sendQueue->stop();
|
||||
_sendQueue->deleteLater();
|
||||
_sendQueue.release();
|
||||
sendQueue->stop();
|
||||
sendQueue->deleteLater();
|
||||
|
||||
// since we're stopping the send queue we should consider our handshake ACK not receieved
|
||||
_hasReceivedHandshakeACK = false;
|
||||
|
@ -858,37 +857,22 @@ void PendingReceivedMessage::enqueuePacket(std::unique_ptr<Packet> packet) {
|
|||
"PendingReceivedMessage::enqueuePacket",
|
||||
"called with a packet that is not part of a message");
|
||||
|
||||
if (_isComplete) {
|
||||
qCDebug(networking) << "UNEXPECTED: Received packet for a message that is already complete";
|
||||
return;
|
||||
}
|
||||
|
||||
auto sequenceNumber = packet->getSequenceNumber();
|
||||
|
||||
if (packet->getPacketPosition() == Packet::PacketPosition::FIRST) {
|
||||
_hasFirstSequenceNumber = true;
|
||||
_firstSequenceNumber = sequenceNumber;
|
||||
} else if (packet->getPacketPosition() == Packet::PacketPosition::LAST) {
|
||||
_hasLastSequenceNumber = true;
|
||||
_lastSequenceNumber = sequenceNumber;
|
||||
} else if (packet->getPacketPosition() == Packet::PacketPosition::ONLY) {
|
||||
_hasFirstSequenceNumber = true;
|
||||
_hasLastSequenceNumber = true;
|
||||
_firstSequenceNumber = sequenceNumber;
|
||||
_lastSequenceNumber = sequenceNumber;
|
||||
if (packet->getPacketPosition() == Packet::PacketPosition::LAST ||
|
||||
packet->getPacketPosition() == Packet::PacketPosition::ONLY) {
|
||||
_hasLastPacket = true;
|
||||
_numPackets = packet->getMessagePartNumber() + 1;
|
||||
}
|
||||
|
||||
// Insert into the packets list in sorted order. Because we generally expect to receive packets in order, begin
|
||||
// searching from the end of the list.
|
||||
auto it = find_if(_packets.rbegin(), _packets.rend(),
|
||||
[&](const std::unique_ptr<Packet>& packet) { return sequenceNumber > packet->getSequenceNumber(); });
|
||||
auto messagePartNumber = packet->getMessagePartNumber();
|
||||
auto it = std::find_if(_packets.rbegin(), _packets.rend(),
|
||||
[&](const std::unique_ptr<Packet>& value) { return messagePartNumber >= value->getMessagePartNumber(); });
|
||||
|
||||
_packets.insert(it.base(), std::move(packet));
|
||||
|
||||
if (_hasFirstSequenceNumber && _hasLastSequenceNumber) {
|
||||
auto numPackets = udt::seqlen(_firstSequenceNumber, _lastSequenceNumber);
|
||||
if (uint64_t(numPackets) == _packets.size()) {
|
||||
_isComplete = true;
|
||||
}
|
||||
if (it != _packets.rend() && ((*it)->getMessagePartNumber() == messagePartNumber)) {
|
||||
qCDebug(networking) << "PendingReceivedMessage::enqueuePacket: This is a duplicate packet";
|
||||
return;
|
||||
}
|
||||
|
||||
_packets.insert(it.base(), std::move(packet));
|
||||
}
|
||||
|
|
|
@ -37,16 +37,13 @@ class Socket;
|
|||
class PendingReceivedMessage {
|
||||
public:
|
||||
void enqueuePacket(std::unique_ptr<Packet> packet);
|
||||
bool isComplete() const { return _isComplete; }
|
||||
bool isComplete() const { return _hasLastPacket && _numPackets == _packets.size(); }
|
||||
|
||||
std::list<std::unique_ptr<Packet>> _packets;
|
||||
|
||||
private:
|
||||
bool _isComplete { false };
|
||||
bool _hasFirstSequenceNumber { false };
|
||||
bool _hasLastSequenceNumber { false };
|
||||
SequenceNumber _firstSequenceNumber;
|
||||
SequenceNumber _lastSequenceNumber;
|
||||
bool _hasLastPacket { false };
|
||||
unsigned int _numPackets { 0 };
|
||||
};
|
||||
|
||||
class Connection : public QObject {
|
||||
|
|
|
@ -13,16 +13,13 @@
|
|||
#define hifi_ConnectionStats_h
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
namespace udt {
|
||||
|
||||
class ConnectionStats {
|
||||
public:
|
||||
struct Stats {
|
||||
std::chrono::microseconds startTime;
|
||||
std::chrono::microseconds endTime;
|
||||
|
||||
enum Event {
|
||||
SentACK,
|
||||
ReceivedACK,
|
||||
|
@ -41,8 +38,14 @@ public:
|
|||
NumEvents
|
||||
};
|
||||
|
||||
using microseconds = std::chrono::microseconds;
|
||||
using Events = std::array<int, NumEvents>;
|
||||
|
||||
microseconds startTime;
|
||||
microseconds endTime;
|
||||
|
||||
// construct a vector for the events of the size of our Enum - default value is zero
|
||||
std::vector<int> events = std::vector<int>((int) Event::NumEvents, 0);
|
||||
Events events;
|
||||
|
||||
// packet counts and sizes
|
||||
int sentPackets { 0 };
|
||||
|
@ -66,6 +69,9 @@ public:
|
|||
int rtt { 0 };
|
||||
int congestionWindowSize { 0 };
|
||||
int packetSendPeriod { 0 };
|
||||
|
||||
// TODO: Remove once Win build supports brace initialization: `Events events {{ 0 }};`
|
||||
Stats() { events.fill(0); }
|
||||
};
|
||||
|
||||
ConnectionStats();
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
void remove(SequenceNumber start, SequenceNumber end);
|
||||
|
||||
int getLength() const { return _length; }
|
||||
bool isEmpty() const { return _length == 0; }
|
||||
SequenceNumber getFirstSequenceNumber() const;
|
||||
SequenceNumber popFirstSequenceNumber();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ using namespace udt;
|
|||
|
||||
int Packet::localHeaderSize(bool isPartOfMessage) {
|
||||
return sizeof(Packet::SequenceNumberAndBitField) +
|
||||
(isPartOfMessage ? sizeof(Packet::MessageNumberAndBitField) : 0);
|
||||
(isPartOfMessage ? sizeof(Packet::MessageNumberAndBitField) + sizeof(MessagePartNumber) : 0);
|
||||
}
|
||||
|
||||
int Packet::totalHeaderSize(bool isPartOfMessage) {
|
||||
|
@ -109,9 +109,11 @@ Packet& Packet::operator=(Packet&& other) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
void Packet::writeMessageNumber(MessageNumber messageNumber) {
|
||||
void Packet::writeMessageNumber(MessageNumber messageNumber, PacketPosition position, MessagePartNumber messagePartNumber) {
|
||||
_isPartOfMessage = true;
|
||||
_messageNumber = messageNumber;
|
||||
_packetPosition = position;
|
||||
_messagePartNumber = messagePartNumber;
|
||||
writeHeader();
|
||||
}
|
||||
|
||||
|
@ -124,7 +126,8 @@ static const uint32_t RELIABILITY_BIT_MASK = uint32_t(1) << (SEQUENCE_NUMBER_BIT
|
|||
static const uint32_t MESSAGE_BIT_MASK = uint32_t(1) << (SEQUENCE_NUMBER_BITS - 3);
|
||||
static const uint32_t BIT_FIELD_MASK = CONTROL_BIT_MASK | RELIABILITY_BIT_MASK | MESSAGE_BIT_MASK;
|
||||
|
||||
static const uint32_t PACKET_POSITION_MASK = uint32_t(0x03) << 30;
|
||||
static const uint8_t PACKET_POSITION_OFFSET = 30;
|
||||
static const uint32_t PACKET_POSITION_MASK = uint32_t(0x03) << PACKET_POSITION_OFFSET;
|
||||
static const uint32_t MESSAGE_NUMBER_MASK = ~PACKET_POSITION_MASK;
|
||||
|
||||
void Packet::readHeader() const {
|
||||
|
@ -139,7 +142,10 @@ void Packet::readHeader() const {
|
|||
if (_isPartOfMessage) {
|
||||
MessageNumberAndBitField* messageNumberAndBitField = seqNumBitField + 1;
|
||||
_messageNumber = *messageNumberAndBitField & MESSAGE_NUMBER_MASK;
|
||||
_packetPosition = static_cast<PacketPosition>(*messageNumberAndBitField >> 30);
|
||||
_packetPosition = static_cast<PacketPosition>(*messageNumberAndBitField >> PACKET_POSITION_OFFSET);
|
||||
|
||||
MessagePartNumber* messagePartNumber = messageNumberAndBitField + 1;
|
||||
_messagePartNumber = *messagePartNumber;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,6 +170,9 @@ void Packet::writeHeader() const {
|
|||
|
||||
MessageNumberAndBitField* messageNumberAndBitField = seqNumBitField + 1;
|
||||
*messageNumberAndBitField = _messageNumber;
|
||||
*messageNumberAndBitField |= _packetPosition << 30;
|
||||
*messageNumberAndBitField |= _packetPosition << PACKET_POSITION_OFFSET;
|
||||
|
||||
MessagePartNumber* messagePartNumber = messageNumberAndBitField + 1;
|
||||
*messagePartNumber = _messagePartNumber;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,10 @@ public:
|
|||
// NOTE: The SequenceNumber is only actually 29 bits to leave room for a bit field
|
||||
using SequenceNumberAndBitField = uint32_t;
|
||||
|
||||
// NOTE: The MessageNumber is only actually 29 bits to leave room for a bit field
|
||||
// NOTE: The MessageNumber is only actually 30 bits to leave room for a bit field
|
||||
using MessageNumber = uint32_t;
|
||||
using MessageNumberAndBitField = uint32_t;
|
||||
using MessagePartNumber = uint32_t;
|
||||
|
||||
// Use same size as MessageNumberAndBitField so we can use the enum with bitwise operations
|
||||
enum PacketPosition : MessageNumberAndBitField {
|
||||
|
@ -55,14 +56,13 @@ public:
|
|||
|
||||
bool isPartOfMessage() const { return _isPartOfMessage; }
|
||||
bool isReliable() const { return _isReliable; }
|
||||
SequenceNumber getSequenceNumber() const { return _sequenceNumber; }
|
||||
|
||||
MessageNumber getMessageNumber() const { return _messageNumber; }
|
||||
|
||||
void setPacketPosition(PacketPosition position) { _packetPosition = position; }
|
||||
PacketPosition getPacketPosition() const { return _packetPosition; }
|
||||
|
||||
void writeMessageNumber(MessageNumber messageNumber);
|
||||
SequenceNumber getSequenceNumber() const { return _sequenceNumber; }
|
||||
MessageNumber getMessageNumber() const { return _messageNumber; }
|
||||
PacketPosition getPacketPosition() const { return _packetPosition; }
|
||||
MessagePartNumber getMessagePartNumber() const { return _messagePartNumber; }
|
||||
|
||||
void writeMessageNumber(MessageNumber messageNumber, PacketPosition position, MessagePartNumber messagePartNumber);
|
||||
void writeSequenceNumber(SequenceNumber sequenceNumber) const;
|
||||
|
||||
protected:
|
||||
|
@ -83,9 +83,10 @@ private:
|
|||
// Simple holders to prevent multiple reading and bitwise ops
|
||||
mutable bool _isReliable { false };
|
||||
mutable bool _isPartOfMessage { false };
|
||||
mutable SequenceNumber _sequenceNumber;
|
||||
mutable PacketPosition _packetPosition { PacketPosition::ONLY };
|
||||
mutable SequenceNumber _sequenceNumber { 0 };
|
||||
mutable MessageNumber _messageNumber { 0 };
|
||||
mutable PacketPosition _packetPosition { PacketPosition::ONLY };
|
||||
mutable MessagePartNumber _messagePartNumber { 0 };
|
||||
};
|
||||
|
||||
} // namespace udt
|
||||
|
|
|
@ -38,12 +38,9 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
return VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER;
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
return 15;
|
||||
return VERSION_ENTITIES_PROTOCOL_CHANNELS;
|
||||
default:
|
||||
return 14;
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -141,5 +141,6 @@ const PacketVersion VERSION_ENTITIES_PARTICLE_RADIUS_PROPERTIES = 41;
|
|||
const PacketVersion VERSION_ENTITIES_PARTICLE_COLOR_PROPERTIES = 42;
|
||||
const PacketVersion VERSION_ENTITIES_PROTOCOL_HEADER_SWAP = 43;
|
||||
const PacketVersion VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER = 44;
|
||||
const PacketVersion VERSION_ENTITIES_PROTOCOL_CHANNELS = 45;
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -105,7 +105,7 @@ std::unique_ptr<Packet> PacketList::createPacketWithExtendedHeader() {
|
|||
}
|
||||
|
||||
void PacketList::closeCurrentPacket(bool shouldSendEmpty) {
|
||||
if (shouldSendEmpty && !_currentPacket) {
|
||||
if (shouldSendEmpty && !_currentPacket && _packets.empty()) {
|
||||
_currentPacket = createPacketWithExtendedHeader();
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,24 @@ QByteArray PacketList::getMessage() {
|
|||
return data;
|
||||
}
|
||||
|
||||
void PacketList::preparePackets(MessageNumber messageNumber) {
|
||||
Q_ASSERT(_packets.size() > 0);
|
||||
|
||||
if (_packets.size() == 1) {
|
||||
_packets.front()->writeMessageNumber(messageNumber, Packet::PacketPosition::ONLY, 0);
|
||||
} else {
|
||||
const auto second = ++_packets.begin();
|
||||
const auto last = --_packets.end();
|
||||
Packet::MessagePartNumber messagePartNumber = 0;
|
||||
std::for_each(second, last, [&](const PacketPointer& packet) {
|
||||
packet->writeMessageNumber(messageNumber, Packet::PacketPosition::MIDDLE, ++messagePartNumber);
|
||||
});
|
||||
|
||||
_packets.front()->writeMessageNumber(messageNumber, Packet::PacketPosition::FIRST, 0);
|
||||
_packets.back()->writeMessageNumber(messageNumber, Packet::PacketPosition::LAST, ++messagePartNumber);
|
||||
}
|
||||
}
|
||||
|
||||
qint64 PacketList::writeData(const char* data, qint64 maxSize) {
|
||||
auto sizeRemaining = maxSize;
|
||||
|
||||
|
|
|
@ -28,28 +28,29 @@ class Packet;
|
|||
class PacketList : public QIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using MessageNumber = uint32_t;
|
||||
using PacketPointer = std::unique_ptr<Packet>;
|
||||
|
||||
static std::unique_ptr<PacketList> create(PacketType packetType, QByteArray extendedHeader = QByteArray(),
|
||||
bool isReliable = false, bool isOrdered = false);
|
||||
static std::unique_ptr<PacketList> fromReceivedPackets(std::list<std::unique_ptr<Packet>>&& packets);
|
||||
|
||||
PacketType getType() const { return _packetType; }
|
||||
bool isReliable() const { return _isReliable; }
|
||||
bool isOrdered() const { return _isOrdered; }
|
||||
|
||||
int getNumPackets() const { return _packets.size() + (_currentPacket ? 1 : 0); }
|
||||
size_t getDataSize() const;
|
||||
size_t getMessageSize() const;
|
||||
QByteArray getMessage();
|
||||
|
||||
QByteArray getExtendedHeader() const { return _extendedHeader; }
|
||||
|
||||
void startSegment();
|
||||
void endSegment();
|
||||
|
||||
PacketType getType() const { return _packetType; }
|
||||
int getNumPackets() const { return _packets.size() + (_currentPacket ? 1 : 0); }
|
||||
|
||||
QByteArray getExtendedHeader() const { return _extendedHeader; }
|
||||
|
||||
size_t getDataSize() const;
|
||||
size_t getMessageSize() const;
|
||||
|
||||
void closeCurrentPacket(bool shouldSendEmpty = false);
|
||||
|
||||
QByteArray getMessage();
|
||||
|
||||
// QIODevice virtual functions
|
||||
virtual bool isSequential() const { return false; }
|
||||
virtual qint64 size() const { return getDataSize(); }
|
||||
|
@ -60,6 +61,8 @@ public:
|
|||
protected:
|
||||
PacketList(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false);
|
||||
PacketList(PacketList&& other);
|
||||
|
||||
void preparePackets(MessageNumber messageNumber);
|
||||
|
||||
virtual qint64 writeData(const char* data, qint64 maxSize);
|
||||
// Not implemented, added an assert so that it doesn't get used by accident
|
||||
|
@ -70,6 +73,7 @@ protected:
|
|||
|
||||
private:
|
||||
friend class ::LimitedNodeList;
|
||||
friend class PacketQueue;
|
||||
friend class SendQueue;
|
||||
friend class Socket;
|
||||
|
||||
|
|
72
libraries/networking/src/udt/PacketQueue.cpp
Normal file
72
libraries/networking/src/udt/PacketQueue.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// PacketQueue.cpp
|
||||
// libraries/networking/src/udt
|
||||
//
|
||||
// Created by Clement on 9/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "PacketQueue.h"
|
||||
|
||||
#include "PacketList.h"
|
||||
|
||||
using namespace udt;
|
||||
|
||||
MessageNumber PacketQueue::getNextMessageNumber() {
|
||||
static const MessageNumber MAX_MESSAGE_NUMBER = MessageNumber(1) << MESSAGE_NUMBER_BITS;
|
||||
_currentMessageNumber = (_currentMessageNumber + 1) % MAX_MESSAGE_NUMBER;
|
||||
return _currentMessageNumber;
|
||||
}
|
||||
|
||||
bool PacketQueue::isEmpty() const {
|
||||
LockGuard locker(_packetsLock);
|
||||
// Only the main channel and it is empty
|
||||
return (_channels.size() == 1) && _channels.front().empty();
|
||||
}
|
||||
|
||||
PacketQueue::PacketPointer PacketQueue::takePacket() {
|
||||
LockGuard locker(_packetsLock);
|
||||
if (isEmpty()) {
|
||||
return PacketPointer();
|
||||
}
|
||||
|
||||
// Find next non empty channel
|
||||
if (_channels[nextIndex()].empty()) {
|
||||
nextIndex();
|
||||
}
|
||||
auto& channel = _channels[_currentIndex];
|
||||
Q_ASSERT(!channel.empty());
|
||||
|
||||
// Take front packet
|
||||
auto packet = std::move(channel.front());
|
||||
channel.pop_front();
|
||||
|
||||
// Remove now empty channel (Don't remove the main channel)
|
||||
if (channel.empty() && _currentIndex != 0) {
|
||||
channel.swap(_channels.back());
|
||||
_channels.pop_back();
|
||||
--_currentIndex;
|
||||
}
|
||||
|
||||
return std::move(packet);
|
||||
}
|
||||
|
||||
unsigned int PacketQueue::nextIndex() {
|
||||
_currentIndex = (++_currentIndex) % _channels.size();
|
||||
return _currentIndex;
|
||||
}
|
||||
|
||||
void PacketQueue::queuePacket(PacketPointer packet) {
|
||||
LockGuard locker(_packetsLock);
|
||||
_channels.front().push_back(std::move(packet));
|
||||
}
|
||||
|
||||
void PacketQueue::queuePacketList(PacketListPointer packetList) {
|
||||
packetList->preparePackets(getNextMessageNumber());
|
||||
|
||||
LockGuard locker(_packetsLock);
|
||||
_channels.push_back(std::move(packetList->_packets));
|
||||
}
|
59
libraries/networking/src/udt/PacketQueue.h
Normal file
59
libraries/networking/src/udt/PacketQueue.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// PacketQueue.h
|
||||
// libraries/networking/src/udt
|
||||
//
|
||||
// Created by Clement on 9/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_PacketQueue_h
|
||||
#define hifi_PacketQueue_h
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "Packet.h"
|
||||
|
||||
namespace udt {
|
||||
|
||||
class PacketList;
|
||||
|
||||
using MessageNumber = uint32_t;
|
||||
|
||||
class PacketQueue {
|
||||
using Mutex = std::recursive_mutex;
|
||||
using LockGuard = std::lock_guard<Mutex>;
|
||||
using PacketPointer = std::unique_ptr<Packet>;
|
||||
using PacketListPointer = std::unique_ptr<PacketList>;
|
||||
using Channel = std::list<PacketPointer>;
|
||||
using Channels = std::vector<Channel>;
|
||||
|
||||
public:
|
||||
void queuePacket(PacketPointer packet);
|
||||
void queuePacketList(PacketListPointer packetList);
|
||||
|
||||
bool isEmpty() const;
|
||||
PacketPointer takePacket();
|
||||
|
||||
Mutex& getLock() { return _packetsLock; }
|
||||
|
||||
private:
|
||||
MessageNumber getNextMessageNumber();
|
||||
unsigned int nextIndex();
|
||||
|
||||
MessageNumber _currentMessageNumber { 0 };
|
||||
|
||||
mutable Mutex _packetsLock; // Protects the packets to be sent.
|
||||
Channels _channels = Channels(1); // One channel per packet list + Main channel
|
||||
unsigned int _currentIndex { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // hifi_PacketQueue_h
|
|
@ -28,9 +28,12 @@
|
|||
|
||||
using namespace udt;
|
||||
|
||||
template <typename Mutex1, typename Mutex2>
|
||||
class DoubleLock {
|
||||
public:
|
||||
DoubleLock(std::mutex& mutex1, std::mutex& mutex2) : _mutex1(mutex1), _mutex2(mutex2) { }
|
||||
using Lock = std::unique_lock<DoubleLock<Mutex1, Mutex2>>;
|
||||
|
||||
DoubleLock(Mutex1& mutex1, Mutex2& mutex2) : _mutex1(mutex1), _mutex2(mutex2) { }
|
||||
|
||||
DoubleLock(const DoubleLock&) = delete;
|
||||
DoubleLock& operator=(const DoubleLock&) = delete;
|
||||
|
@ -45,15 +48,15 @@ public:
|
|||
void unlock() { _mutex1.unlock(); _mutex2.unlock(); }
|
||||
|
||||
private:
|
||||
std::mutex& _mutex1;
|
||||
std::mutex& _mutex2;
|
||||
Mutex1& _mutex1;
|
||||
Mutex2& _mutex2;
|
||||
};
|
||||
|
||||
std::unique_ptr<SendQueue> SendQueue::create(Socket* socket, HifiSockAddr destination) {
|
||||
auto queue = std::unique_ptr<SendQueue>(new SendQueue(socket, destination));
|
||||
|
||||
Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*");
|
||||
|
||||
auto queue = std::unique_ptr<SendQueue>(new SendQueue(socket, destination));
|
||||
|
||||
// Setup queue private thread
|
||||
QThread* thread = new QThread;
|
||||
thread->setObjectName("Networking: SendQueue " + destination.objectName()); // Name thread for easier debug
|
||||
|
@ -68,28 +71,20 @@ std::unique_ptr<SendQueue> SendQueue::create(Socket* socket, HifiSockAddr destin
|
|||
|
||||
thread->start();
|
||||
|
||||
return std::move(queue);
|
||||
return queue;
|
||||
}
|
||||
|
||||
SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) :
|
||||
_socket(socket),
|
||||
_destination(dest)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SendQueue::queuePacket(std::unique_ptr<Packet> packet) {
|
||||
{
|
||||
std::unique_lock<std::mutex> locker(_packetsLock);
|
||||
|
||||
_packets.push_back(std::move(packet));
|
||||
|
||||
// unlock the mutex before we notify
|
||||
locker.unlock();
|
||||
|
||||
// call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets
|
||||
_emptyCondition.notify_one();
|
||||
}
|
||||
_packets.queuePacket(std::move(packet));
|
||||
|
||||
// call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets
|
||||
_emptyCondition.notify_one();
|
||||
|
||||
if (!this->thread()->isRunning() && _state == State::NotStarted) {
|
||||
this->thread()->start();
|
||||
|
@ -97,46 +92,10 @@ void SendQueue::queuePacket(std::unique_ptr<Packet> packet) {
|
|||
}
|
||||
|
||||
void SendQueue::queuePacketList(std::unique_ptr<PacketList> packetList) {
|
||||
Q_ASSERT(packetList->_packets.size() > 0);
|
||||
|
||||
{
|
||||
auto messageNumber = getNextMessageNumber();
|
||||
|
||||
if (packetList->_packets.size() == 1) {
|
||||
auto& packet = packetList->_packets.front();
|
||||
|
||||
packet->setPacketPosition(Packet::PacketPosition::ONLY);
|
||||
packet->writeMessageNumber(messageNumber);
|
||||
} else {
|
||||
bool haveMarkedFirstPacket = false;
|
||||
auto end = packetList->_packets.end();
|
||||
auto lastElement = --packetList->_packets.end();
|
||||
for (auto it = packetList->_packets.begin(); it != end; ++it) {
|
||||
auto& packet = *it;
|
||||
|
||||
if (!haveMarkedFirstPacket) {
|
||||
packet->setPacketPosition(Packet::PacketPosition::FIRST);
|
||||
haveMarkedFirstPacket = true;
|
||||
} else if (it == lastElement) {
|
||||
packet->setPacketPosition(Packet::PacketPosition::LAST);
|
||||
} else {
|
||||
packet->setPacketPosition(Packet::PacketPosition::MIDDLE);
|
||||
}
|
||||
|
||||
packet->writeMessageNumber(messageNumber);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> locker(_packetsLock);
|
||||
|
||||
_packets.splice(_packets.end(), packetList->_packets);
|
||||
|
||||
// unlock the mutex so we can notify
|
||||
locker.unlock();
|
||||
|
||||
// call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets
|
||||
_emptyCondition.notify_one();
|
||||
}
|
||||
_packets.queuePacketList(std::move(packetList));
|
||||
|
||||
// call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets
|
||||
_emptyCondition.notify_one();
|
||||
|
||||
if (!this->thread()->isRunning() && _state == State::NotStarted) {
|
||||
this->thread()->start();
|
||||
|
@ -147,10 +106,8 @@ void SendQueue::stop() {
|
|||
|
||||
_state = State::Stopped;
|
||||
|
||||
// in case we're waiting to send another handshake, release the condition_variable now so we cleanup sooner
|
||||
// Notify all conditions in case we're waiting somewhere
|
||||
_handshakeACKCondition.notify_one();
|
||||
|
||||
// in case the empty condition is waiting for packets/loss release it now so that the queue is cleaned up
|
||||
_emptyCondition.notify_one();
|
||||
}
|
||||
|
||||
|
@ -178,7 +135,7 @@ void SendQueue::ack(SequenceNumber ack) {
|
|||
{ // remove any sequence numbers equal to or lower than this ACK in the loss list
|
||||
std::lock_guard<std::mutex> nakLocker(_naksLock);
|
||||
|
||||
if (_naks.getLength() > 0 && _naks.getFirstSequenceNumber() <= ack) {
|
||||
if (!_naks.isEmpty() && _naks.getFirstSequenceNumber() <= ack) {
|
||||
_naks.remove(_naks.getFirstSequenceNumber(), ack);
|
||||
}
|
||||
}
|
||||
|
@ -191,12 +148,10 @@ void SendQueue::nak(SequenceNumber start, SequenceNumber end) {
|
|||
_timeoutExpiryCount = 0;
|
||||
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
std::unique_lock<std::mutex> nakLocker(_naksLock);
|
||||
|
||||
_naks.insert(start, end);
|
||||
|
||||
// unlock the locked mutex before we notify
|
||||
nakLocker.unlock();
|
||||
{
|
||||
std::lock_guard<std::mutex> nakLocker(_naksLock);
|
||||
_naks.insert(start, end);
|
||||
}
|
||||
|
||||
// call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send
|
||||
_emptyCondition.notify_one();
|
||||
|
@ -207,36 +162,47 @@ void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) {
|
|||
_timeoutExpiryCount = 0;
|
||||
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
std::unique_lock<std::mutex> nakLocker(_naksLock);
|
||||
_naks.clear();
|
||||
|
||||
SequenceNumber first, second;
|
||||
while (packet.bytesLeftToRead() >= (qint64)(2 * sizeof(SequenceNumber))) {
|
||||
packet.readPrimitive(&first);
|
||||
packet.readPrimitive(&second);
|
||||
{
|
||||
std::lock_guard<std::mutex> nakLocker(_naksLock);
|
||||
_naks.clear();
|
||||
|
||||
if (first == second) {
|
||||
_naks.append(first);
|
||||
} else {
|
||||
_naks.append(first, second);
|
||||
SequenceNumber first, second;
|
||||
while (packet.bytesLeftToRead() >= (qint64)(2 * sizeof(SequenceNumber))) {
|
||||
packet.readPrimitive(&first);
|
||||
packet.readPrimitive(&second);
|
||||
|
||||
if (first == second) {
|
||||
_naks.append(first);
|
||||
} else {
|
||||
_naks.append(first, second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unlock the mutex before we notify
|
||||
nakLocker.unlock();
|
||||
|
||||
// call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send
|
||||
_emptyCondition.notify_one();
|
||||
}
|
||||
|
||||
void SendQueue::sendHandshake() {
|
||||
std::unique_lock<std::mutex> handshakeLock { _handshakeMutex };
|
||||
if (!_hasReceivedHandshakeACK) {
|
||||
// we haven't received a handshake ACK from the client, send another now
|
||||
static const auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0);
|
||||
_socket->writeBasePacket(*handshakePacket, _destination);
|
||||
|
||||
// we wait for the ACK or the re-send interval to expire
|
||||
static const auto HANDSHAKE_RESEND_INTERVAL = std::chrono::milliseconds(100);
|
||||
_handshakeACKCondition.wait_for(handshakeLock, HANDSHAKE_RESEND_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
void SendQueue::handshakeACK() {
|
||||
std::unique_lock<std::mutex> locker { _handshakeMutex };
|
||||
|
||||
_hasReceivedHandshakeACK = true;
|
||||
|
||||
// unlock the mutex and notify on the handshake ACK condition
|
||||
locker.unlock();
|
||||
{
|
||||
std::lock_guard<std::mutex> locker { _handshakeMutex };
|
||||
_hasReceivedHandshakeACK = true;
|
||||
}
|
||||
|
||||
// Notify on the handshake ACK condition
|
||||
_handshakeACKCondition.notify_one();
|
||||
}
|
||||
|
||||
|
@ -245,12 +211,6 @@ SequenceNumber SendQueue::getNextSequenceNumber() {
|
|||
return _currentSequenceNumber;
|
||||
}
|
||||
|
||||
uint32_t SendQueue::getNextMessageNumber() {
|
||||
static const MessageNumber MAX_MESSAGE_NUMBER = MessageNumber(1) << MESSAGE_NUMBER_BITS;
|
||||
_currentMessageNumber = (_currentMessageNumber + 1) % MAX_MESSAGE_NUMBER;
|
||||
return _currentMessageNumber;
|
||||
}
|
||||
|
||||
void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber) {
|
||||
// write the sequence number and send the packet
|
||||
newPacket->writeSequenceNumber(sequenceNumber);
|
||||
|
@ -287,207 +247,88 @@ void SendQueue::run() {
|
|||
|
||||
_state = State::Running;
|
||||
|
||||
// Wait for handshake to be complete
|
||||
while (_state == State::Running && !_hasReceivedHandshakeACK) {
|
||||
sendHandshake();
|
||||
|
||||
// Keep processing events
|
||||
QCoreApplication::sendPostedEvents(this);
|
||||
|
||||
// Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake.
|
||||
// Either way let's continue processing - no packets will be sent if no handshake ACK has been received.
|
||||
}
|
||||
|
||||
while (_state == State::Running) {
|
||||
// Record how long the loop takes to execute
|
||||
auto loopStartTimestamp = p_high_resolution_clock::now();
|
||||
|
||||
std::unique_lock<std::mutex> handshakeLock { _handshakeMutex };
|
||||
|
||||
if (!_hasReceivedHandshakeACK) {
|
||||
// we haven't received a handshake ACK from the client
|
||||
// if it has been at least 100ms since we last sent a handshake, send another now
|
||||
|
||||
static const auto HANDSHAKE_RESEND_INTERVAL_MS = std::chrono::milliseconds(100);
|
||||
|
||||
// hold the time of last send in a static
|
||||
static auto lastSendHandshake = p_high_resolution_clock::now() - HANDSHAKE_RESEND_INTERVAL_MS;
|
||||
|
||||
if (p_high_resolution_clock::now() - lastSendHandshake >= HANDSHAKE_RESEND_INTERVAL_MS) {
|
||||
|
||||
// it has been long enough since last handshake, send another
|
||||
static auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0);
|
||||
_socket->writeBasePacket(*handshakePacket, _destination);
|
||||
|
||||
lastSendHandshake = p_high_resolution_clock::now();
|
||||
}
|
||||
|
||||
// we wait for the ACK or the re-send interval to expire
|
||||
_handshakeACKCondition.wait_until(handshakeLock, p_high_resolution_clock::now() + HANDSHAKE_RESEND_INTERVAL_MS);
|
||||
|
||||
// Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake.
|
||||
// Either way let's continue processing - no packets will be sent if no handshake ACK has been received.
|
||||
}
|
||||
|
||||
handshakeLock.unlock();
|
||||
const auto loopStartTimestamp = p_high_resolution_clock::now();
|
||||
|
||||
bool sentAPacket = maybeResendPacket();
|
||||
|
||||
// if we didn't find a packet to re-send AND we think we can fit a new packet on the wire
|
||||
// (this is according to the current flow window size) then we send out a new packet
|
||||
if (_hasReceivedHandshakeACK && !sentAPacket) {
|
||||
if (seqlen(SequenceNumber { (uint32_t) _lastACKSequenceNumber }, _currentSequenceNumber) <= _flowWindowSize) {
|
||||
sentAPacket = maybeSendNewPacket();
|
||||
}
|
||||
if (!sentAPacket) {
|
||||
sentAPacket = maybeSendNewPacket();
|
||||
}
|
||||
|
||||
// since we're a while loop, give the thread a chance to process events
|
||||
QCoreApplication::sendPostedEvents(this, 0);
|
||||
QCoreApplication::sendPostedEvents(this);
|
||||
|
||||
// we just processed events so check now if we were just told to stop
|
||||
if (_state != State::Running) {
|
||||
// If the send queue has been innactive, skip the sleep for
|
||||
// Either _isRunning will have been set to false and we'll break
|
||||
// Or something happened and we'll keep going
|
||||
if (_state != State::Running || isInactive(sentAPacket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hasReceivedHandshakeACK && !sentAPacket) {
|
||||
// check if it is time to break this connection
|
||||
|
||||
// that will be the case if we have had 16 timeouts since hearing back from the client, and it has been
|
||||
// at least 5 seconds
|
||||
|
||||
static const int NUM_TIMEOUTS_BEFORE_INACTIVE = 16;
|
||||
static const int MIN_SECONDS_BEFORE_INACTIVE_MS = 5 * 1000;
|
||||
|
||||
auto sinceEpochNow = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
if (_timeoutExpiryCount >= NUM_TIMEOUTS_BEFORE_INACTIVE
|
||||
&& (sinceEpochNow - _lastReceiverResponse) > MIN_SECONDS_BEFORE_INACTIVE_MS) {
|
||||
// If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
|
||||
// then signal the queue is inactive and return so it can be cleaned up
|
||||
|
||||
#ifdef UDT_CONNECTION_DEBUG
|
||||
qCDebug(networking) << "SendQueue to" << _destination << "reached" << NUM_TIMEOUTS_BEFORE_INACTIVE << "timeouts"
|
||||
<< "and 10s before receiving any ACK/NAK and is now inactive. Stopping.";
|
||||
#endif
|
||||
|
||||
deactivate();
|
||||
|
||||
return;
|
||||
} else {
|
||||
// During our processing above we didn't send any packets
|
||||
|
||||
// If that is still the case we should use a condition_variable_any to sleep until we have data to handle.
|
||||
// To confirm that the queue of packets and the NAKs list are still both empty we'll need to use the DoubleLock
|
||||
DoubleLock doubleLock(_packetsLock, _naksLock);
|
||||
|
||||
if (doubleLock.try_lock()) {
|
||||
// The packets queue and loss list mutexes are now both locked - check if they're still both empty
|
||||
|
||||
if (_packets.empty() && _naks.getLength() == 0) {
|
||||
if (uint32_t(_lastACKSequenceNumber) == uint32_t(_currentSequenceNumber)) {
|
||||
// we've sent the client as much data as we have (and they've ACKed it)
|
||||
// either wait for new data to send or 5 seconds before cleaning up the queue
|
||||
static const auto EMPTY_QUEUES_INACTIVE_TIMEOUT = std::chrono::seconds(5);
|
||||
|
||||
// use our condition_variable_any to wait
|
||||
auto cvStatus = _emptyCondition.wait_for(doubleLock, EMPTY_QUEUES_INACTIVE_TIMEOUT);
|
||||
|
||||
// we have the double lock again - Make sure to unlock it
|
||||
doubleLock.unlock();
|
||||
|
||||
if (cvStatus == std::cv_status::timeout) {
|
||||
#ifdef UDT_CONNECTION_DEBUG
|
||||
qCDebug(networking) << "SendQueue to" << _destination << "has been empty for"
|
||||
<< EMPTY_QUEUES_INACTIVE_TIMEOUT.count()
|
||||
<< "seconds and receiver has ACKed all packets."
|
||||
<< "The queue is now inactive and will be stopped.";
|
||||
#endif
|
||||
|
||||
deactivate();
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// We think the client is still waiting for data (based on the sequence number gap)
|
||||
// Let's wait either for a response from the client or until the estimated timeout
|
||||
auto waitDuration = std::chrono::microseconds(_estimatedTimeout);
|
||||
|
||||
// use our condition_variable_any to wait
|
||||
auto cvStatus = _emptyCondition.wait_for(doubleLock, waitDuration);
|
||||
|
||||
if (cvStatus == std::cv_status::timeout) {
|
||||
// increase the number of timeouts
|
||||
++_timeoutExpiryCount;
|
||||
|
||||
if (SequenceNumber(_lastACKSequenceNumber) < _currentSequenceNumber) {
|
||||
// after a timeout if we still have sent packets that the client hasn't ACKed we
|
||||
// add them to the loss list
|
||||
|
||||
// Note that thanks to the DoubleLock we have the _naksLock right now
|
||||
_naks.append(SequenceNumber(_lastACKSequenceNumber) + 1, _currentSequenceNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// we have the double lock again - Make sure to unlock it
|
||||
doubleLock.unlock();
|
||||
|
||||
// skip to the next iteration
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// we got the try_lock but failed the other conditionals so we need to unlock
|
||||
doubleLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto loopEndTimestamp = p_high_resolution_clock::now();
|
||||
|
||||
// sleep as long as we need until next packet send, if we can
|
||||
auto timeToSleep = (loopStartTimestamp + std::chrono::microseconds(_packetSendPeriod)) - loopEndTimestamp;
|
||||
if (timeToSleep > timeToSleep.zero()) {
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
}
|
||||
const auto loopEndTimestamp = p_high_resolution_clock::now();
|
||||
const auto timeToSleep = (loopStartTimestamp + std::chrono::microseconds(_packetSendPeriod)) - loopEndTimestamp;
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
}
|
||||
}
|
||||
|
||||
bool SendQueue::maybeSendNewPacket() {
|
||||
// we didn't re-send a packet, so time to send a new one
|
||||
std::unique_lock<std::mutex> locker(_packetsLock);
|
||||
|
||||
if (_packets.size() > 0) {
|
||||
SequenceNumber nextNumber = getNextSequenceNumber();
|
||||
if (seqlen(SequenceNumber { (uint32_t) _lastACKSequenceNumber }, _currentSequenceNumber) <= _flowWindowSize) {
|
||||
// we didn't re-send a packet, so time to send a new one
|
||||
|
||||
// grab the first packet we will send
|
||||
std::unique_ptr<Packet> firstPacket;
|
||||
firstPacket.swap(_packets.front());
|
||||
_packets.pop_front();
|
||||
|
||||
std::unique_ptr<Packet> secondPacket;
|
||||
bool shouldSendPairTail = false;
|
||||
|
||||
if (((uint32_t) nextNumber & 0xF) == 0) {
|
||||
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
|
||||
// pull off a second packet if we can before we unlock
|
||||
shouldSendPairTail = true;
|
||||
if (!_packets.isEmpty()) {
|
||||
SequenceNumber nextNumber = getNextSequenceNumber();
|
||||
|
||||
if (_packets.size() > 0) {
|
||||
secondPacket.swap(_packets.front());
|
||||
_packets.pop_front();
|
||||
// grab the first packet we will send
|
||||
std::unique_ptr<Packet> firstPacket = _packets.takePacket();
|
||||
Q_ASSERT(firstPacket);
|
||||
|
||||
std::unique_ptr<Packet> secondPacket;
|
||||
bool shouldSendPairTail = false;
|
||||
|
||||
if (((uint32_t) nextNumber & 0xF) == 0) {
|
||||
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
|
||||
// pull off a second packet if we can before we unlock
|
||||
shouldSendPairTail = true;
|
||||
|
||||
secondPacket = _packets.takePacket();
|
||||
}
|
||||
|
||||
// definitely send the first packet
|
||||
sendNewPacketAndAddToSentList(move(firstPacket), nextNumber);
|
||||
|
||||
// do we have a second in a pair to send as well?
|
||||
if (secondPacket) {
|
||||
sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
|
||||
} else if (shouldSendPairTail) {
|
||||
// we didn't get a second packet to send in the probe pair
|
||||
// send a control packet of type ProbePairTail so the receiver can still do
|
||||
// proper bandwidth estimation
|
||||
static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
|
||||
_socket->writeBasePacket(*pairTailPacket, _destination);
|
||||
}
|
||||
|
||||
// We sent our packet(s), return here
|
||||
return true;
|
||||
}
|
||||
|
||||
// unlock the packets, we're done pulling
|
||||
locker.unlock();
|
||||
|
||||
// definitely send the first packet
|
||||
sendNewPacketAndAddToSentList(move(firstPacket), nextNumber);
|
||||
|
||||
// do we have a second in a pair to send as well?
|
||||
if (secondPacket) {
|
||||
sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
|
||||
} else if (shouldSendPairTail) {
|
||||
// we didn't get a second packet to send in the probe pair
|
||||
// send a control packet of type ProbePairTail so the receiver can still do
|
||||
// proper bandwidth estimation
|
||||
static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
|
||||
_socket->writeBasePacket(*pairTailPacket, _destination);
|
||||
}
|
||||
|
||||
// We sent our packet(s), return here
|
||||
return true;
|
||||
}
|
||||
|
||||
// No packets were sent
|
||||
return false;
|
||||
}
|
||||
|
@ -499,7 +340,7 @@ bool SendQueue::maybeResendPacket() {
|
|||
|
||||
std::unique_lock<std::mutex> naksLocker(_naksLock);
|
||||
|
||||
if (_naks.getLength() > 0) {
|
||||
if (!_naks.isEmpty()) {
|
||||
// pull the sequence number we need to re-send
|
||||
SequenceNumber resendNumber = _naks.popFirstSequenceNumber();
|
||||
naksLocker.unlock();
|
||||
|
@ -538,6 +379,89 @@ bool SendQueue::maybeResendPacket() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool SendQueue::isInactive(bool sentAPacket) {
|
||||
if (!sentAPacket) {
|
||||
// check if it is time to break this connection
|
||||
|
||||
// that will be the case if we have had 16 timeouts since hearing back from the client, and it has been
|
||||
// at least 5 seconds
|
||||
static const int NUM_TIMEOUTS_BEFORE_INACTIVE = 16;
|
||||
static const int MIN_SECONDS_BEFORE_INACTIVE_MS = 5 * 1000;
|
||||
if (_timeoutExpiryCount >= NUM_TIMEOUTS_BEFORE_INACTIVE &&
|
||||
(QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse) > MIN_SECONDS_BEFORE_INACTIVE_MS) {
|
||||
// If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
|
||||
// then signal the queue is inactive and return so it can be cleaned up
|
||||
|
||||
#ifdef UDT_CONNECTION_DEBUG
|
||||
qCDebug(networking) << "SendQueue to" << _destination << "reached" << NUM_TIMEOUTS_BEFORE_INACTIVE << "timeouts"
|
||||
<< "and 5s before receiving any ACK/NAK and is now inactive. Stopping.";
|
||||
#endif
|
||||
|
||||
deactivate();
|
||||
return true;
|
||||
}
|
||||
|
||||
// During our processing above we didn't send any packets
|
||||
|
||||
// If that is still the case we should use a condition_variable_any to sleep until we have data to handle.
|
||||
// To confirm that the queue of packets and the NAKs list are still both empty we'll need to use the DoubleLock
|
||||
using DoubleLock = DoubleLock<std::recursive_mutex, std::mutex>;
|
||||
DoubleLock doubleLock(_packets.getLock(), _naksLock);
|
||||
DoubleLock::Lock locker(doubleLock, std::try_to_lock);
|
||||
|
||||
if (locker.owns_lock() && _packets.isEmpty() && _naks.isEmpty()) {
|
||||
// The packets queue and loss list mutexes are now both locked and they're both empty
|
||||
|
||||
if (uint32_t(_lastACKSequenceNumber) == uint32_t(_currentSequenceNumber)) {
|
||||
// we've sent the client as much data as we have (and they've ACKed it)
|
||||
// either wait for new data to send or 5 seconds before cleaning up the queue
|
||||
static const auto EMPTY_QUEUES_INACTIVE_TIMEOUT = std::chrono::seconds(5);
|
||||
|
||||
// use our condition_variable_any to wait
|
||||
auto cvStatus = _emptyCondition.wait_for(locker, EMPTY_QUEUES_INACTIVE_TIMEOUT);
|
||||
|
||||
// we have the lock again - Make sure to unlock it
|
||||
locker.unlock();
|
||||
|
||||
if (cvStatus == std::cv_status::timeout) {
|
||||
#ifdef UDT_CONNECTION_DEBUG
|
||||
qCDebug(networking) << "SendQueue to" << _destination << "has been empty for"
|
||||
<< EMPTY_QUEUES_INACTIVE_TIMEOUT.count()
|
||||
<< "seconds and receiver has ACKed all packets."
|
||||
<< "The queue is now inactive and will be stopped.";
|
||||
#endif
|
||||
|
||||
// Deactivate queue
|
||||
deactivate();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// We think the client is still waiting for data (based on the sequence number gap)
|
||||
// Let's wait either for a response from the client or until the estimated timeout
|
||||
auto waitDuration = std::chrono::microseconds(_estimatedTimeout);
|
||||
|
||||
// use our condition_variable_any to wait
|
||||
auto cvStatus = _emptyCondition.wait_for(locker, waitDuration);
|
||||
|
||||
if (cvStatus == std::cv_status::timeout) {
|
||||
// increase the number of timeouts
|
||||
++_timeoutExpiryCount;
|
||||
|
||||
if (SequenceNumber(_lastACKSequenceNumber) < _currentSequenceNumber) {
|
||||
// after a timeout if we still have sent packets that the client hasn't ACKed we
|
||||
// add them to the loss list
|
||||
|
||||
// Note that thanks to the DoubleLock we have the _naksLock right now
|
||||
_naks.append(SequenceNumber(_lastACKSequenceNumber) + 1, _currentSequenceNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SendQueue::deactivate() {
|
||||
// this queue is inactive - emit that signal and stop the while
|
||||
emit queueInactive();
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "../HifiSockAddr.h"
|
||||
|
||||
#include "Constants.h"
|
||||
#include "PacketQueue.h"
|
||||
#include "SequenceNumber.h"
|
||||
#include "LossList.h"
|
||||
|
||||
|
@ -38,8 +39,6 @@ class ControlPacket;
|
|||
class Packet;
|
||||
class PacketList;
|
||||
class Socket;
|
||||
|
||||
using MessageNumber = uint32_t;
|
||||
|
||||
class SendQueue : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -87,29 +86,29 @@ private:
|
|||
SendQueue(SendQueue& other) = delete;
|
||||
SendQueue(SendQueue&& other) = delete;
|
||||
|
||||
void sendHandshake();
|
||||
|
||||
void sendPacket(const Packet& packet);
|
||||
void sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber);
|
||||
|
||||
bool maybeSendNewPacket(); // Figures out what packet to send next
|
||||
bool maybeResendPacket(); // Determines whether to resend a packet and which one
|
||||
|
||||
bool isInactive(bool sentAPacket);
|
||||
void deactivate(); // makes the queue inactive and cleans it up
|
||||
|
||||
// Increments current sequence number and return it
|
||||
SequenceNumber getNextSequenceNumber();
|
||||
MessageNumber getNextMessageNumber();
|
||||
|
||||
mutable std::mutex _packetsLock; // Protects the packets to be sent list.
|
||||
std::list<std::unique_ptr<Packet>> _packets; // List of packets to be sent
|
||||
PacketQueue _packets;
|
||||
|
||||
Socket* _socket { nullptr }; // Socket to send packet on
|
||||
HifiSockAddr _destination; // Destination addr
|
||||
|
||||
std::atomic<uint32_t> _lastACKSequenceNumber { 0 }; // Last ACKed sequence number
|
||||
|
||||
MessageNumber _currentMessageNumber { 0 };
|
||||
SequenceNumber _currentSequenceNumber; // Last sequence number sent out
|
||||
std::atomic<uint32_t> _atomicCurrentSequenceNumber { 0 };// Atomic for last sequence number sent out
|
||||
std::atomic<uint32_t> _atomicCurrentSequenceNumber { 0 }; // Atomic for last sequence number sent out
|
||||
|
||||
std::atomic<int> _packetSendPeriod { 0 }; // Interval between two packet send event in microseconds, set from CC
|
||||
std::atomic<State> _state { State::NotStarted };
|
||||
|
|
|
@ -9,7 +9,17 @@
|
|||
|
||||
static PluginContainer* INSTANCE{ nullptr };
|
||||
|
||||
PluginContainer& PluginContainer::getInstance() {
|
||||
Q_ASSERT(INSTANCE);
|
||||
return *INSTANCE;
|
||||
}
|
||||
|
||||
PluginContainer::PluginContainer() {
|
||||
Q_ASSERT(!INSTANCE);
|
||||
INSTANCE = this;
|
||||
};
|
||||
|
||||
PluginContainer::~PluginContainer() {
|
||||
Q_ASSERT(INSTANCE == this);
|
||||
INSTANCE = nullptr;
|
||||
};
|
||||
|
|
|
@ -13,10 +13,13 @@
|
|||
class QAction;
|
||||
class QGLWidget;
|
||||
class QScreen;
|
||||
class DisplayPlugin;
|
||||
|
||||
class PluginContainer {
|
||||
public:
|
||||
static PluginContainer& getInstance();
|
||||
PluginContainer();
|
||||
virtual ~PluginContainer();
|
||||
virtual void addMenu(const QString& menuName) = 0;
|
||||
virtual void removeMenu(const QString& menuName) = 0;
|
||||
virtual QAction* addMenuItem(const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0;
|
||||
|
@ -29,4 +32,5 @@ public:
|
|||
virtual void requestReset() = 0;
|
||||
virtual QGLWidget* getPrimarySurface() = 0;
|
||||
virtual bool isForeground() = 0;
|
||||
virtual const DisplayPlugin* getActiveDisplayPlugin() const = 0;
|
||||
};
|
||||
|
|
|
@ -497,9 +497,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec
|
|||
}
|
||||
|
||||
QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget) {
|
||||
QPointF transformedPos = _mouseTranslator(originalPoint);
|
||||
transformedPos = mapWindowToUi(transformedPos, originalWidget);
|
||||
return transformedPos;
|
||||
return _mouseTranslator(originalPoint);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -282,7 +282,7 @@ void UDTTest::sendPacket() {
|
|||
packetList->write(randomPaddedData);
|
||||
}
|
||||
|
||||
packetList->closeCurrentPacket(false);
|
||||
packetList->closeCurrentPacket();
|
||||
|
||||
_totalQueuedBytes += packetList->getDataSize();
|
||||
_totalQueuedPackets += packetList->getNumPackets();
|
||||
|
|
|
@ -86,6 +86,8 @@ function createAllToys() {
|
|||
|
||||
createBasketballHoop();
|
||||
|
||||
createBasketballRack();
|
||||
|
||||
createGates();
|
||||
|
||||
createFire();
|
||||
|
@ -673,6 +675,147 @@ function createBasketballHoop() {
|
|||
});
|
||||
}
|
||||
|
||||
function createBasketballRack() {
|
||||
var NUMBER_OF_BALLS = 4;
|
||||
var DIAMETER = 0.30;
|
||||
var RESET_DISTANCE = 1;
|
||||
var MINIMUM_MOVE_LENGTH = 0.05;
|
||||
var basketballURL = HIFI_PUBLIC_BUCKET + "models/content/basketball2.fbx";
|
||||
var basketballCollisionSoundURL = HIFI_PUBLIC_BUCKET + "sounds/basketball/basketball.wav";
|
||||
var rackURL = HIFI_PUBLIC_BUCKET + "models/basketball_hoop/basketball_rack.fbx";
|
||||
var rackCollisionHullURL = HIFI_PUBLIC_BUCKET + "models/basketball_hoop/rack_collision_hull.obj";
|
||||
|
||||
var rackRotation = Quat.fromPitchYawRollDegrees(0, -90, 0);
|
||||
|
||||
var rackStartPosition = {
|
||||
x: 542.86,
|
||||
y: 494.84,
|
||||
z: 475.06
|
||||
};
|
||||
var rack = Entities.addEntity({
|
||||
name: 'Basketball Rack',
|
||||
type: "Model",
|
||||
modelURL: rackURL,
|
||||
position: rackStartPosition,
|
||||
rotation: rackRotation,
|
||||
shapeType: 'compound',
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
linearDamping: 1,
|
||||
dimensions: {
|
||||
x: 0.4,
|
||||
y: 1.37,
|
||||
z: 1.73
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
ignoreForCollisions: false,
|
||||
compoundShapeURL: rackCollisionHullURL
|
||||
});
|
||||
|
||||
setEntityCustomData(resetKey, rack, {
|
||||
resetMe: true
|
||||
});
|
||||
|
||||
setEntityCustomData(GRABBABLE_DATA_KEY, rack, {
|
||||
grabbable: false
|
||||
});
|
||||
|
||||
var collidingBalls = [];
|
||||
var originalBallPositions = [];
|
||||
|
||||
function createCollidingBalls() {
|
||||
var position = rackStartPosition;
|
||||
|
||||
var i;
|
||||
for (i = 0; i < NUMBER_OF_BALLS; i++) {
|
||||
var ballPosition = {
|
||||
x: position.x,
|
||||
y: position.y + DIAMETER * 2,
|
||||
z: position.z + (DIAMETER) - (DIAMETER * i)
|
||||
};
|
||||
|
||||
var collidingBall = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: 'Colliding Basketball',
|
||||
shapeType: 'Sphere',
|
||||
position: {
|
||||
x: position.x + (DIAMETER * 2) - (DIAMETER * i),
|
||||
y: position.y + DIAMETER * 2,
|
||||
z: position.z
|
||||
},
|
||||
dimensions: {
|
||||
x: DIAMETER,
|
||||
y: DIAMETER,
|
||||
z: DIAMETER
|
||||
},
|
||||
restitution: 1.0,
|
||||
linearDamping: 0.00001,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
ignoreForCollisions: false,
|
||||
modelURL: basketballURL,
|
||||
});
|
||||
|
||||
collidingBalls.push(collidingBall);
|
||||
originalBallPositions.push(position);
|
||||
}
|
||||
}
|
||||
|
||||
function testBallDistanceFromStart() {
|
||||
var resetCount = 0;
|
||||
|
||||
collidingBalls.forEach(function(ball, index) {
|
||||
var currentPosition = Entities.getEntityProperties(ball, "position").position;
|
||||
var originalPosition = originalBallPositions[index];
|
||||
var distance = Vec3.subtract(originalPosition, currentPosition);
|
||||
var length = Vec3.length(distance);
|
||||
|
||||
if (length > RESET_DISTANCE) {
|
||||
Script.setTimeout(function() {
|
||||
var newPosition = Entities.getEntityProperties(ball, "position").position;
|
||||
var moving = Vec3.length(Vec3.subtract(currentPosition, newPosition));
|
||||
if (moving < MINIMUM_MOVE_LENGTH) {
|
||||
resetCount++;
|
||||
if (resetCount === NUMBER_OF_BALLS) {
|
||||
deleteCollidingBalls();
|
||||
createCollidingBalls();
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteEntity(entityID) {
|
||||
if (entityID === rack) {
|
||||
deleteCollidingBalls();
|
||||
Script.clearInterval(distanceCheckInterval);
|
||||
Entities.deletingEntity.disconnect(deleteEntity);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteCollidingBalls() {
|
||||
while (collidingBalls.length > 0) {
|
||||
Entities.deleteEntity(collidingBalls.pop());
|
||||
}
|
||||
}
|
||||
|
||||
createCollidingBalls();
|
||||
Entities.deletingEntity.connect(deleteEntity);
|
||||
|
||||
var distanceCheckInterval = Script.setInterval(testBallDistanceFromStart, 1000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function createWand(position) {
|
||||
var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx';
|
||||
var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj';
|
||||
|
|
Loading…
Reference in a new issue