mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 12:04:18 +02:00
Merge branch 'android_goto_splash' of https://github.com/gcalero/hifi into android_goto_splash
This commit is contained in:
commit
28b63c2593
44 changed files with 973 additions and 696 deletions
|
@ -41,8 +41,8 @@ public class InterfaceActivity extends QtActivity {
|
|||
private native void nativeOnDestroy();
|
||||
private native void nativeGotoUrl(String url);
|
||||
private native void nativeGoBackFromAndroidActivity();
|
||||
//private native void nativeOnStop();
|
||||
//private native void nativeOnStart();
|
||||
private native void nativeEnterBackground();
|
||||
private native void nativeEnterForeground();
|
||||
//private native void saveRealScreenSize(int width, int height);
|
||||
//private native void setAppVersion(String version);
|
||||
private native long nativeOnExitVr();
|
||||
|
@ -124,14 +124,13 @@ public class InterfaceActivity extends QtActivity {
|
|||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
// nativeOnStart();
|
||||
nativeEnterForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Log.d("[Background]","Calling nativeOnStop from InterfaceActivity");
|
||||
// nativeOnStop();
|
||||
super.onStop();
|
||||
nativeEnterBackground();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -33,7 +33,7 @@ uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) {
|
||||
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) {
|
||||
std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
if (itr != _lastOtherAvatarEncodeTime.end()) {
|
||||
itr->second = time;
|
||||
|
|
|
@ -113,7 +113,7 @@ public:
|
|||
ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
|
||||
|
||||
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
|
||||
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time);
|
||||
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
|
||||
|
||||
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
|
||||
auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar];
|
||||
|
|
|
@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66.zip
|
||||
URL_MD5 91edfde96e06efc847ca327ab97f4c74
|
||||
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v2.zip
|
||||
URL_MD5 d76bdb3e2bf7ae5d20115bd97b0c44a8
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
85
interface/resources/icons/+android/backward.svg
Normal file → Executable file
85
interface/resources/icons/+android/backward.svg
Normal file → Executable file
|
@ -1,70 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 150 450"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="backward.svg"
|
||||
width="150"
|
||||
height="450"><metadata
|
||||
id="metadata20"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs18" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="971"
|
||||
id="namedview16"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1125147"
|
||||
inkscape:cx="12.362631"
|
||||
inkscape:cy="233.34206"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#CCCCCC;}
|
||||
.st2{fill:#1398BB;}
|
||||
.st3{fill:#31D8FF;}
|
||||
</style><g
|
||||
id="Layer_1"
|
||||
transform="translate(0,300)"><path
|
||||
id="path4148"
|
||||
d="M 103.51176,131.14554 61.96248,89.596247 58.602209,86.235997 61.96248,82.875716 104.41995,40.41826 c 2.67913,-2.679128 2.67913,-7.083865 0,-9.762962 -1.31687,-1.316854 -3.04241,-2.043396 -4.90418,-2.043396 -1.86178,0 -3.587322,0.726542 -4.904182,2.043396 L 39.030908,86.235997 93.70341,140.9085 c 1.31686,1.31689 3.042403,2.04343 4.904176,2.04343 1.861774,0 3.587314,-0.72654 4.904174,-2.04343 2.67914,-2.67913 2.67914,-7.0838 0,-9.76296 z"
|
||||
class="st0"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#54c9ef;fill-opacity:1" /><path
|
||||
style="fill:#54c9ef;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
class="st0"
|
||||
d="m 103.51176,-28.954508 -41.54928,-41.549293 -3.360271,-3.36025 3.360271,-3.360281 42.45747,-42.457458 c 2.67913,-2.67913 2.67913,-7.08386 0,-9.76296 -1.31687,-1.31685 -3.04241,-2.0434 -4.90418,-2.0434 -1.86178,0 -3.587322,0.72655 -4.904182,2.0434 l -55.58068,55.580699 54.672502,54.672505 c 1.31686,1.316886 3.042403,2.043428 4.904176,2.043428 1.861774,0 3.587314,-0.726542 4.904174,-2.043428 2.67914,-2.679128 2.67914,-7.083802 0,-9.762962 z"
|
||||
id="path4158" /><path
|
||||
id="path4169"
|
||||
d="m 103.51176,-188.41523 -41.54928,-41.54929 -3.360271,-3.36025 3.360271,-3.36028 42.45747,-42.45746 c 2.67913,-2.67913 2.67913,-7.08386 0,-9.76296 -1.31687,-1.31685 -3.04241,-2.0434 -4.90418,-2.0434 -1.86178,0 -3.587322,0.72655 -4.904182,2.0434 l -55.58068,55.5807 54.672502,54.67251 c 1.31686,1.31688 3.042403,2.04342 4.904176,2.04342 1.861774,0 3.587314,-0.72654 4.904174,-2.04342 2.67914,-2.67913 2.67914,-7.08381 0,-9.76297 z"
|
||||
class="st0"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1" /></g><g
|
||||
id="Layer_2"
|
||||
transform="translate(0,300)" /></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0.7;}
|
||||
.st1{fill:none;stroke:#FFFFFF;stroke-width:6.4193;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_2_1_">
|
||||
</g>
|
||||
<g class="st0">
|
||||
<polyline class="st1" points="24.3,43.4 5.8,25.2 5.8,25.1 24,6.6 "/>
|
||||
<line class="st1" x1="7.2" y1="25" x2="44.2" y2="25"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 688 B |
|
@ -1,56 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="150px"
|
||||
height="150px"
|
||||
viewBox="0 0 150 150"
|
||||
enable-background="new 0 0 150 150"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="myview-a.svg"><metadata
|
||||
id="metadata15"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs13" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview11"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.5733333"
|
||||
inkscape:cx="75"
|
||||
inkscape:cy="75"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" /><title
|
||||
id="title3">Asset 3</title><g
|
||||
id="Layer_2"
|
||||
transform="translate(0,18)"><g
|
||||
id="Layer_1-2"><path
|
||||
d="M 135.105,20 12.135,20 C 4.381,20 0,25.958 0,34.238 l 0,80.608 c -0.653,7.229 4.677,13.618 11.906,14.271 0.411,0.037 0.824,0.055 1.236,0.053 l 122.664,0 c 7.709,0 14.105,-8.061 14.105,-16.34 l 0,-80.607 C 150,23.943 142.815,20 135.105,20 Z m 2.322,95.678 c 0.005,2.32 -1.802,4.241 -4.118,4.381 l -118.02,0 c -2.316,-0.14 -4.122,-2.061 -4.118,-4.381 l 0,-82.229 c -0.004,-2.32 1.802,-4.242 4.118,-4.381 l 118.02,0 c 2.316,0.139 4.123,2.061 4.118,4.381 l 0,82.229 z"
|
||||
id="path7"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" /><polygon
|
||||
points="107.462,74.322 129.761,74.322 129.761,67.576 99.795,67.576 99.62,67.444 99.533,67.576 49.854,67.576 49.767,67.444 49.547,67.576 18.137,67.576 18.137,74.322 42.188,74.322 18.137,89.349 18.137,97.584 53.578,74.322 61.595,74.322 44.378,113.838 51.519,113.838 68.691,74.322 80.651,74.322 97.868,113.838 105.009,113.838 87.792,74.322 96.072,74.322 129.761,97.41 129.761,89.174 "
|
||||
id="polygon9"
|
||||
style="fill:#ffffff" /></g></g></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0.7;}
|
||||
.st1{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_2_1_">
|
||||
</g>
|
||||
<g class="st0">
|
||||
<path class="st1" d="M48.1,22.5c-1.6-1.9-3.2-4-5.2-5.6c-5.2-4.4-11.1-6.9-17.8-7.1c-6,0.1-11.1,2-15.8,5.2c-3,2-5.6,4.4-7.6,7.4
|
||||
c-1.1,1.7-1.1,3.3,0,5c1.8,2.7,4.1,4.8,6.6,6.7c4.9,3.6,10.4,5.9,16.6,5.9c5.8,0,10.9-2,15.6-5.2c3-2,5.6-4.4,7.6-7.4
|
||||
C49.4,25.8,49.5,24.1,48.1,22.5z M46.4,27.1C41,34,33.9,38,26.2,38.5c-7.3-0.1-12.6-2.4-17.4-6c-2.2-1.6-4.1-3.5-5.6-5.8
|
||||
c-0.7-1-0.9-2-0.1-3.1c4.8-7.1,16.4-14.6,28.2-11.1c6.2,1.9,11.3,5.3,15.2,10.5C47.8,24.7,47.8,25.3,46.4,27.1z"/>
|
||||
<path class="st1" d="M25,15.6c-5.3,0-9.4,4.1-9.5,9.4c0,5.3,4.2,9.5,9.5,9.5c5.3,0,9.4-4.2,9.4-9.5C34.4,19.7,30.3,15.6,25,15.6z
|
||||
M24.9,32.9c-4.2,0-7.8-3.6-7.8-7.9c0-4.2,3.6-7.8,7.8-7.8c4.2,0,7.9,3.6,7.8,7.8C32.8,29.3,29.2,32.9,24.9,32.9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -122,9 +122,21 @@ Item {
|
|||
newViewRequestedCallback(request)
|
||||
}
|
||||
|
||||
// Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false
|
||||
// as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns
|
||||
// loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed
|
||||
// when QT fixes this.
|
||||
property bool safeLoading: false
|
||||
property bool loadingLatched: false
|
||||
property var loadingRequest: null
|
||||
onLoadingChanged: {
|
||||
flick.onLoadingChanged(loadRequest)
|
||||
loadingChangedCallback(loadRequest)
|
||||
webViewCore.loadingRequest = loadRequest;
|
||||
webViewCore.safeLoading = webViewCore.loading && !loadingLatched;
|
||||
webViewCore.loadingLatched |= webViewCore.loading;
|
||||
}
|
||||
onSafeLoadingChanged: {
|
||||
flick.onLoadingChanged(webViewCore.loadingRequest)
|
||||
loadingChangedCallback(webViewCore.loadingRequest)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,7 +145,7 @@ Item {
|
|||
x: flick.width/2 - width/2
|
||||
y: flick.height/2 - height/2
|
||||
source: "../../icons/loader-snake-64-w.gif"
|
||||
visible: webViewCore.loading && /^(http.*|)$/i.test(webViewCore.url.toString())
|
||||
visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString())
|
||||
playing: visible
|
||||
z: 10000
|
||||
}
|
||||
|
|
71
interface/resources/qml/hifi/+android/ActionBar.qml
Normal file
71
interface/resources/qml/hifi/+android/ActionBar.qml
Normal file
|
@ -0,0 +1,71 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import Qt.labs.settings 1.0
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit" as HifiControlsUit
|
||||
import "../../controls" as HifiControls
|
||||
import ".."
|
||||
|
||||
Item {
|
||||
id: actionBar
|
||||
x:0
|
||||
y:0
|
||||
width: 300
|
||||
height: 300
|
||||
z: -1
|
||||
|
||||
signal sendToScript(var message);
|
||||
signal windowClosed();
|
||||
|
||||
property bool shown: true
|
||||
|
||||
onShownChanged: {
|
||||
actionBar.visible = shown;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill : parent
|
||||
color: "transparent"
|
||||
Flow {
|
||||
id: flowMain
|
||||
spacing: 10
|
||||
flow: Flow.TopToBottom
|
||||
layoutDirection: Flow.TopToBottom
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// put on bottom
|
||||
x = 50;
|
||||
y = 0;
|
||||
width = 300;
|
||||
height = 300;
|
||||
}
|
||||
|
||||
function addButton(properties) {
|
||||
var component = Qt.createComponent("button.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var button = component.createObject(flowMain);
|
||||
// copy all properites to button
|
||||
var keys = Object.keys(properties).forEach(function (key) {
|
||||
button[key] = properties[key];
|
||||
});
|
||||
return button;
|
||||
} else if( component.status == Component.Error) {
|
||||
console.log("Load button errors " + component.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
function urlHelper(src) {
|
||||
if (src.match(/\bhttp/)) {
|
||||
return src;
|
||||
} else {
|
||||
return "../../../" + src;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -40,7 +40,7 @@ Item {
|
|||
|
||||
Component.onCompleted: {
|
||||
// put on bottom
|
||||
x = 0;
|
||||
x = parent.width-300;
|
||||
y = 0;
|
||||
width = 300;
|
||||
height = 300;
|
||||
|
|
|
@ -10,7 +10,7 @@ import ".."
|
|||
|
||||
Item {
|
||||
id: modesbar
|
||||
y:60
|
||||
y:20
|
||||
Rectangle {
|
||||
anchors.fill : parent
|
||||
color: "transparent"
|
||||
|
@ -25,9 +25,9 @@ Item {
|
|||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
width = 300 + 30; // That 30 is extra regardless the qty of items shown
|
||||
height = 300 + 30;
|
||||
x=Window.innerWidth - width;
|
||||
width = 300; // That 30 is extra regardless the qty of items shown
|
||||
height = 300;
|
||||
x=parent.width - 540;
|
||||
}
|
||||
|
||||
function addButton(properties) {
|
||||
|
|
|
@ -242,6 +242,7 @@ extern "C" {
|
|||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include <android/log.h>
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
#endif
|
||||
|
||||
enum ApplicationEvent {
|
||||
|
@ -3046,28 +3047,20 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
|
||||
static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location";
|
||||
static const QString SENT_TO_ENTRY = "entry";
|
||||
static const QString SENT_TO_SANDBOX = "sandbox";
|
||||
|
||||
QString sentTo;
|
||||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (firstRun.get()) {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
qCDebug(interfaceapp) << "First run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("default location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
qCDebug(interfaceapp) << "First run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("default location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
#else
|
||||
showHelp();
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
sentTo = SENT_TO_SANDBOX;
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
}
|
||||
showHelp();
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
#endif
|
||||
firstRun.set(false);
|
||||
firstRun.set(false);
|
||||
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||
|
@ -4682,7 +4675,7 @@ void Application::init() {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
|
||||
connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity,
|
||||
connect(_entitySimulation.get(), &PhysicalEntitySimulation::entityCollisionWithEntity,
|
||||
getEntities().data(), &EntityTreeRenderer::entityCollisionWithEntity);
|
||||
|
||||
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
|
||||
|
@ -5277,11 +5270,13 @@ void Application::update(float deltaTime) {
|
|||
{
|
||||
PROFILE_RANGE(simulation_physics, "PreStep");
|
||||
PerformanceTimer perfTimer("preStep)");
|
||||
static VectorOfMotionStates motionStates;
|
||||
_entitySimulation->getObjectsToRemoveFromPhysics(motionStates);
|
||||
_physicsEngine->removeObjects(motionStates);
|
||||
_entitySimulation->deleteObjectsRemovedFromPhysics();
|
||||
{
|
||||
const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
|
||||
_physicsEngine->removeObjects(motionStates);
|
||||
_entitySimulation->deleteObjectsRemovedFromPhysics();
|
||||
}
|
||||
|
||||
VectorOfMotionStates motionStates;
|
||||
getEntities()->getTree()->withReadLock([&] {
|
||||
_entitySimulation->getObjectsToAddToPhysics(motionStates);
|
||||
_physicsEngine->addObjects(motionStates);
|
||||
|
@ -5295,7 +5290,7 @@ void Application::update(float deltaTime) {
|
|||
|
||||
_entitySimulation->applyDynamicChanges();
|
||||
|
||||
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
|
||||
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
|
||||
_physicsEngine->removeObjects(motionStates);
|
||||
avatarManager->getObjectsToAddToPhysics(motionStates);
|
||||
_physicsEngine->addObjects(motionStates);
|
||||
|
@ -7868,7 +7863,8 @@ void Application::saveNextPhysicsStats(QString filename) {
|
|||
|
||||
void Application::openAndroidActivity(const QString& activityName) {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
getActiveDisplayPlugin()->deactivate();
|
||||
qDebug() << "[Background-HIFI] Application::openAndroidActivity";
|
||||
//getActiveDisplayPlugin()->deactivate();
|
||||
AndroidHelper::instance().requestActivity(activityName);
|
||||
connect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity);
|
||||
#endif
|
||||
|
@ -7876,11 +7872,63 @@ void Application::openAndroidActivity(const QString& activityName) {
|
|||
|
||||
void Application::restoreAfterAndroidActivity() {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) {
|
||||
qDebug() << "[Background-HIFI] restoreAfterAndroidActivity: this wouldn't be needed";
|
||||
|
||||
/*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) {
|
||||
qWarning() << "Could not re-activate display plugin";
|
||||
}
|
||||
}*/
|
||||
disconnect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void Application::enterBackground() {
|
||||
qDebug() << "[Background-HIFI] enterBackground begin";
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
||||
"stop", Qt::BlockingQueuedConnection);
|
||||
qDebug() << "[Background-HIFI] deactivating display plugin";
|
||||
getActiveDisplayPlugin()->deactivate();
|
||||
qDebug() << "[Background-HIFI] enterBackground end";
|
||||
}
|
||||
void Application::enterForeground() {
|
||||
qDebug() << "[Background-HIFI] enterForeground qApp?" << (qApp?"yeah":"false");
|
||||
if (qApp && DependencyManager::isSet<AudioClient>()) {
|
||||
qDebug() << "[Background-HIFI] audioclient.start()";
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
||||
"start", Qt::BlockingQueuedConnection);
|
||||
} else {
|
||||
qDebug() << "[Background-HIFI] audioclient.start() not done";
|
||||
}
|
||||
if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) {
|
||||
qWarning() << "[Background-HIFI] Could not re-activate display plugin";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
||||
JNIEXPORT void
|
||||
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
|
||||
qDebug() << "[Background-HIFI] nativeEnterBackground";
|
||||
if (qApp) {
|
||||
qDebug() << "[Background-HIFI] nativeEnterBackground begin (qApp)";
|
||||
qApp->enterBackground();
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void
|
||||
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) {
|
||||
qDebug() << "[Background-HIFI] nativeEnterForeground";
|
||||
if (qApp) {
|
||||
qDebug() << "[Background-HIFI] nativeEnterForeground begin (qApp)";
|
||||
qApp->enterForeground();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#include "Application.moc"
|
||||
|
|
|
@ -292,6 +292,11 @@ public:
|
|||
|
||||
void replaceDomainContent(const QString& url);
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void enterBackground();
|
||||
void enterForeground();
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ void DiscoverabilityManager::updateLocation() {
|
|||
|
||||
// Update Steam
|
||||
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
|
||||
steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingShareableAddress());
|
||||
steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingPublicAddress());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ bool SelectionScriptingInterface::enableListHighlight(const QString& listName, c
|
|||
}
|
||||
|
||||
if (!(*highlightStyle).isBoundToList()) {
|
||||
enableListToScene(listName);
|
||||
(*highlightStyle).setBoundToList(true);
|
||||
}
|
||||
|
||||
|
@ -133,6 +134,7 @@ bool SelectionScriptingInterface::disableListHighlight(const QString& listName)
|
|||
auto highlightStyle = _highlightStyleMap.find(listName);
|
||||
if (highlightStyle != _highlightStyleMap.end()) {
|
||||
if ((*highlightStyle).isBoundToList()) {
|
||||
disableListToScene(listName);
|
||||
}
|
||||
|
||||
_highlightStyleMap.erase(highlightStyle);
|
||||
|
@ -476,4 +478,4 @@ QVariantMap SelectionHighlightStyle::toVariantMap() const {
|
|||
properties["isOutlineSmooth"] = _style._isOutlineSmooth;
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,6 +165,8 @@ public:
|
|||
* @param listName {string} name of the selection
|
||||
* @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle).
|
||||
* @returns {bool} true if the selection was successfully enabled for highlight.
|
||||
*
|
||||
* Note: This function will implicitly call Selection.enableListToScene
|
||||
*/
|
||||
Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle);
|
||||
|
||||
|
@ -175,8 +177,10 @@ public:
|
|||
* @function Selection.disableListHighlight
|
||||
* @param listName {string} name of the selection
|
||||
* @returns {bool} true if the selection was successfully disabled for highlight, false otherwise.
|
||||
*
|
||||
* Note: This function will implicitly call Selection.disableListToScene
|
||||
*/
|
||||
Q_INVOKABLE bool disableListHighlight(const QString& listName);
|
||||
Q_INVOKABLE bool disableListHighlight(const QString& listName);
|
||||
/**jsdoc
|
||||
* Enable scene selection for the named selection.
|
||||
* If the Selection doesn't exist, it will be created.
|
||||
|
@ -246,7 +250,7 @@ private:
|
|||
void setupHandler(const QString& selectionName);
|
||||
void removeHandler(const QString& selectionName);
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_SelectionScriptingInterface_h
|
||||
|
|
|
@ -95,7 +95,7 @@ QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) {
|
|||
QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QString& userSelectedFilename) {
|
||||
|
||||
// adding URL to snapshot
|
||||
QUrl currentURL = DependencyManager::get<AddressManager>()->currentShareableAddress();
|
||||
QUrl currentURL = DependencyManager::get<AddressManager>()->currentPublicAddress();
|
||||
shot.setText(URL, currentURL.toString());
|
||||
|
||||
QString username = DependencyManager::get<AccountManager>()->getAccountInfo().getUsername();
|
||||
|
|
|
@ -65,7 +65,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
|
|||
|
||||
} else {
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, contents);
|
||||
delete this;
|
||||
this->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,23 +75,27 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) {
|
|||
if (replyString.size() == 0) {
|
||||
replyString = reply.errorString();
|
||||
}
|
||||
replyString = replyString.left(1000); // Only print first 1000 characters of error
|
||||
qDebug() << "Snapshot upload reply error (truncated):" << replyString;
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, replyString); // maybe someday include _inWorldLocation, _filename?
|
||||
delete this;
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
void SnapshotUploader::createStorySuccess(QNetworkReply& reply) {
|
||||
QString replyString = reply.readAll();
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(false, replyString);
|
||||
delete this;
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
void SnapshotUploader::createStoryFailure(QNetworkReply& reply) {
|
||||
QString replyString = reply.readAll();
|
||||
qDebug() << "Error " << reply.errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation;
|
||||
qDebug() << "Error " << reply.errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation;
|
||||
if (replyString.size() == 0) {
|
||||
replyString = reply.errorString();
|
||||
}
|
||||
replyString = replyString.left(1000); // Only print first 1000 characters of error
|
||||
qDebug() << "Snapshot story upload reply error (truncated):" << replyString;
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, replyString);
|
||||
delete this;
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
|
|
|
@ -1911,7 +1911,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
|||
}
|
||||
}
|
||||
|
||||
void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) {
|
||||
void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) {
|
||||
if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) {
|
||||
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority;
|
||||
}
|
||||
|
@ -1942,7 +1942,7 @@ void EntityItem::clearSimulationOwnership() {
|
|||
|
||||
}
|
||||
|
||||
void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) {
|
||||
void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) {
|
||||
_simulationOwner.setPendingPriority(priority, timestamp);
|
||||
}
|
||||
|
||||
|
@ -2962,13 +2962,6 @@ void EntityItem::retrieveMarketplacePublicKey() {
|
|||
}
|
||||
|
||||
void EntityItem::preDelete() {
|
||||
// clear out any left-over actions
|
||||
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
|
||||
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
|
||||
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||
if (simulation) {
|
||||
clearActions(simulation);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
|
|
|
@ -304,14 +304,14 @@ public:
|
|||
|
||||
// FIXME not thread safe?
|
||||
const SimulationOwner& getSimulationOwner() const { return _simulationOwner; }
|
||||
void setSimulationOwner(const QUuid& id, quint8 priority);
|
||||
void setSimulationOwner(const QUuid& id, uint8_t priority);
|
||||
void setSimulationOwner(const SimulationOwner& owner);
|
||||
void promoteSimulationPriority(quint8 priority);
|
||||
void promoteSimulationPriority(uint8_t priority);
|
||||
|
||||
quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); }
|
||||
uint8_t getSimulationPriority() const { return _simulationOwner.getPriority(); }
|
||||
QUuid getSimulatorID() const { return _simulationOwner.getID(); }
|
||||
void clearSimulationOwnership();
|
||||
void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp);
|
||||
void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp);
|
||||
uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
|
||||
void rememberHasSimulationOwnershipBid() const;
|
||||
|
||||
|
|
|
@ -326,7 +326,7 @@ public:
|
|||
void clearSimulationOwner();
|
||||
void setSimulationOwner(const QUuid& id, uint8_t priority);
|
||||
void setSimulationOwner(const QByteArray& data);
|
||||
void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); }
|
||||
void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); }
|
||||
|
||||
void setActionDataDirty() { _actionDataChanged = true; }
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
void EntitySimulation::setEntityTree(EntityTreePointer tree) {
|
||||
if (_entityTree && _entityTree != tree) {
|
||||
_mortalEntities.clear();
|
||||
_nextExpiry = quint64(-1);
|
||||
_nextExpiry = std::numeric_limits<uint64_t>::max();
|
||||
_entitiesToUpdate.clear();
|
||||
_entitiesToSort.clear();
|
||||
_simpleKinematicEntities.clear();
|
||||
|
@ -30,7 +30,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) {
|
|||
|
||||
void EntitySimulation::updateEntities() {
|
||||
QMutexLocker lock(&_mutex);
|
||||
quint64 now = usecTimestampNow();
|
||||
uint64_t now = usecTimestampNow();
|
||||
|
||||
// these methods may accumulate entries in _entitiesToBeDeleted
|
||||
expireMortalEntities(now);
|
||||
|
@ -40,18 +40,14 @@ void EntitySimulation::updateEntities() {
|
|||
sortEntitiesThatMoved();
|
||||
}
|
||||
|
||||
void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
|
||||
void EntitySimulation::takeDeadEntities(SetOfEntities& entitiesToDelete) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
for (auto entity : _entitiesToDelete) {
|
||||
// push this entity onto the external list
|
||||
entitiesToDelete.push_back(entity);
|
||||
}
|
||||
_entitiesToDelete.clear();
|
||||
entitiesToDelete.swap(_deadEntities);
|
||||
_deadEntities.clear();
|
||||
}
|
||||
|
||||
void EntitySimulation::removeEntityInternal(EntityItemPointer entity) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
// remove from all internal lists except _entitiesToDelete
|
||||
// remove from all internal lists except _deadEntities
|
||||
_mortalEntities.remove(entity);
|
||||
_entitiesToUpdate.remove(entity);
|
||||
_entitiesToSort.remove(entity);
|
||||
|
@ -67,42 +63,23 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
|
|||
QMutexLocker lock(&_mutex);
|
||||
entity->clearActions(getThisPointer());
|
||||
removeEntityInternal(entity);
|
||||
_entitiesToDelete.insert(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void EntitySimulation::addEntityInternal(EntityItemPointer entity) {
|
||||
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
entity->setLastSimulated(usecTimestampNow());
|
||||
}
|
||||
}
|
||||
|
||||
void EntitySimulation::changeEntityInternal(EntityItemPointer entity) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
|
||||
int numKinematicEntities = _simpleKinematicEntities.size();
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
if (numKinematicEntities != _simpleKinematicEntities.size()) {
|
||||
entity->setLastSimulated(usecTimestampNow());
|
||||
if (entity->getElement()) {
|
||||
_deadEntities.insert(entity);
|
||||
}
|
||||
} else {
|
||||
_simpleKinematicEntities.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// protected
|
||||
void EntitySimulation::expireMortalEntities(const quint64& now) {
|
||||
void EntitySimulation::expireMortalEntities(uint64_t now) {
|
||||
if (now > _nextExpiry) {
|
||||
PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size());
|
||||
// only search for expired entities if we expect to find one
|
||||
_nextExpiry = quint64(-1);
|
||||
_nextExpiry = std::numeric_limits<uint64_t>::max();
|
||||
QMutexLocker lock(&_mutex);
|
||||
SetOfEntities::iterator itemItr = _mortalEntities.begin();
|
||||
while (itemItr != _mortalEntities.end()) {
|
||||
EntityItemPointer entity = *itemItr;
|
||||
quint64 expiry = entity->getExpiry();
|
||||
uint64_t expiry = entity->getExpiry();
|
||||
if (expiry < now) {
|
||||
itemItr = _mortalEntities.erase(itemItr);
|
||||
entity->die();
|
||||
|
@ -122,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
|
|||
}
|
||||
|
||||
// protected
|
||||
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
|
||||
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(uint64_t now) {
|
||||
PerformanceTimer perfTimer("updatingEntities");
|
||||
QMutexLocker lock(&_mutex);
|
||||
SetOfEntities::iterator itemItr = _entitiesToUpdate.begin();
|
||||
|
@ -176,7 +153,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) {
|
|||
entity->deserializeActions();
|
||||
if (entity->isMortal()) {
|
||||
_mortalEntities.insert(entity);
|
||||
quint64 expiry = entity->getExpiry();
|
||||
uint64_t expiry = entity->getExpiry();
|
||||
if (expiry < _nextExpiry) {
|
||||
_nextExpiry = expiry;
|
||||
}
|
||||
|
@ -207,7 +184,6 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
|
|||
// Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes
|
||||
// it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence
|
||||
// we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag.
|
||||
bool wasRemoved = false;
|
||||
uint32_t dirtyFlags = entity->getDirtyFlags();
|
||||
if (dirtyFlags & Simulation::DIRTY_POSITION) {
|
||||
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
|
||||
|
@ -217,50 +193,45 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
|
|||
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
|
||||
entity->die();
|
||||
prepareEntityForDelete(entity);
|
||||
wasRemoved = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!wasRemoved) {
|
||||
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
|
||||
if (entity->isMortal()) {
|
||||
_mortalEntities.insert(entity);
|
||||
quint64 expiry = entity->getExpiry();
|
||||
if (expiry < _nextExpiry) {
|
||||
_nextExpiry = expiry;
|
||||
}
|
||||
} else {
|
||||
_mortalEntities.remove(entity);
|
||||
|
||||
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
|
||||
if (entity->isMortal()) {
|
||||
_mortalEntities.insert(entity);
|
||||
uint64_t expiry = entity->getExpiry();
|
||||
if (expiry < _nextExpiry) {
|
||||
_nextExpiry = expiry;
|
||||
}
|
||||
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
|
||||
}
|
||||
if (entity->needsToCallUpdate()) {
|
||||
_entitiesToUpdate.insert(entity);
|
||||
} else {
|
||||
_entitiesToUpdate.remove(entity);
|
||||
_mortalEntities.remove(entity);
|
||||
}
|
||||
changeEntityInternal(entity);
|
||||
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
|
||||
}
|
||||
if (entity->needsToCallUpdate()) {
|
||||
_entitiesToUpdate.insert(entity);
|
||||
} else {
|
||||
_entitiesToUpdate.remove(entity);
|
||||
}
|
||||
changeEntityInternal(entity);
|
||||
}
|
||||
|
||||
void EntitySimulation::clearEntities() {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_mortalEntities.clear();
|
||||
_nextExpiry = quint64(-1);
|
||||
_nextExpiry = std::numeric_limits<uint64_t>::max();
|
||||
_entitiesToUpdate.clear();
|
||||
_entitiesToSort.clear();
|
||||
_simpleKinematicEntities.clear();
|
||||
|
||||
clearEntitiesInternal();
|
||||
|
||||
for (auto entity : _allEntities) {
|
||||
entity->setSimulated(false);
|
||||
entity->die();
|
||||
}
|
||||
_allEntities.clear();
|
||||
_entitiesToDelete.clear();
|
||||
_deadEntities.clear();
|
||||
}
|
||||
|
||||
void EntitySimulation::moveSimpleKinematics(const quint64& now) {
|
||||
void EntitySimulation::moveSimpleKinematics(uint64_t now) {
|
||||
PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size());
|
||||
SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin();
|
||||
while (itemItr != _simpleKinematicEntities.end()) {
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_EntitySimulation_h
|
||||
#define hifi_EntitySimulation_h
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
@ -43,9 +45,8 @@ const int DIRTY_SIMULATION_FLAGS =
|
|||
Simulation::DIRTY_SIMULATOR_ID;
|
||||
|
||||
class EntitySimulation : public QObject, public std::enable_shared_from_this<EntitySimulation> {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { }
|
||||
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(std::numeric_limits<uint64_t>::max()) { }
|
||||
virtual ~EntitySimulation() { setEntityTree(NULL); }
|
||||
|
||||
inline EntitySimulationPointer getThisPointer() const {
|
||||
|
@ -57,8 +58,6 @@ public:
|
|||
|
||||
void updateEntities();
|
||||
|
||||
// friend class EntityTree;
|
||||
|
||||
virtual void addDynamic(EntityDynamicPointer dynamic);
|
||||
virtual void removeDynamic(const QUuid dynamicID);
|
||||
virtual void removeDynamics(QList<QUuid> dynamicIDsToRemove);
|
||||
|
@ -74,29 +73,26 @@ public:
|
|||
|
||||
void clearEntities();
|
||||
|
||||
void moveSimpleKinematics(const quint64& now);
|
||||
void moveSimpleKinematics(uint64_t now);
|
||||
|
||||
EntityTreePointer getEntityTree() { return _entityTree; }
|
||||
|
||||
virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete);
|
||||
virtual void takeDeadEntities(SetOfEntities& entitiesToDelete);
|
||||
|
||||
/// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others.
|
||||
virtual void prepareEntityForDelete(EntityItemPointer entity);
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
protected:
|
||||
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
|
||||
// calls them in the right places.
|
||||
virtual void updateEntitiesInternal(const quint64& now) = 0;
|
||||
virtual void addEntityInternal(EntityItemPointer entity);
|
||||
virtual void removeEntityInternal(EntityItemPointer entity) = 0;
|
||||
virtual void changeEntityInternal(EntityItemPointer entity);
|
||||
virtual void updateEntitiesInternal(uint64_t now) = 0;
|
||||
virtual void addEntityInternal(EntityItemPointer entity) = 0;
|
||||
virtual void removeEntityInternal(EntityItemPointer entity);
|
||||
virtual void changeEntityInternal(EntityItemPointer entity) = 0;
|
||||
virtual void clearEntitiesInternal() = 0;
|
||||
|
||||
void expireMortalEntities(const quint64& now);
|
||||
void callUpdateOnEntitiesThatNeedIt(const quint64& now);
|
||||
void expireMortalEntities(uint64_t now);
|
||||
void callUpdateOnEntitiesThatNeedIt(uint64_t now);
|
||||
virtual void sortEntitiesThatMoved();
|
||||
|
||||
QMutex _mutex{ QMutex::Recursive };
|
||||
|
@ -108,7 +104,7 @@ protected:
|
|||
QMutex _dynamicsMutex { QMutex::Recursive };
|
||||
|
||||
protected:
|
||||
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)
|
||||
SetOfEntities _deadEntities;
|
||||
|
||||
private:
|
||||
void moveSimpleKinematics();
|
||||
|
@ -120,11 +116,10 @@ private:
|
|||
// An entity may be in more than one list.
|
||||
SetOfEntities _allEntities; // tracks all entities added the simulation
|
||||
SetOfEntities _mortalEntities; // entities that have an expiry
|
||||
quint64 _nextExpiry;
|
||||
uint64_t _nextExpiry;
|
||||
|
||||
|
||||
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_EntitySimulation_h
|
||||
|
|
|
@ -94,7 +94,6 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) {
|
|||
void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
|
||||
emit clearingEntities();
|
||||
|
||||
// this would be a good place to clean up our entities...
|
||||
if (_simulation) {
|
||||
_simulation->clearEntities();
|
||||
}
|
||||
|
@ -260,7 +259,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check to see if we need to simulate this entity..
|
||||
if (_simulation) {
|
||||
_simulation->addEntity(entity);
|
||||
|
@ -453,12 +452,13 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
|
|||
|
||||
uint32_t newFlags = entity->getDirtyFlags() & ~preFlags;
|
||||
if (newFlags) {
|
||||
if (_simulation) {
|
||||
if (entity->isSimulated()) {
|
||||
assert((bool)_simulation);
|
||||
if (newFlags & DIRTY_SIMULATION_FLAGS) {
|
||||
_simulation->changeEntity(entity);
|
||||
}
|
||||
} else {
|
||||
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
|
||||
// normally the _simulation clears ALL dirtyFlags, but when not possible we do it explicitly
|
||||
entity->clearDirtyFlags();
|
||||
}
|
||||
}
|
||||
|
@ -469,7 +469,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
|
|||
if (entityScriptBefore != entityScriptAfter || reload) {
|
||||
emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG).
|
||||
if (!entity->getElement()) {
|
||||
|
@ -551,8 +551,6 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) {
|
|||
assert(simulation->getEntityTree().get() == this);
|
||||
}
|
||||
if (_simulation && _simulation != simulation) {
|
||||
// It's important to clearEntities() on the simulation since taht will update each
|
||||
// EntityItem::_simulationState correctly so as to not confuse the next _simulation.
|
||||
_simulation->clearEntities();
|
||||
}
|
||||
_simulation = simulation;
|
||||
|
@ -650,7 +648,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
|
|||
emit deletingEntityPointer(existingEntity.get());
|
||||
}
|
||||
|
||||
if (theOperator.getEntities().size() > 0) {
|
||||
if (!theOperator.getEntities().empty()) {
|
||||
recurseTreeWithOperator(&theOperator);
|
||||
processRemovedEntities(theOperator);
|
||||
_isDirty = true;
|
||||
|
@ -692,7 +690,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
|
|||
trackDeletedEntity(theEntity->getEntityItemID());
|
||||
}
|
||||
|
||||
if (_simulation) {
|
||||
if (theEntity->isSimulated()) {
|
||||
_simulation->prepareEntityForDelete(theEntity);
|
||||
}
|
||||
}
|
||||
|
@ -1688,7 +1686,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
|
|||
}
|
||||
|
||||
void EntityTree::entityChanged(EntityItemPointer entity) {
|
||||
if (_simulation) {
|
||||
if (entity->isSimulated()) {
|
||||
_simulation->changeEntity(entity);
|
||||
}
|
||||
}
|
||||
|
@ -1802,13 +1800,13 @@ void EntityTree::update(bool simulate) {
|
|||
_simulation->updateEntities();
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "Deletes");
|
||||
VectorOfEntities pendingDeletes;
|
||||
_simulation->takeEntitiesToDelete(pendingDeletes);
|
||||
if (pendingDeletes.size() > 0) {
|
||||
SetOfEntities deadEntities;
|
||||
_simulation->takeDeadEntities(deadEntities);
|
||||
if (!deadEntities.empty()) {
|
||||
// translate into list of ID's
|
||||
QSet<EntityItemID> idsToDelete;
|
||||
|
||||
for (auto entity : pendingDeletes) {
|
||||
for (auto entity : deadEntities) {
|
||||
idsToDelete.insert(entity->getEntityItemID());
|
||||
}
|
||||
|
||||
|
|
|
@ -267,8 +267,11 @@ void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) {
|
|||
|
||||
void MaterialEntityItem::removeMaterial() {
|
||||
graphics::MaterialPointer material = getMaterial();
|
||||
if (!material) {
|
||||
return;
|
||||
}
|
||||
QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID();
|
||||
if (!material || parentID.isNull()) {
|
||||
if (parentID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -336,4 +339,4 @@ void MaterialEntityItem::update(const quint64& now) {
|
|||
}
|
||||
|
||||
EntityItem::update(now);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include "EntityItem.h"
|
||||
#include "EntitiesLogging.h"
|
||||
|
||||
const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND;
|
||||
const uint64_t MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND;
|
||||
|
||||
void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
|
@ -33,7 +33,7 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
|
|||
if (entity->getDynamic() && entity->hasLocalVelocity()) {
|
||||
// it is still moving dynamically --> add to orphaned list
|
||||
_entitiesThatNeedSimulationOwner.insert(entity);
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < _nextOwnerlessExpiry) {
|
||||
_nextOwnerlessExpiry = expiry;
|
||||
}
|
||||
|
@ -50,15 +50,15 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
|
|||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
||||
void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) {
|
||||
if (now > _nextOwnerlessExpiry) {
|
||||
// search for ownerless objects that have expired
|
||||
QMutexLocker lock(&_mutex);
|
||||
_nextOwnerlessExpiry = -1;
|
||||
_nextOwnerlessExpiry = std::numeric_limits<uint64_t>::max();
|
||||
SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin();
|
||||
while (itemItr != _entitiesThatNeedSimulationOwner.end()) {
|
||||
EntityItemPointer entity = *itemItr;
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < now) {
|
||||
// no simulators have volunteered ownership --> remove from list
|
||||
itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr);
|
||||
|
@ -85,14 +85,18 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
|||
}
|
||||
|
||||
void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::addEntityInternal(entity);
|
||||
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
entity->setLastSimulated(usecTimestampNow());
|
||||
}
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulationOwner.insert(entity);
|
||||
} else if (entity->getDynamic() && entity->hasLocalVelocity()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesThatNeedSimulationOwner.insert(entity);
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < _nextOwnerlessExpiry) {
|
||||
_nextOwnerlessExpiry = expiry;
|
||||
}
|
||||
|
@ -101,19 +105,29 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
|
|||
|
||||
void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::removeEntityInternal(entity);
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulationOwner.remove(entity);
|
||||
_entitiesThatNeedSimulationOwner.remove(entity);
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::changeEntityInternal(entity);
|
||||
{
|
||||
QMutexLocker lock(&_mutex);
|
||||
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
|
||||
int numKinematicEntities = _simpleKinematicEntities.size();
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
if (numKinematicEntities != _simpleKinematicEntities.size()) {
|
||||
entity->setLastSimulated(usecTimestampNow());
|
||||
}
|
||||
} else {
|
||||
_simpleKinematicEntities.remove(entity);
|
||||
}
|
||||
}
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulationOwner.remove(entity);
|
||||
if (entity->getDynamic() && entity->hasLocalVelocity()) {
|
||||
_entitiesThatNeedSimulationOwner.insert(entity);
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < _nextOwnerlessExpiry) {
|
||||
_nextOwnerlessExpiry = expiry;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
void clearOwnership(const QUuid& ownerID);
|
||||
|
||||
protected:
|
||||
virtual void updateEntitiesInternal(const quint64& now) override;
|
||||
virtual void updateEntitiesInternal(uint64_t now) override;
|
||||
virtual void addEntityInternal(EntityItemPointer entity) override;
|
||||
virtual void removeEntityInternal(EntityItemPointer entity) override;
|
||||
virtual void changeEntityInternal(EntityItemPointer entity) override;
|
||||
|
@ -38,7 +38,7 @@ protected:
|
|||
|
||||
SetOfEntities _entitiesWithSimulationOwner;
|
||||
SetOfEntities _entitiesThatNeedSimulationOwner;
|
||||
quint64 _nextOwnerlessExpiry { 0 };
|
||||
uint64_t _nextOwnerlessExpiry { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_SimpleEntitySimulation_h
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
const quint8 PENDING_STATE_NOTHING = 0;
|
||||
const quint8 PENDING_STATE_TAKE = 1;
|
||||
const quint8 PENDING_STATE_RELEASE = 2;
|
||||
const uint8_t PENDING_STATE_NOTHING = 0;
|
||||
const uint8_t PENDING_STATE_TAKE = 1;
|
||||
const uint8_t PENDING_STATE_RELEASE = 2;
|
||||
|
||||
// static
|
||||
const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1;
|
||||
|
@ -33,7 +33,7 @@ SimulationOwner::SimulationOwner() :
|
|||
{
|
||||
}
|
||||
|
||||
SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) :
|
||||
SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) :
|
||||
_id(id),
|
||||
_expiry(0),
|
||||
_pendingBidTimestamp(0),
|
||||
|
@ -67,11 +67,11 @@ void SimulationOwner::clear() {
|
|||
_pendingState = PENDING_STATE_NOTHING;
|
||||
}
|
||||
|
||||
void SimulationOwner::setPriority(quint8 priority) {
|
||||
void SimulationOwner::setPriority(uint8_t priority) {
|
||||
_priority = priority;
|
||||
}
|
||||
|
||||
void SimulationOwner::promotePriority(quint8 priority) {
|
||||
void SimulationOwner::promotePriority(uint8_t priority) {
|
||||
if (priority > _priority) {
|
||||
_priority = priority;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ bool SimulationOwner::setID(const QUuid& id) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool SimulationOwner::set(const QUuid& id, quint8 priority) {
|
||||
bool SimulationOwner::set(const QUuid& id, uint8_t priority) {
|
||||
uint8_t oldPriority = _priority;
|
||||
setPriority(priority);
|
||||
return setID(id) || oldPriority != _priority;
|
||||
|
@ -101,22 +101,22 @@ bool SimulationOwner::set(const SimulationOwner& owner) {
|
|||
return setID(owner._id) || oldPriority != _priority;
|
||||
}
|
||||
|
||||
void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) {
|
||||
void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) {
|
||||
_pendingBidPriority = priority;
|
||||
_pendingBidTimestamp = timestamp;
|
||||
_pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
|
||||
}
|
||||
|
||||
void SimulationOwner::updateExpiry() {
|
||||
const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5;
|
||||
const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC;
|
||||
_expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY;
|
||||
}
|
||||
|
||||
bool SimulationOwner::pendingRelease(const quint64& timestamp) {
|
||||
bool SimulationOwner::pendingRelease(uint64_t timestamp) {
|
||||
return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp;
|
||||
}
|
||||
|
||||
bool SimulationOwner::pendingTake(const quint64& timestamp) {
|
||||
bool SimulationOwner::pendingTake(uint64_t timestamp) {
|
||||
return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp;
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ void SimulationOwner::test() {
|
|||
|
||||
{ // test set constructor
|
||||
QUuid id = QUuid::createUuid();
|
||||
quint8 priority = 128;
|
||||
uint8_t priority = 128;
|
||||
SimulationOwner simOwner(id, priority);
|
||||
if (simOwner.isNull()) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl;
|
||||
|
@ -164,7 +164,7 @@ void SimulationOwner::test() {
|
|||
|
||||
{ // test set()
|
||||
QUuid id = QUuid::createUuid();
|
||||
quint8 priority = 1;
|
||||
uint8_t priority = 1;
|
||||
SimulationOwner simOwner;
|
||||
simOwner.set(id, priority);
|
||||
if (simOwner.isNull()) {
|
||||
|
|
|
@ -18,20 +18,88 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
|
||||
// Simulation observers will bid to simulate unowned active objects at the lowest possible priority
|
||||
// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it
|
||||
// to RECRUIT priority so that other volunteers don't accidentally take over.
|
||||
const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01;
|
||||
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
|
||||
// HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions
|
||||
// of the same world. When portions overlap only one participant is allowed to be the authority for any
|
||||
// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and
|
||||
// their duty is to send transform/velocity updates for the entity to the central entity-server.
|
||||
// The entity-server relays updates to other participants who apply them as "state synchronization"
|
||||
// to their own simulation.
|
||||
//
|
||||
// Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update:
|
||||
// {
|
||||
// "simulationOwner": { "ownerID" : sessionID, "priority" : priority },
|
||||
// transform/velocity properties
|
||||
// }
|
||||
//
|
||||
// The entity-server is the authority as to who owns what and may reject a bid.
|
||||
// The rules for handling a bid are as follows:
|
||||
//
|
||||
// (1) A bid may be refused for special ownership restrictions, but otherwise...
|
||||
//
|
||||
// (2) A bid at higher priority is accepted
|
||||
//
|
||||
// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec)
|
||||
// of the last ownership transition, otherwise it is accepted
|
||||
//
|
||||
// (4) The current owner is the only participant allowed to clear ownership (entity-server can override).
|
||||
//
|
||||
// (5) The current owner is the only participant allowed to adjust priority (entity-server can override).
|
||||
//
|
||||
// (6) If an owner does not update the transform or velocities of an owned entity within some period
|
||||
// (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle
|
||||
// the case when an owner drops off the network.
|
||||
//
|
||||
// The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules
|
||||
// for bidding are as follows:
|
||||
//
|
||||
// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the
|
||||
// simulation owner and priority as if they really did change but doesn't actually modify them
|
||||
// locally. Thus, if the bid packet is lost the participant will re-send after some period.
|
||||
// The participant only updates its knowledge of who owns what when it recieves an update from the
|
||||
// entity-server. An exception is when the participant creates a moving entity: it assumes it starts
|
||||
// off owning any moving entities it creates.
|
||||
//
|
||||
// (8) When an unowned entity becomes active in the physics simulation the participant will
|
||||
// start a timer and if the entity is still unowned after some period (0.5 seconds)
|
||||
// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER
|
||||
// priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to
|
||||
// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership
|
||||
// when multiple participants (with variable ping-times to the server) bid simultaneously for a
|
||||
// recently activated entity.
|
||||
//
|
||||
// (9) When a participant changes an entity's transform/velocity it will bid at priority = POKE (=127)
|
||||
//
|
||||
// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE.
|
||||
//
|
||||
// (11) When a participant grabs an entity it will bid at priority = GRAB (=128).
|
||||
//
|
||||
// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will
|
||||
// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger).
|
||||
//
|
||||
// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will
|
||||
// send an update to: clear their ownerhsip, set priority to zero, and set the object's
|
||||
// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership
|
||||
// has been cleared until it hears from the entity-server. This, if the packet is lost the
|
||||
// owner will re-send after some period.
|
||||
//
|
||||
// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it
|
||||
// immediately at priority = VOLUNTEER.
|
||||
//
|
||||
// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority
|
||||
// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it.
|
||||
//
|
||||
const uint8_t YIELD_SIMULATION_PRIORITY = 1;
|
||||
const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1;
|
||||
const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
|
||||
|
||||
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
|
||||
const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80;
|
||||
const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
|
||||
const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
|
||||
const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128;
|
||||
const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
|
||||
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
|
||||
|
||||
// PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar
|
||||
// which really just means: things that collide with it will be bid at a priority level one lower
|
||||
const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
|
||||
const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
|
||||
|
||||
|
||||
class SimulationOwner {
|
||||
|
@ -39,25 +107,25 @@ public:
|
|||
static const int NUM_BYTES_ENCODED;
|
||||
|
||||
SimulationOwner();
|
||||
SimulationOwner(const QUuid& id, quint8 priority);
|
||||
SimulationOwner(const QUuid& id, uint8_t priority);
|
||||
|
||||
const QUuid& getID() const { return _id; }
|
||||
const quint64& getExpiry() const { return _expiry; }
|
||||
quint8 getPriority() const { return _priority; }
|
||||
const uint64_t& getExpiry() const { return _expiry; }
|
||||
uint8_t getPriority() const { return _priority; }
|
||||
|
||||
QByteArray toByteArray() const;
|
||||
bool fromByteArray(const QByteArray& data);
|
||||
|
||||
void clear();
|
||||
|
||||
void setPriority(quint8 priority);
|
||||
void promotePriority(quint8 priority);
|
||||
void setPriority(uint8_t priority);
|
||||
void promotePriority(uint8_t priority);
|
||||
|
||||
// return true if id is changed
|
||||
bool setID(const QUuid& id);
|
||||
bool set(const QUuid& id, quint8 priority);
|
||||
bool set(const QUuid& id, uint8_t priority);
|
||||
bool set(const SimulationOwner& owner);
|
||||
void setPendingPriority(quint8 priority, const quint64& timestamp);
|
||||
void setPendingPriority(uint8_t priority, uint64_t timestamp);
|
||||
|
||||
bool isNull() const { return _id.isNull(); }
|
||||
bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); }
|
||||
|
@ -67,11 +135,11 @@ public:
|
|||
bool hasExpired() const { return usecTimestampNow() > _expiry; }
|
||||
|
||||
uint8_t getPendingPriority() const { return _pendingBidPriority; }
|
||||
bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE
|
||||
bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE
|
||||
bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE
|
||||
bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE
|
||||
void clearCurrentOwner();
|
||||
|
||||
bool operator>=(quint8 priority) const { return _priority >= priority; }
|
||||
bool operator>=(uint8_t priority) const { return _priority >= priority; }
|
||||
bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); }
|
||||
|
||||
bool operator!=(const SimulationOwner& other);
|
||||
|
@ -84,11 +152,11 @@ public:
|
|||
|
||||
private:
|
||||
QUuid _id; // owner
|
||||
quint64 _expiry; // time when ownership can transition at equal priority
|
||||
quint64 _pendingBidTimestamp; // time when pending bid was set
|
||||
quint8 _priority; // priority of current owner
|
||||
quint8 _pendingBidPriority; // priority at which we'd like to own it
|
||||
quint8 _pendingState; // NOTHING, TAKE, or RELEASE
|
||||
uint64_t _expiry; // time when ownership can transition at equal priority
|
||||
uint64_t _pendingBidTimestamp; // time when pending bid was set
|
||||
uint8_t _priority; // priority of current owner
|
||||
uint8_t _pendingBidPriority; // priority at which we'd like to own it
|
||||
uint8_t _pendingState; // NOTHING, TAKE, or RELEASE
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -77,6 +77,17 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const {
|
|||
}
|
||||
}
|
||||
|
||||
QUrl AddressManager::currentPublicAddress(bool domainOnly) const {
|
||||
// return an address that can be used by others to visit this client's current location. If
|
||||
// in a serverless domain (which can't be visited) return an empty URL.
|
||||
QUrl shareableAddress = currentShareableAddress(domainOnly);
|
||||
if (shareableAddress.scheme() != URL_SCHEME_HIFI) {
|
||||
return QUrl(); // file: urls aren't public
|
||||
}
|
||||
return shareableAddress;
|
||||
}
|
||||
|
||||
|
||||
QUrl AddressManager::currentFacingShareableAddress() const {
|
||||
auto hifiURL = currentShareableAddress();
|
||||
if (hifiURL.scheme() == URL_SCHEME_HIFI) {
|
||||
|
@ -86,6 +97,17 @@ QUrl AddressManager::currentFacingShareableAddress() const {
|
|||
return hifiURL;
|
||||
}
|
||||
|
||||
QUrl AddressManager::currentFacingPublicAddress() const {
|
||||
// return an address that can be used by others to visit this client's current location. If
|
||||
// in a serverless domain (which can't be visited) return an empty URL.
|
||||
QUrl shareableAddress = currentFacingShareableAddress();
|
||||
if (shareableAddress.scheme() != URL_SCHEME_HIFI) {
|
||||
return QUrl(); // file: urls aren't public
|
||||
}
|
||||
return shareableAddress;
|
||||
}
|
||||
|
||||
|
||||
void AddressManager::loadSettings(const QString& lookupString) {
|
||||
#if defined(USE_GLES) && defined(Q_OS_WIN)
|
||||
handleUrl(QUrl("hifi://127.0.0.0"), LookupTrigger::StartupFromSettings);
|
||||
|
@ -286,6 +308,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
// lookupUrl.scheme() == URL_SCHEME_HTTP ||
|
||||
// lookupUrl.scheme() == URL_SCHEME_HTTPS ||
|
||||
_previousLookup.clear();
|
||||
_shareablePlaceName.clear();
|
||||
QUrl domainURL = PathUtils::expandToLocalDataAbsolutePath(lookupUrl);
|
||||
setDomainInfo(domainURL, trigger);
|
||||
emit lookupResultsFinished();
|
||||
|
|
|
@ -150,7 +150,9 @@ public:
|
|||
QUrl currentAddress(bool domainOnly = false) const;
|
||||
QUrl currentFacingAddress() const;
|
||||
QUrl currentShareableAddress(bool domainOnly = false) const;
|
||||
QUrl currentPublicAddress(bool domainOnly = false) const;
|
||||
QUrl currentFacingShareableAddress() const;
|
||||
QUrl currentFacingPublicAddress() const;
|
||||
QString currentPath(bool withOrientation = true) const;
|
||||
QString currentFacingPath() const;
|
||||
|
||||
|
|
|
@ -192,17 +192,12 @@ bool OctreePersistThread::process() {
|
|||
QString lockFileName = _filename + ".lock";
|
||||
std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate);
|
||||
if (lockFile.is_open()) {
|
||||
qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName
|
||||
<< "-- Attempting to restore from previous backup file.";
|
||||
|
||||
// This is where we should attempt to find the most recent backup and restore from
|
||||
// that file as our persist file.
|
||||
restoreFromMostRecentBackup();
|
||||
qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName;
|
||||
|
||||
lockFile.close();
|
||||
qCDebug(octree) << "Loading Octree... lock file closed:" << lockFileName;
|
||||
qCDebug(octree) << "Removing lock file:" << lockFileName;
|
||||
remove(qPrintable(lockFileName));
|
||||
qCDebug(octree) << "Loading Octree... lock file removed:" << lockFileName;
|
||||
qCDebug(octree) << "Lock file removed:" << lockFileName;
|
||||
}
|
||||
|
||||
persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit()));
|
||||
|
|
|
@ -26,12 +26,7 @@
|
|||
|
||||
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
|
||||
#include "EntityTree.h"
|
||||
#endif
|
||||
|
||||
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
|
||||
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
|
||||
|
||||
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
|
||||
bool EntityMotionState::entityTreeIsLocked() const {
|
||||
EntityTreeElementPointer element = _entity->getElement();
|
||||
EntityTreePointer tree = element ? element->getTree() : nullptr;
|
||||
|
@ -46,11 +41,13 @@ bool entityTreeIsLocked() {
|
|||
}
|
||||
#endif
|
||||
|
||||
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
|
||||
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
|
||||
|
||||
|
||||
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
|
||||
ObjectMotionState(nullptr),
|
||||
_entityPtr(entity),
|
||||
_entity(entity.get()),
|
||||
_entity(entity),
|
||||
_serverPosition(0.0f),
|
||||
_serverRotation(),
|
||||
_serverVelocity(0.0f),
|
||||
|
@ -60,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
|||
_serverActionData(QByteArray()),
|
||||
_lastVelocity(0.0f),
|
||||
_measuredAcceleration(0.0f),
|
||||
_nextOwnershipBid(0),
|
||||
_nextBidExpiry(0),
|
||||
_measuredDeltaTime(0.0f),
|
||||
_lastMeasureStep(0),
|
||||
_lastStep(0),
|
||||
|
@ -68,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
|||
_accelerationNearlyGravityCount(0),
|
||||
_numInactiveUpdates(1)
|
||||
{
|
||||
// Why is _numInactiveUpdates initialied to 1?
|
||||
// Because: when an entity is first created by a LOCAL operatioin its local simulation ownership is assumed,
|
||||
// which causes it to be immediately placed on the 'owned' list, but in this case an "update" already just
|
||||
// went out for the object's creation and there is no need to send another. By initializing _numInactiveUpdates
|
||||
// to 1 here we trick remoteSimulationOutOfSync() to return "false" the first time through for this case.
|
||||
|
||||
_type = MOTIONSTATE_TYPE_ENTITY;
|
||||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
|
@ -76,77 +79,89 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
|||
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
|
||||
setShape(shape);
|
||||
|
||||
_outgoingPriority = _entity->getPendingOwnershipPriority();
|
||||
}
|
||||
|
||||
EntityMotionState::~EntityMotionState() {
|
||||
assert(_entity);
|
||||
_entity = nullptr;
|
||||
}
|
||||
|
||||
void EntityMotionState::updateServerPhysicsVariables() {
|
||||
assert(entityTreeIsLocked());
|
||||
if (isLocallyOwned()) {
|
||||
// don't slam these values if we are the simulation owner
|
||||
return;
|
||||
_bidPriority = _entity->getPendingOwnershipPriority();
|
||||
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
|
||||
// client-only entities are always thus, so we cache this fact in _ownershipState
|
||||
_ownershipState = EntityMotionState::OwnershipState::Unownable;
|
||||
}
|
||||
|
||||
Transform localTransform;
|
||||
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
|
||||
_serverVariablesSet = true;
|
||||
_serverPosition = localTransform.getTranslation();
|
||||
_serverRotation = localTransform.getRotation();
|
||||
_serverAcceleration = _entity->getAcceleration();
|
||||
_serverActionData = _entity->getDynamicData();
|
||||
|
||||
}
|
||||
|
||||
EntityMotionState::~EntityMotionState() {
|
||||
if (_entity) {
|
||||
assert(_entity->getPhysicsInfo() == this);
|
||||
_entity->setPhysicsInfo(nullptr);
|
||||
_entity.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityMotionState::updateServerPhysicsVariables() {
|
||||
if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) {
|
||||
// only slam these values if we are NOT the simulation owner
|
||||
Transform localTransform;
|
||||
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
|
||||
_serverPosition = localTransform.getTranslation();
|
||||
_serverRotation = localTransform.getRotation();
|
||||
_serverAcceleration = _entity->getAcceleration();
|
||||
_serverActionData = _entity->getDynamicData();
|
||||
_lastStep = ObjectMotionState::getWorldSimulationStep();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityMotionState::handleDeactivation() {
|
||||
if (_serverVariablesSet) {
|
||||
// copy _server data to entity
|
||||
Transform localTransform = _entity->getLocalTransform();
|
||||
localTransform.setTranslation(_serverPosition);
|
||||
localTransform.setRotation(_serverRotation);
|
||||
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
|
||||
// and also to RigidBody
|
||||
btTransform worldTrans;
|
||||
worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition()));
|
||||
worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation()));
|
||||
_body->setWorldTransform(worldTrans);
|
||||
// no need to update velocities... should already be zero
|
||||
}
|
||||
// copy _server data to entity
|
||||
Transform localTransform = _entity->getLocalTransform();
|
||||
localTransform.setTranslation(_serverPosition);
|
||||
localTransform.setRotation(_serverRotation);
|
||||
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
|
||||
// and also to RigidBody
|
||||
btTransform worldTrans;
|
||||
worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition()));
|
||||
worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation()));
|
||||
_body->setWorldTransform(worldTrans);
|
||||
// no need to update velocities... should already be zero
|
||||
}
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
||||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
updateServerPhysicsVariables();
|
||||
ObjectMotionState::handleEasyChanges(flags);
|
||||
|
||||
if (flags & Simulation::DIRTY_SIMULATOR_ID) {
|
||||
if (_entity->getSimulatorID().isNull()) {
|
||||
// simulation ownership has been removed by an external simulator
|
||||
// simulation ownership has been removed
|
||||
if (glm::length2(_entity->getWorldVelocity()) == 0.0f) {
|
||||
// this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority
|
||||
// this object is coming to rest --> clear the ACTIVATION flag and _bidPriority
|
||||
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_body->setActivationState(WANTS_DEACTIVATION);
|
||||
_outgoingPriority = 0;
|
||||
_bidPriority = 0;
|
||||
const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet
|
||||
_body->setDeactivationTime(ACTIVATION_EXPIRY);
|
||||
} else {
|
||||
// disowned object is still moving --> start timer for ownership bid
|
||||
// TODO? put a delay in here proportional to distance from object?
|
||||
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
_nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
}
|
||||
_loopsWithoutOwner = 0;
|
||||
_numInactiveUpdates = 0;
|
||||
} else if (isLocallyOwned()) {
|
||||
// we just inherited ownership, make sure our desired priority matches what we have
|
||||
upgradeOutgoingPriority(_entity->getSimulationPriority());
|
||||
upgradeBidPriority(_entity->getSimulationPriority());
|
||||
} else {
|
||||
_outgoingPriority = 0;
|
||||
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
// the entity is owned by someone else, so we clear _bidPriority here
|
||||
// but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation
|
||||
// in which case we may try to bid again
|
||||
_bidPriority = 0;
|
||||
_nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
_numInactiveUpdates = 0;
|
||||
}
|
||||
}
|
||||
|
@ -155,10 +170,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
// (1) we own it but may need to change the priority OR...
|
||||
// (2) we don't own it but should bid (because a local script has been changing physics properties)
|
||||
uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
|
||||
upgradeOutgoingPriority(newPriority);
|
||||
upgradeBidPriority(newPriority);
|
||||
|
||||
// reset bid expiry so that we bid ASAP
|
||||
_nextOwnershipBid = 0;
|
||||
_nextBidExpiry = 0;
|
||||
}
|
||||
if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
|
||||
if (_body->isKinematicObject()) {
|
||||
|
@ -175,7 +190,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
|
||||
// virtual
|
||||
bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
|
||||
assert(_entity);
|
||||
updateServerPhysicsVariables();
|
||||
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||
}
|
||||
|
@ -253,7 +267,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
|
|||
// This callback is invoked by the physics simulation at the end of each simulation step...
|
||||
// iff the corresponding RigidBody is DYNAMIC and ACTIVE.
|
||||
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
measureBodyAcceleration();
|
||||
|
||||
|
@ -285,21 +298,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
|||
|
||||
if (_entity->getSimulatorID().isNull()) {
|
||||
_loopsWithoutOwner++;
|
||||
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) {
|
||||
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) {
|
||||
upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID();
|
||||
qCDebug(physics) << " last edited:" << _entity->getLastEdited()
|
||||
<< formatUsecTime(now - _entity->getLastEdited()) << "ago";
|
||||
qCDebug(physics) << " last simulated:" << _entity->getLastSimulated()
|
||||
<< formatUsecTime(now - _entity->getLastSimulated()) << "ago";
|
||||
qCDebug(physics) << " last updated:" << _entity->getLastUpdated()
|
||||
<< formatUsecTime(now - _entity->getLastUpdated()) << "ago";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -323,19 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) {
|
|||
}
|
||||
}
|
||||
|
||||
bool EntityMotionState::isCandidateForOwnership() const {
|
||||
assert(_body);
|
||||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
return _outgoingPriority != 0
|
||||
|| isLocallyOwned()
|
||||
|| _entity->dynamicDataNeedsTransmit();
|
||||
}
|
||||
|
||||
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
||||
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
|
||||
// NOTE: we only get here if we think we own the simulation
|
||||
assert(_body);
|
||||
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
|
||||
|
||||
// Since we own the simulation: make sure _bidPriority is not less than current owned priority
|
||||
// because: an _bidPriority of zero indicates that we should drop ownership when we have it.
|
||||
upgradeBidPriority(_entity->getSimulationPriority());
|
||||
|
||||
bool parentTransformSuccess;
|
||||
Transform localToWorld = _entity->getParentTransform(parentTransformSuccess);
|
||||
|
@ -347,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
worldVelocityToLocal.setTranslation(glm::vec3(0.0f));
|
||||
}
|
||||
|
||||
// if we've never checked before, our _lastStep will be 0, and we need to initialize our state
|
||||
if (_lastStep == 0) {
|
||||
btTransform xform = _body->getWorldTransform();
|
||||
_serverVariablesSet = true;
|
||||
_serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin()));
|
||||
_serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation());
|
||||
_serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma());
|
||||
_serverAcceleration = Vectors::ZERO;
|
||||
_serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity()));
|
||||
_lastStep = simulationStep;
|
||||
_serverActionData = _entity->getDynamicData();
|
||||
_numInactiveUpdates = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
glm::vec3 wasPosition = _serverPosition;
|
||||
glm::quat wasRotation = _serverRotation;
|
||||
glm::vec3 wasAngularVelocity = _serverAngularVelocity;
|
||||
#endif
|
||||
|
||||
int numSteps = simulationStep - _lastStep;
|
||||
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
|
||||
|
@ -378,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
_entity->clearSimulationOwnership();
|
||||
return false;
|
||||
}
|
||||
// we resend the inactive update every INACTIVE_UPDATE_PERIOD
|
||||
// until it is removed from the outgoing updates
|
||||
// (which happens when we don't own the simulation and it isn't touching our simulation)
|
||||
// we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates
|
||||
// until it is removed from the owned list
|
||||
// (which happens when we no longer own the simulation)
|
||||
const float INACTIVE_UPDATE_PERIOD = 0.5f;
|
||||
return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates);
|
||||
}
|
||||
|
@ -411,14 +386,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
|
||||
if (_entity->dynamicDataNeedsTransmit()) {
|
||||
uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
|
||||
upgradeOutgoingPriority(priority);
|
||||
upgradeBidPriority(priority);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_entity->shouldSuppressLocationEdits()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Else we measure the error between current and extrapolated transform (according to expected behavior
|
||||
// of remote EntitySimulation) and return true if the error is significant.
|
||||
|
||||
|
@ -440,13 +411,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
const float MIN_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second
|
||||
const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec
|
||||
if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) {
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
|
||||
qCDebug(physics) << "wasPosition:" << wasPosition;
|
||||
qCDebug(physics) << "bullet position:" << position;
|
||||
qCDebug(physics) << "_serverPosition:" << _serverPosition;
|
||||
qCDebug(physics) << "dx2:" << dx2;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -466,22 +430,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation
|
||||
glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation());
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) {
|
||||
qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ....";
|
||||
|
||||
qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity;
|
||||
qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity;
|
||||
|
||||
qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
|
||||
qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity);
|
||||
|
||||
qCDebug(physics) << "wasRotation:" << wasRotation;
|
||||
qCDebug(physics) << "bullet actualRotation:" << actualRotation;
|
||||
qCDebug(physics) << "_serverRotation:" << _serverRotation;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT);
|
||||
}
|
||||
|
||||
|
@ -489,14 +437,10 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
|
|||
DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend");
|
||||
// NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called
|
||||
// after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL.
|
||||
assert(_entity);
|
||||
assert(_body);
|
||||
assert(entityTreeIsLocked());
|
||||
|
||||
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
|
||||
// don't send updates for someone else's avatarEntities
|
||||
return false;
|
||||
}
|
||||
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
|
||||
assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
|
||||
|
||||
if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) {
|
||||
return true;
|
||||
|
@ -506,44 +450,19 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!isLocallyOwned()) {
|
||||
// we don't own the simulation
|
||||
|
||||
// NOTE: we do not volunteer to own kinematic or static objects
|
||||
uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0;
|
||||
|
||||
bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND
|
||||
usecTimestampNow() > _nextOwnershipBid; // it is time to bid again
|
||||
if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) {
|
||||
// we are insufficiently interested so clear _outgoingPriority
|
||||
// and reset the bid expiry
|
||||
_outgoingPriority = 0;
|
||||
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
}
|
||||
return shouldBid;
|
||||
} else {
|
||||
// When we own the simulation: make sure _outgoingPriority is not less than current owned priority
|
||||
// because: an _outgoingPriority of zero indicates that we should drop ownership when we have it.
|
||||
upgradeOutgoingPriority(_entity->getSimulationPriority());
|
||||
}
|
||||
|
||||
return remoteSimulationOutOfSync(simulationStep);
|
||||
}
|
||||
|
||||
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
|
||||
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
|
||||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
|
||||
void EntityMotionState::updateSendVelocities() {
|
||||
if (!_body->isActive()) {
|
||||
// make sure all derivatives are zero
|
||||
zeroCleanObjectVelocities();
|
||||
_numInactiveUpdates++;
|
||||
clearObjectVelocities();
|
||||
_numInactiveUpdates = 1;
|
||||
} else {
|
||||
glm::vec3 gravity = _entity->getGravity();
|
||||
|
||||
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let
|
||||
// the entity server's estimates include gravity.
|
||||
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps
|
||||
// let the entity server's estimates include gravity.
|
||||
const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4;
|
||||
if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) {
|
||||
_entity->setAcceleration(gravity);
|
||||
|
@ -564,13 +483,82 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
if (movingSlowly) {
|
||||
// velocities might not be zero, but we'll fake them as such, which will hopefully help convince
|
||||
// other simulating observers to deactivate their own copies
|
||||
zeroCleanObjectVelocities();
|
||||
clearObjectVelocities();
|
||||
}
|
||||
}
|
||||
_numInactiveUpdates = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// remember properties for local server prediction
|
||||
void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) {
|
||||
DETAILED_PROFILE_RANGE(simulation_physics, "Bid");
|
||||
assert(entityTreeIsLocked());
|
||||
|
||||
updateSendVelocities();
|
||||
|
||||
EntityItemProperties properties;
|
||||
Transform localTransform;
|
||||
glm::vec3 linearVelocity;
|
||||
glm::vec3 angularVelocity;
|
||||
_entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity);
|
||||
properties.setPosition(localTransform.getTranslation());
|
||||
properties.setRotation(localTransform.getRotation());
|
||||
properties.setVelocity(linearVelocity);
|
||||
properties.setAcceleration(_entity->getAcceleration());
|
||||
properties.setAngularVelocity(angularVelocity);
|
||||
if (_entity->dynamicDataNeedsTransmit()) {
|
||||
_entity->setDynamicDataNeedsTransmit(false);
|
||||
properties.setActionData(_entity->getDynamicData());
|
||||
}
|
||||
|
||||
if (_entity->updateQueryAACube()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
properties.setQueryAACube(_entity->getQueryAACube());
|
||||
}
|
||||
|
||||
// set the LastEdited of the properties but NOT the entity itself
|
||||
quint64 now = usecTimestampNow();
|
||||
properties.setLastEdited(now);
|
||||
|
||||
// we don't own the simulation for this entity yet, but we're sending a bid for it
|
||||
uint8_t bidPriority = glm::max<uint8_t>(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY);
|
||||
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
|
||||
// copy _bidPriority into pendingPriority...
|
||||
_entity->setPendingOwnershipPriority(_bidPriority, now);
|
||||
// don't forget to remember that we have made a bid
|
||||
_entity->rememberHasSimulationOwnershipBid();
|
||||
|
||||
EntityTreeElementPointer element = _entity->getElement();
|
||||
EntityTreePointer tree = element ? element->getTree() : nullptr;
|
||||
|
||||
properties.setClientOnly(_entity->getClientOnly());
|
||||
properties.setOwningAvatarID(_entity->getOwningAvatarID());
|
||||
|
||||
EntityItemID id(_entity->getID());
|
||||
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
|
||||
_entity->setLastBroadcast(now); // for debug/physics status icons
|
||||
|
||||
// NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent
|
||||
// then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary.
|
||||
|
||||
_lastStep = step;
|
||||
_nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
|
||||
// finally: clear _bidPriority
|
||||
// which will may get promoted before next bid
|
||||
// or maybe we'll win simulation ownership
|
||||
_bidPriority = 0;
|
||||
}
|
||||
|
||||
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
|
||||
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
|
||||
assert(entityTreeIsLocked());
|
||||
assert(isLocallyOwned());
|
||||
|
||||
updateSendVelocities();
|
||||
|
||||
// remember _serverFoo data for local prediction of server state
|
||||
Transform localTransform;
|
||||
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
|
||||
_serverPosition = localTransform.getTranslation();
|
||||
|
@ -579,11 +567,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
_serverActionData = _entity->getDynamicData();
|
||||
|
||||
EntityItemProperties properties;
|
||||
|
||||
// explicitly set the properties that changed so that they will be packed
|
||||
properties.setPosition(_entity->getLocalPosition());
|
||||
properties.setRotation(_entity->getLocalOrientation());
|
||||
|
||||
properties.setVelocity(_serverVelocity);
|
||||
properties.setAcceleration(_serverAcceleration);
|
||||
properties.setAngularVelocity(_serverAngularVelocity);
|
||||
|
@ -592,61 +577,35 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
properties.setActionData(_serverActionData);
|
||||
}
|
||||
|
||||
if (properties.transformChanged()) {
|
||||
if (_entity->updateQueryAACube()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
properties.setQueryAACube(_entity->getQueryAACube());
|
||||
}
|
||||
if (_entity->updateQueryAACube()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
properties.setQueryAACube(_entity->getQueryAACube());
|
||||
}
|
||||
|
||||
// set the LastEdited of the properties but NOT the entity itself
|
||||
quint64 now = usecTimestampNow();
|
||||
properties.setLastEdited(now);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
quint64 lastSimulated = _entity->getLastSimulated();
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()";
|
||||
qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
|
||||
<< "---------------------------------------------";
|
||||
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
|
||||
#endif //def WANT_DEBUG
|
||||
|
||||
if (_numInactiveUpdates > 0) {
|
||||
// we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID
|
||||
// the entity is stopped and inactive so we tell the server we're clearing simulatorID
|
||||
// but we remember we do still own it... and rely on the server to tell us we don't
|
||||
properties.clearSimulationOwner();
|
||||
_outgoingPriority = 0;
|
||||
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
|
||||
} else if (!isLocallyOwned()) {
|
||||
// we don't own the simulation for this entity yet, but we're sending a bid for it
|
||||
quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY);
|
||||
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
|
||||
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
// copy _outgoingPriority into pendingPriority...
|
||||
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
|
||||
// don't forget to remember that we have made a bid
|
||||
_entity->rememberHasSimulationOwnershipBid();
|
||||
// ...then reset _outgoingPriority
|
||||
_outgoingPriority = 0;
|
||||
// _outgoingPrioriuty will be re-computed before next bid,
|
||||
// or will be set to agree with ownership priority should we win the bid
|
||||
} else if (_outgoingPriority != _entity->getSimulationPriority()) {
|
||||
// we own the simulation but our desired priority has changed
|
||||
if (_outgoingPriority == 0) {
|
||||
_bidPriority = 0;
|
||||
_entity->setPendingOwnershipPriority(_bidPriority, now);
|
||||
} else if (_bidPriority != _entity->getSimulationPriority()) {
|
||||
// our desired priority has changed
|
||||
if (_bidPriority == 0) {
|
||||
// we should release ownership
|
||||
properties.clearSimulationOwner();
|
||||
} else {
|
||||
// we just need to change the priority
|
||||
properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority);
|
||||
properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority);
|
||||
}
|
||||
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
|
||||
_entity->setPendingOwnershipPriority(_bidPriority, now);
|
||||
}
|
||||
|
||||
EntityItemID id(_entity->getID());
|
||||
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
|
||||
#endif
|
||||
|
||||
EntityTreeElementPointer element = _entity->getElement();
|
||||
EntityTreePointer tree = element ? element->getTree() : nullptr;
|
||||
|
@ -655,7 +614,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
properties.setOwningAvatarID(_entity->getOwningAvatarID());
|
||||
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
|
||||
_entity->setLastBroadcast(now);
|
||||
_entity->setLastBroadcast(now); // for debug/physics status icons
|
||||
|
||||
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
|
||||
// if they've changed.
|
||||
|
@ -666,13 +625,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
EntityItemProperties newQueryCubeProperties;
|
||||
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
|
||||
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
|
||||
|
||||
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
|
||||
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
|
||||
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree,
|
||||
descendant->getID(), newQueryCubeProperties);
|
||||
entityDescendant->setLastBroadcast(now);
|
||||
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -723,6 +681,10 @@ uint8_t EntityMotionState::getSimulationPriority() const {
|
|||
return _entity->getSimulationPriority();
|
||||
}
|
||||
|
||||
void EntityMotionState::slaveBidPriority() {
|
||||
upgradeBidPriority(_entity->getSimulationPriority());
|
||||
}
|
||||
|
||||
// virtual
|
||||
QUuid EntityMotionState::getSimulatorID() const {
|
||||
assert(entityTreeIsLocked());
|
||||
|
@ -731,7 +693,7 @@ QUuid EntityMotionState::getSimulatorID() const {
|
|||
|
||||
void EntityMotionState::bump(uint8_t priority) {
|
||||
assert(priority != 0);
|
||||
upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
|
||||
upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
|
||||
}
|
||||
|
||||
void EntityMotionState::resetMeasuredBodyAcceleration() {
|
||||
|
@ -755,13 +717,14 @@ void EntityMotionState::measureBodyAcceleration() {
|
|||
_lastMeasureStep = thisStep;
|
||||
_measuredDeltaTime = dt;
|
||||
|
||||
// Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt
|
||||
// Note: the integration equation for velocity uses damping (D): v1 = (v0 + a * dt) * (1 - D)^dt
|
||||
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
|
||||
glm::vec3 velocity = getBodyLinearVelocityGTSigma();
|
||||
|
||||
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
|
||||
_lastVelocity = velocity;
|
||||
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
|
||||
// we fall in here when _lastMeasureStep is old: the body has just become active
|
||||
_loopsWithoutOwner = 0;
|
||||
_lastStep = ObjectMotionState::getWorldSimulationStep();
|
||||
_numInactiveUpdates = 0;
|
||||
|
@ -805,24 +768,44 @@ QString EntityMotionState::getName() const {
|
|||
|
||||
// virtual
|
||||
void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
|
||||
assert(_entity);
|
||||
_entity->computeCollisionGroupAndFinalMask(group, mask);
|
||||
}
|
||||
|
||||
bool EntityMotionState::shouldSendBid() {
|
||||
if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) {
|
||||
return true;
|
||||
} else {
|
||||
// NOTE: this 'else' case has a side-effect: it clears _bidPriority
|
||||
// which may be updated next simulation step (via collision or script event)
|
||||
_bidPriority = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityMotionState::isLocallyOwned() const {
|
||||
return _entity->getSimulatorID() == Physics::getSessionUUID();
|
||||
}
|
||||
|
||||
bool EntityMotionState::shouldBeLocallyOwned() const {
|
||||
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
|
||||
bool EntityMotionState::isLocallyOwnedOrShouldBe() const {
|
||||
return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) ||
|
||||
_entity->getSimulatorID() == Physics::getSessionUUID();
|
||||
}
|
||||
|
||||
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) {
|
||||
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
|
||||
void EntityMotionState::initForBid() {
|
||||
assert(_ownershipState != EntityMotionState::OwnershipState::Unownable);
|
||||
_ownershipState = EntityMotionState::OwnershipState::PendingBid;
|
||||
}
|
||||
|
||||
void EntityMotionState::zeroCleanObjectVelocities() const {
|
||||
void EntityMotionState::initForOwned() {
|
||||
assert(_ownershipState != EntityMotionState::OwnershipState::Unownable);
|
||||
_ownershipState = EntityMotionState::OwnershipState::LocallyOwned;
|
||||
}
|
||||
|
||||
void EntityMotionState::upgradeBidPriority(uint8_t priority) {
|
||||
_bidPriority = glm::max<uint8_t>(_bidPriority, priority);
|
||||
}
|
||||
|
||||
void EntityMotionState::clearObjectVelocities() const {
|
||||
// If transform or velocities are flagged as dirty it means a network or scripted change
|
||||
// occured between the beginning and end of the stepSimulation() and we DON'T want to apply
|
||||
// these physics simulation results.
|
||||
|
|
|
@ -24,11 +24,17 @@
|
|||
|
||||
class EntityMotionState : public ObjectMotionState {
|
||||
public:
|
||||
enum class OwnershipState {
|
||||
NotLocallyOwned = 0,
|
||||
PendingBid,
|
||||
LocallyOwned,
|
||||
Unownable
|
||||
};
|
||||
|
||||
EntityMotionState() = delete;
|
||||
EntityMotionState(btCollisionShape* shape, EntityItemPointer item);
|
||||
virtual ~EntityMotionState();
|
||||
|
||||
void updateServerPhysicsVariables();
|
||||
void handleDeactivation();
|
||||
virtual void handleEasyChanges(uint32_t& flags) override;
|
||||
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
|
||||
|
@ -44,9 +50,8 @@ public:
|
|||
// this relays outgoing position/rotation to the EntityItem
|
||||
virtual void setWorldTransform(const btTransform& worldTrans) override;
|
||||
|
||||
bool isCandidateForOwnership() const;
|
||||
bool remoteSimulationOutOfSync(uint32_t simulationStep);
|
||||
bool shouldSendUpdate(uint32_t simulationStep);
|
||||
void sendBid(OctreeEditPacketSender* packetSender, uint32_t step);
|
||||
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
|
||||
|
||||
virtual uint32_t getIncomingDirtyFlags() override;
|
||||
|
@ -70,7 +75,9 @@ public:
|
|||
virtual QUuid getSimulatorID() const override;
|
||||
virtual void bump(uint8_t priority) override;
|
||||
|
||||
EntityItemPointer getEntity() const { return _entityPtr.lock(); }
|
||||
// getEntity() returns a smart-pointer by reference because it is only ever used
|
||||
// to insert into lists of smart pointers, and the lists will make their own copies
|
||||
const EntityItemPointer& getEntity() const { return _entity; }
|
||||
|
||||
void resetMeasuredBodyAcceleration();
|
||||
void measureBodyAcceleration();
|
||||
|
@ -79,15 +86,29 @@ public:
|
|||
|
||||
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
||||
|
||||
bool shouldSendBid();
|
||||
bool isLocallyOwned() const override;
|
||||
bool shouldBeLocallyOwned() const override;
|
||||
bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents()
|
||||
|
||||
friend class PhysicalEntitySimulation;
|
||||
OwnershipState getOwnershipState() const { return _ownershipState; }
|
||||
|
||||
protected:
|
||||
// changes _outgoingPriority only if priority is larger
|
||||
void upgradeOutgoingPriority(uint8_t priority);
|
||||
void zeroCleanObjectVelocities() const;
|
||||
void updateSendVelocities();
|
||||
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
|
||||
void initForBid();
|
||||
void initForOwned();
|
||||
void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; }
|
||||
void updateServerPhysicsVariables();
|
||||
bool remoteSimulationOutOfSync(uint32_t simulationStep);
|
||||
|
||||
// changes _bidPriority only if priority is larger
|
||||
void upgradeBidPriority(uint8_t priority);
|
||||
|
||||
// upgradeBidPriority to value stored in _entity
|
||||
void slaveBidPriority();
|
||||
|
||||
void clearObjectVelocities() const;
|
||||
|
||||
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
|
||||
bool entityTreeIsLocked() const;
|
||||
|
@ -98,17 +119,21 @@ protected:
|
|||
void setShape(const btCollisionShape* shape) override;
|
||||
void setMotionType(PhysicsMotionType motionType) override;
|
||||
|
||||
// In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be
|
||||
// properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that
|
||||
// state of affairs we can't keep a real EntityItemPointer as data member (it would produce a
|
||||
// recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while
|
||||
// still granting us the capability to generate EntityItemPointers as necessary (for external data
|
||||
// structures that use the MotionState to get to the EntityItem).
|
||||
EntityItemWeakPointer _entityPtr;
|
||||
// Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid.
|
||||
EntityItem* _entity;
|
||||
// EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR
|
||||
// and is only cleared in the DTOR
|
||||
EntityItemPointer _entity;
|
||||
|
||||
bool _serverVariablesSet { false };
|
||||
// These "_serverFoo" variables represent what we think the server knows.
|
||||
// They are used in two different modes:
|
||||
//
|
||||
// (1) For remotely owned simulation: we store the last values recieved from the server.
|
||||
// When the body comes to rest and goes inactive we slam its final transforms to agree with the last server
|
||||
// update. This to reduce state synchronization errors when the local simulation deviated from remote.
|
||||
//
|
||||
// (2) For locally owned simulation: we store the last values sent to the server, integrated forward over time
|
||||
// according to how we think the server doing it. We calculate the error between the true local transform
|
||||
// and the remote to decide when to send another update.
|
||||
//
|
||||
glm::vec3 _serverPosition; // in simulation-frame (not world-frame)
|
||||
glm::quat _serverRotation;
|
||||
glm::vec3 _serverVelocity;
|
||||
|
@ -119,16 +144,18 @@ protected:
|
|||
|
||||
glm::vec3 _lastVelocity;
|
||||
glm::vec3 _measuredAcceleration;
|
||||
quint64 _nextOwnershipBid { 0 };
|
||||
quint64 _nextBidExpiry { 0 };
|
||||
|
||||
float _measuredDeltaTime;
|
||||
uint32_t _lastMeasureStep;
|
||||
uint32_t _lastStep; // last step of server extrapolation
|
||||
|
||||
OwnershipState _ownershipState { OwnershipState::NotLocallyOwned };
|
||||
uint8_t _loopsWithoutOwner;
|
||||
mutable uint8_t _accelerationNearlyGravityCount;
|
||||
uint8_t _numInactiveUpdates { 1 };
|
||||
uint8_t _outgoingPriority { 0 };
|
||||
uint8_t _bidPriority { 0 };
|
||||
bool _serverVariablesSet { false };
|
||||
};
|
||||
|
||||
#endif // hifi_EntityMotionState_h
|
||||
|
|
|
@ -148,9 +148,9 @@ public:
|
|||
|
||||
virtual const QUuid getObjectID() const = 0;
|
||||
|
||||
virtual quint8 getSimulationPriority() const { return 0; }
|
||||
virtual uint8_t getSimulationPriority() const { return 0; }
|
||||
virtual QUuid getSimulatorID() const = 0;
|
||||
virtual void bump(quint8 priority) {}
|
||||
virtual void bump(uint8_t priority) {}
|
||||
|
||||
virtual QString getName() const { return ""; }
|
||||
|
||||
|
@ -164,7 +164,7 @@ public:
|
|||
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
|
||||
|
||||
virtual bool isLocallyOwned() const { return false; }
|
||||
virtual bool shouldBeLocallyOwned() const { return false; }
|
||||
virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents()
|
||||
|
||||
friend class PhysicsEngine;
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ void PhysicalEntitySimulation::init(
|
|||
}
|
||||
|
||||
// begin EntitySimulation overrides
|
||||
void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
||||
void PhysicalEntitySimulation::updateEntitiesInternal(uint64_t now) {
|
||||
// Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere.
|
||||
}
|
||||
|
||||
|
@ -61,33 +61,58 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
|
|||
void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
|
||||
if (entity->isSimulated()) {
|
||||
EntitySimulation::removeEntityInternal(entity);
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesToAddToPhysics.remove(entity);
|
||||
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
_outgoingChanges.remove(motionState);
|
||||
removeOwnershipData(motionState);
|
||||
_entitiesToRemoveFromPhysics.insert(entity);
|
||||
} else {
|
||||
_entitiesToDelete.insert(entity);
|
||||
} else if (entity->isDead() && entity->getElement()) {
|
||||
_deadEntities.insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
for (auto entity : _entitiesToDelete) {
|
||||
// this entity is still in its tree, so we insert into the external list
|
||||
entitiesToDelete.push_back(entity);
|
||||
void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) {
|
||||
assert(motionState);
|
||||
if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) {
|
||||
for (uint32_t i = 0; i < _owned.size(); ++i) {
|
||||
if (_owned[i] == motionState) {
|
||||
_owned[i]->clearOwnershipState();
|
||||
_owned.remove(i);
|
||||
}
|
||||
}
|
||||
} else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) {
|
||||
for (uint32_t i = 0; i < _bids.size(); ++i) {
|
||||
if (_bids[i] == motionState) {
|
||||
_bids[i]->clearOwnershipState();
|
||||
_bids.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
|
||||
// rather than do it here
|
||||
void PhysicalEntitySimulation::clearOwnershipData() {
|
||||
for (uint32_t i = 0; i < _owned.size(); ++i) {
|
||||
_owned[i]->clearOwnershipState();
|
||||
}
|
||||
_owned.clear();
|
||||
for (uint32_t i = 0; i < _bids.size(); ++i) {
|
||||
_bids[i]->clearOwnershipState();
|
||||
}
|
||||
_bids.clear();
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::takeDeadEntities(SetOfEntities& deadEntities) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
for (auto entity : _deadEntities) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
_entitiesToRemoveFromPhysics.insert(entity);
|
||||
}
|
||||
}
|
||||
_entitiesToDelete.clear();
|
||||
_deadEntities.swap(deadEntities);
|
||||
_deadEntities.clear();
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
|
||||
|
@ -98,15 +123,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
|
|||
if (motionState) {
|
||||
if (!entity->shouldBePhysical()) {
|
||||
// the entity should be removed from the physical simulation
|
||||
_pendingChanges.remove(motionState);
|
||||
_incomingChanges.remove(motionState);
|
||||
_physicalObjects.remove(motionState);
|
||||
_outgoingChanges.remove(motionState);
|
||||
removeOwnershipData(motionState);
|
||||
_entitiesToRemoveFromPhysics.insert(entity);
|
||||
if (entity->isMovingRelativeToParent()) {
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
}
|
||||
} else {
|
||||
_pendingChanges.insert(motionState);
|
||||
_incomingChanges.insert(motionState);
|
||||
}
|
||||
} else if (entity->shouldBePhysical()) {
|
||||
// The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet.
|
||||
|
@ -125,80 +150,69 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
|
|||
// while it is in the middle of a simulation step. As it is, we're probably in shutdown mode
|
||||
// anyway, so maybe the simulation was already properly shutdown? Cross our fingers...
|
||||
|
||||
// copy everything into _entitiesToDelete
|
||||
for (auto stateItr : _physicalObjects) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
|
||||
_entitiesToDelete.insert(motionState->getEntity());
|
||||
}
|
||||
|
||||
// then remove the objects (aka MotionStates) from physics
|
||||
// remove the objects (aka MotionStates) from physics
|
||||
_physicsEngine->removeSetOfObjects(_physicalObjects);
|
||||
|
||||
// delete the MotionStates
|
||||
// TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete
|
||||
// its own PhysicsInfo rather than do it here
|
||||
for (auto entity : _entitiesToDelete) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
entity->setPhysicsInfo(nullptr);
|
||||
delete motionState;
|
||||
}
|
||||
for (auto stateItr : _physicalObjects) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
|
||||
assert(motionState);
|
||||
EntityItemPointer entity = motionState->getEntity();
|
||||
entity->setPhysicsInfo(nullptr);
|
||||
// TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
|
||||
// until then we must do it here
|
||||
delete motionState;
|
||||
}
|
||||
|
||||
// finally clear all lists maintained by this class
|
||||
_physicalObjects.clear();
|
||||
|
||||
// clear all other lists specific to this derived class
|
||||
clearOwnershipData();
|
||||
_entitiesToRemoveFromPhysics.clear();
|
||||
_entitiesToRelease.clear();
|
||||
_entitiesToAddToPhysics.clear();
|
||||
_pendingChanges.clear();
|
||||
_outgoingChanges.clear();
|
||||
_incomingChanges.clear();
|
||||
}
|
||||
|
||||
// virtual
|
||||
void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
|
||||
assert(entity);
|
||||
assert(entity->isDead());
|
||||
QMutexLocker lock(&_mutex);
|
||||
entity->clearActions(getThisPointer());
|
||||
removeEntityInternal(entity);
|
||||
}
|
||||
// end EntitySimulation overrides
|
||||
|
||||
void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) {
|
||||
result.clear();
|
||||
const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhysics() {
|
||||
QMutexLocker lock(&_mutex);
|
||||
for (auto entity: _entitiesToRemoveFromPhysics) {
|
||||
// make sure it isn't on any side lists
|
||||
_entitiesToAddToPhysics.remove(entity);
|
||||
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
_pendingChanges.remove(motionState);
|
||||
_outgoingChanges.remove(motionState);
|
||||
_physicalObjects.remove(motionState);
|
||||
result.push_back(motionState);
|
||||
_entitiesToRelease.insert(entity);
|
||||
assert(motionState);
|
||||
|
||||
_entitiesToAddToPhysics.remove(entity);
|
||||
if (entity->isDead() && entity->getElement()) {
|
||||
_deadEntities.insert(entity);
|
||||
}
|
||||
|
||||
if (entity->isDead()) {
|
||||
_entitiesToDelete.insert(entity);
|
||||
}
|
||||
_incomingChanges.remove(motionState);
|
||||
removeOwnershipData(motionState);
|
||||
_physicalObjects.remove(motionState);
|
||||
|
||||
// remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine)
|
||||
_objectsToDelete.push_back(motionState);
|
||||
}
|
||||
_entitiesToRemoveFromPhysics.clear();
|
||||
return _objectsToDelete;
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
|
||||
QMutexLocker lock(&_mutex);
|
||||
for (auto entity: _entitiesToRelease) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
assert(motionState);
|
||||
entity->setPhysicsInfo(nullptr);
|
||||
for (auto motionState : _objectsToDelete) {
|
||||
// someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
|
||||
// until then we must do it here
|
||||
// NOTE: a reference to the EntityItemPointer is released in the EntityMotionState::dtor
|
||||
delete motionState;
|
||||
|
||||
if (entity->isDead()) {
|
||||
_entitiesToDelete.insert(entity);
|
||||
}
|
||||
}
|
||||
_entitiesToRelease.clear();
|
||||
_objectsToDelete.clear();
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
|
||||
|
@ -248,18 +262,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
|
|||
void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
for (auto object : objectsToChange) {
|
||||
_pendingChanges.insert(static_cast<EntityMotionState*>(object));
|
||||
_incomingChanges.insert(static_cast<EntityMotionState*>(object));
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) {
|
||||
result.clear();
|
||||
QMutexLocker lock(&_mutex);
|
||||
for (auto stateItr : _pendingChanges) {
|
||||
for (auto stateItr : _incomingChanges) {
|
||||
EntityMotionState* motionState = &(*stateItr);
|
||||
result.push_back(motionState);
|
||||
}
|
||||
_pendingChanges.clear();
|
||||
_incomingChanges.clear();
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) {
|
||||
|
@ -279,20 +293,22 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
|
|||
PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size());
|
||||
QMutexLocker lock(&_mutex);
|
||||
|
||||
// walk the motionStates looking for those that correspond to entities
|
||||
{
|
||||
PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size());
|
||||
for (auto stateItr : motionStates) {
|
||||
ObjectMotionState* state = &(*stateItr);
|
||||
assert(state);
|
||||
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
|
||||
EntityItemPointer entity = entityState->getEntity();
|
||||
assert(entity.get());
|
||||
if (entityState->isCandidateForOwnership()) {
|
||||
_outgoingChanges.insert(entityState);
|
||||
for (auto stateItr : motionStates) {
|
||||
ObjectMotionState* state = &(*stateItr);
|
||||
assert(state);
|
||||
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
|
||||
_entitiesToSort.insert(entityState->getEntity());
|
||||
if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) {
|
||||
// NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in
|
||||
// and is distinct from entityState->isLocallyOwned() which checks the simulation ownership
|
||||
// properties of the corresponding EntityItem. It is possible for the two states to be out
|
||||
// of sync. In fact, we're trying to put them back into sync here.
|
||||
if (entityState->isLocallyOwned()) {
|
||||
addOwnership(entityState);
|
||||
} else if (entityState->shouldSendBid()) {
|
||||
addOwnershipBid(entityState);
|
||||
}
|
||||
_entitiesToSort.insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -302,26 +318,78 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
|
|||
_lastStepSendPackets = numSubsteps;
|
||||
|
||||
if (Physics::getSessionUUID().isNull()) {
|
||||
// usually don't get here, but if so --> nothing to do
|
||||
_outgoingChanges.clear();
|
||||
return;
|
||||
// usually don't get here, but if so clear all ownership
|
||||
clearOwnershipData();
|
||||
}
|
||||
// send updates before bids, because this simplifies the logic thasuccessful bids will immediately send an update when added to the 'owned' list
|
||||
sendOwnedUpdates(numSubsteps);
|
||||
sendOwnershipBids(numSubsteps);
|
||||
}
|
||||
}
|
||||
|
||||
// look for entities to prune or update
|
||||
PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size());
|
||||
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
|
||||
while (stateItr != _outgoingChanges.end()) {
|
||||
EntityMotionState* state = *stateItr;
|
||||
if (!state->isCandidateForOwnership()) {
|
||||
// prune
|
||||
stateItr = _outgoingChanges.erase(stateItr);
|
||||
} else if (state->shouldSendUpdate(numSubsteps)) {
|
||||
// update
|
||||
state->sendUpdate(_entityPacketSender, numSubsteps);
|
||||
++stateItr;
|
||||
} else {
|
||||
++stateItr;
|
||||
void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) {
|
||||
motionState->initForBid();
|
||||
motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps());
|
||||
_bids.push_back(motionState);
|
||||
_nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry());
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) {
|
||||
motionState->initForOwned();
|
||||
_owned.push_back(motionState);
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (now > _nextBidExpiry) {
|
||||
PROFILE_RANGE_EX(simulation_physics, "Bid", 0x00000000, (uint64_t)_bids.size());
|
||||
_nextBidExpiry = std::numeric_limits<uint64_t>::max();
|
||||
uint32_t i = 0;
|
||||
while (i < _bids.size()) {
|
||||
bool removeBid = false;
|
||||
if (_bids[i]->isLocallyOwned()) {
|
||||
// when an object transitions from 'bid' to 'owned' we are changing the "mode" of data stored
|
||||
// in the EntityMotionState::_serverFoo variables (please see comments in EntityMotionState.h)
|
||||
// therefore we need to immediately send an update so that the values stored are what we're
|
||||
// "telling" the server rather than what we've been "hearing" from the server.
|
||||
_bids[i]->slaveBidPriority();
|
||||
_bids[i]->sendUpdate(_entityPacketSender, numSubsteps);
|
||||
|
||||
addOwnership(_bids[i]);
|
||||
removeBid = true;
|
||||
} else if (!_bids[i]->shouldSendBid()) {
|
||||
removeBid = true;
|
||||
_bids[i]->clearOwnershipState();
|
||||
}
|
||||
if (removeBid) {
|
||||
_bids.remove(i);
|
||||
} else {
|
||||
if (now > _bids[i]->getNextBidExpiry()) {
|
||||
_bids[i]->sendBid(_entityPacketSender, numSubsteps);
|
||||
_nextBidExpiry = glm::min(_nextBidExpiry, _bids[i]->getNextBidExpiry());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) {
|
||||
PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size());
|
||||
uint32_t i = 0;
|
||||
while (i < _owned.size()) {
|
||||
if (!_owned[i]->isLocallyOwned()) {
|
||||
if (_owned[i]->shouldSendBid()) {
|
||||
addOwnershipBid(_owned[i]);
|
||||
} else {
|
||||
_owned[i]->clearOwnershipState();
|
||||
}
|
||||
_owned.remove(i);
|
||||
} else {
|
||||
if (_owned[i]->shouldSendUpdate(numSubsteps)) {
|
||||
_owned[i]->sendUpdate(_entityPacketSender, numSubsteps);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -336,7 +404,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
|
||||
if (_physicsEngine) {
|
||||
// FIXME put fine grain locking into _physicsEngine
|
||||
|
|
|
@ -27,7 +27,19 @@ class PhysicalEntitySimulation;
|
|||
using PhysicalEntitySimulationPointer = std::shared_ptr<PhysicalEntitySimulation>;
|
||||
using SetOfEntityMotionStates = QSet<EntityMotionState*>;
|
||||
|
||||
class VectorOfEntityMotionStates: public std::vector<EntityMotionState*> {
|
||||
public:
|
||||
void remove(uint32_t index) {
|
||||
assert(index < size());
|
||||
if (index < size() - 1) {
|
||||
(*this)[index] = back();
|
||||
}
|
||||
pop_back();
|
||||
}
|
||||
};
|
||||
|
||||
class PhysicalEntitySimulation : public EntitySimulation {
|
||||
Q_OBJECT
|
||||
public:
|
||||
PhysicalEntitySimulation();
|
||||
~PhysicalEntitySimulation();
|
||||
|
@ -37,21 +49,28 @@ public:
|
|||
virtual void addDynamic(EntityDynamicPointer dynamic) override;
|
||||
virtual void applyDynamicChanges() override;
|
||||
|
||||
virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override;
|
||||
virtual void takeDeadEntities(SetOfEntities& deadEntities) override;
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
protected: // only called by EntitySimulation
|
||||
// overrides for EntitySimulation
|
||||
virtual void updateEntitiesInternal(const quint64& now) override;
|
||||
virtual void updateEntitiesInternal(uint64_t now) override;
|
||||
virtual void addEntityInternal(EntityItemPointer entity) override;
|
||||
virtual void removeEntityInternal(EntityItemPointer entity) override;
|
||||
virtual void changeEntityInternal(EntityItemPointer entity) override;
|
||||
virtual void clearEntitiesInternal() override;
|
||||
|
||||
void removeOwnershipData(EntityMotionState* motionState);
|
||||
void clearOwnershipData();
|
||||
|
||||
public:
|
||||
virtual void prepareEntityForDelete(EntityItemPointer entity) override;
|
||||
|
||||
void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result);
|
||||
const VectorOfMotionStates& getObjectsToRemoveFromPhysics();
|
||||
void deleteObjectsRemovedFromPhysics();
|
||||
|
||||
void getObjectsToAddToPhysics(VectorOfMotionStates& result);
|
||||
void setObjectsToChange(const VectorOfMotionStates& objectsToChange);
|
||||
void getObjectsToChange(VectorOfMotionStates& result);
|
||||
|
@ -62,19 +81,28 @@ public:
|
|||
|
||||
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
|
||||
|
||||
private:
|
||||
SetOfEntities _entitiesToRemoveFromPhysics;
|
||||
SetOfEntities _entitiesToRelease;
|
||||
SetOfEntities _entitiesToAddToPhysics;
|
||||
void addOwnershipBid(EntityMotionState* motionState);
|
||||
void addOwnership(EntityMotionState* motionState);
|
||||
void sendOwnershipBids(uint32_t numSubsteps);
|
||||
void sendOwnedUpdates(uint32_t numSubsteps);
|
||||
|
||||
SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed
|
||||
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server
|
||||
private:
|
||||
SetOfEntities _entitiesToAddToPhysics;
|
||||
SetOfEntities _entitiesToRemoveFromPhysics;
|
||||
|
||||
VectorOfMotionStates _objectsToDelete;
|
||||
|
||||
SetOfEntityMotionStates _incomingChanges; // EntityMotionStates that have changed from external sources
|
||||
// and need their RigidBodies updated
|
||||
|
||||
SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
|
||||
|
||||
PhysicsEnginePointer _physicsEngine = nullptr;
|
||||
EntityEditPacketSender* _entityPacketSender = nullptr;
|
||||
|
||||
VectorOfEntityMotionStates _owned;
|
||||
VectorOfEntityMotionStates _bids;
|
||||
uint64_t _nextBidExpiry;
|
||||
uint32_t _lastStepSendPackets { 0 };
|
||||
};
|
||||
|
||||
|
|
|
@ -571,7 +571,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
|||
// modify the logic below.
|
||||
//
|
||||
// We only create events when at least one of the objects is (or should be) owned in the local simulation.
|
||||
if (motionStateA && (motionStateA->shouldBeLocallyOwned())) {
|
||||
if (motionStateA && (motionStateA->isLocallyOwnedOrShouldBe())) {
|
||||
QUuid idA = motionStateA->getObjectID();
|
||||
QUuid idB;
|
||||
if (motionStateB) {
|
||||
|
@ -582,7 +582,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
|||
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
|
||||
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
|
||||
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange));
|
||||
} else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) {
|
||||
} else if (motionStateB && (motionStateB->isLocallyOwnedOrShouldBe())) {
|
||||
QUuid idB = motionStateB->getObjectID();
|
||||
QUuid idA;
|
||||
if (motionStateA) {
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "model_lightmap_fade_vert.h"
|
||||
#include "model_lightmap_normal_map_fade_vert.h"
|
||||
#include "model_translucent_vert.h"
|
||||
#include "model_translucent_normal_map_vert.h"
|
||||
#include "skin_model_fade_vert.h"
|
||||
#include "skin_model_normal_map_fade_vert.h"
|
||||
#include "skin_model_fade_dq_vert.h"
|
||||
|
@ -68,11 +69,13 @@
|
|||
#include "model_lightmap_normal_map_frag.h"
|
||||
#include "model_translucent_frag.h"
|
||||
#include "model_translucent_unlit_frag.h"
|
||||
#include "model_translucent_normal_map_frag.h"
|
||||
|
||||
#include "model_lightmap_fade_frag.h"
|
||||
#include "model_lightmap_normal_map_fade_frag.h"
|
||||
#include "model_translucent_fade_frag.h"
|
||||
#include "model_translucent_unlit_fade_frag.h"
|
||||
#include "model_translucent_normal_map_fade_frag.h"
|
||||
|
||||
#include "overlay3D_vert.h"
|
||||
#include "overlay3D_frag.h"
|
||||
|
@ -187,6 +190,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
auto modelLightmapVertex = model_lightmap_vert::getShader();
|
||||
auto modelLightmapNormalMapVertex = model_lightmap_normal_map_vert::getShader();
|
||||
auto modelTranslucentVertex = model_translucent_vert::getShader();
|
||||
auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader();
|
||||
auto modelShadowVertex = model_shadow_vert::getShader();
|
||||
|
||||
auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader();
|
||||
|
@ -227,6 +231,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
auto modelNormalMapPixel = model_normal_map_frag::getShader();
|
||||
auto modelTranslucentPixel = model_translucent_frag::getShader();
|
||||
auto modelTranslucentUnlitPixel = model_translucent_unlit_frag::getShader();
|
||||
auto modelTranslucentNormalMapPixel = model_translucent_normal_map_frag::getShader();
|
||||
auto modelShadowPixel = model_shadow_frag::getShader();
|
||||
auto modelLightmapPixel = model_lightmap_frag::getShader();
|
||||
auto modelLightmapNormalMapPixel = model_lightmap_normal_map_frag::getShader();
|
||||
|
@ -239,6 +244,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
auto modelShadowFadePixel = model_shadow_fade_frag::getShader();
|
||||
auto modelTranslucentFadePixel = model_translucent_fade_frag::getShader();
|
||||
auto modelTranslucentUnlitFadePixel = model_translucent_unlit_fade_frag::getShader();
|
||||
auto modelTranslucentNormalMapFadePixel = model_translucent_normal_map_fade_frag::getShader();
|
||||
auto simpleFadePixel = simple_textured_fade_frag::getShader();
|
||||
auto simpleUnlitFadePixel = simple_textured_unlit_fade_frag::getShader();
|
||||
auto simpleTranslucentFadePixel = simple_transparent_textured_fade_frag::getShader();
|
||||
|
@ -296,7 +302,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withTranslucent().withTangents(),
|
||||
modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
modelTranslucentNormalMapVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
// FIXME: Ignore lightmap for translucents meshpart
|
||||
Key::Builder().withMaterial().withTranslucent().withLightmap(),
|
||||
|
@ -316,7 +322,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
simpleFadeVertex, simpleTranslucentUnlitFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withTranslucent().withTangents().withFade(),
|
||||
modelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
|
||||
modelTranslucentNormalMapVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
// FIXME: Ignore lightmap for translucents meshpart
|
||||
Key::Builder().withMaterial().withTranslucent().withLightmap().withFade(),
|
||||
|
@ -358,14 +364,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(),
|
||||
skinModelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
skinModelNormalMapTranslucentVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
|
||||
// Same thing but with Fade on
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(),
|
||||
skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withFade(),
|
||||
skinModelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
|
||||
skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
|
||||
|
||||
// dual quaternion skinned
|
||||
addPipeline(
|
||||
|
@ -388,14 +394,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(),
|
||||
skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
|
||||
// Same thing but with Fade on
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(),
|
||||
skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(),
|
||||
skinModelNormalMapFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
|
||||
skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
|
||||
|
||||
// Depth-only
|
||||
addPipeline(
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
var DEFAULT_SCRIPTS_COMBINED = [
|
||||
"system/progress.js",
|
||||
"system/+android/touchscreenvirtualpad.js",
|
||||
"system/+android/bottombar.js",
|
||||
"system/+android/actionbar.js",
|
||||
"system/+android/audio.js" ,
|
||||
"system/+android/modes.js",
|
||||
"system/+android/stats.js"/*,
|
||||
|
|
|
@ -157,7 +157,7 @@
|
|||
})
|
||||
|
||||
function cleanup() {
|
||||
Pointers.removePointer(ray);
|
||||
Pointers.removePointer(laser);
|
||||
Selection.disableListHighlight(HoveringList)
|
||||
Selection.removeListFromMap(HoveringList)
|
||||
|
||||
|
|
53
scripts/system/+android/actionbar.js
Normal file
53
scripts/system/+android/actionbar.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
"use strict";
|
||||
//
|
||||
// backbutton.js
|
||||
// scripts/system/+android
|
||||
//
|
||||
// Created by Gabriel Calero & Cristian Duarte on Apr 06, 2018
|
||||
// Copyright 2018 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
|
||||
//
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var actionbar;
|
||||
var backButton;
|
||||
|
||||
var logEnabled = true;
|
||||
|
||||
function printd(str) {
|
||||
if (logEnabled)
|
||||
print("[actionbar.js] " + str);
|
||||
}
|
||||
|
||||
function init() {
|
||||
actionbar = new QmlFragment({
|
||||
qml: "hifi/ActionBar.qml"
|
||||
});
|
||||
backButton = actionbar.addButton({
|
||||
icon: "icons/+android/backward.svg",
|
||||
activeIcon: "icons/+android/backward.svg",
|
||||
text: "",
|
||||
bgOpacity: 0.0,
|
||||
activeBgOpacity: 0.0,
|
||||
bgColor: "#FFFFFF"
|
||||
});
|
||||
|
||||
backButton.clicked.connect(onBackPressed);
|
||||
}
|
||||
|
||||
function onBackPressed() {
|
||||
App.openAndroidActivity("Goto");
|
||||
}
|
||||
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
if(backButton) {
|
||||
backButton.clicked.disconnect(onBackPressed);
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -40,7 +40,7 @@
|
|||
}
|
||||
|
||||
function onScreenChanged(type, url) {
|
||||
onHelpScreen = type === "Web" && url.startsWith(HELP_URL);
|
||||
onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0);
|
||||
button.editProperties({ isActive: onHelpScreen });
|
||||
}
|
||||
|
||||
|
|
|
@ -411,8 +411,6 @@ function snapshotUploaded(isError, reply) {
|
|||
} else {
|
||||
print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID);
|
||||
}
|
||||
} else {
|
||||
print(reply);
|
||||
}
|
||||
isUploadingPrintableStill = false;
|
||||
}
|
||||
|
|
|
@ -554,7 +554,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
|
|||
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
|
||||
void Test::createMDFile() {
|
||||
// Folder selection
|
||||
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
|
||||
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", ".", QFileDialog::ShowDirsOnly);
|
||||
if (testDirectory == "") {
|
||||
return;
|
||||
}
|
||||
|
@ -571,7 +571,7 @@ void Test::createMDFile() {
|
|||
|
||||
QString mdFilename(testDirectory + "/" + "test.md");
|
||||
QFile mdFile(mdFilename);
|
||||
if (!mdFile.open(QIODevice::ReadWrite)) {
|
||||
if (!mdFile.open(QIODevice::WriteOnly)) {
|
||||
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
|
||||
exit(-1);
|
||||
}
|
||||
|
@ -640,7 +640,7 @@ void Test::createMDFile() {
|
|||
int snapShotIndex { 0 };
|
||||
for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) {
|
||||
stream << "### Step " << QString::number(i) << "\n";
|
||||
stream << "- " << testScriptLines.stepList[i]->text << "\n";
|
||||
stream << "- " << testScriptLines.stepList[i + 1]->text << "\n";
|
||||
if (testScriptLines.stepList[i]->takeSnapshot) {
|
||||
stream << "- .rightJustified(5, '0') << ".png)\n";
|
||||
++snapShotIndex;
|
||||
|
|
Loading…
Reference in a new issue