Merge branch 'master' of https://github.com/highfidelity/hifi into OculusSDK

This commit is contained in:
barnold1953 2014-07-08 17:15:35 -07:00
commit b5a9f50ae7
46 changed files with 761 additions and 440 deletions

View file

@ -32,14 +32,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Instruct CMake to run moc automatically when needed.
set(CMAKE_AUTOMOC ON)
if (APPLE)
exec_program(uname ARGS -v OUTPUT_VARIABLE DARWIN_VERSION)
string(REGEX MATCH "[0-9]+" DARWIN_VERSION ${DARWIN_VERSION})
if (DARWIN_VERSION GREATER 12)
set(CMAKE_CXX_FLAGS "-stdlib=libstdc++")
endif (DARWIN_VERSION GREATER 12)
endif (APPLE)
# targets not supported on windows
if (NOT WIN32)
add_subdirectory(animation-server)

View file

@ -211,11 +211,7 @@ void Agent::run() {
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply *reply = networkAccessManager.get(QNetworkRequest(scriptURL));
// Make sure cache on same thread than its parent (NetworkAccessManager)
QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->moveToThread(networkAccessManager.thread());
cache->setParent(&networkAccessManager);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache");
networkAccessManager.setCache(cache);
@ -273,4 +269,5 @@ void Agent::run() {
void Agent::aboutToFinish() {
_scriptEngine.stop();
NetworkAccessManager::getInstance().clearAccessCache();
}

View file

@ -91,6 +91,7 @@ AudioMixer::~AudioMixer() {
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f;
const float ATTENUATION_EPSILON_DISTANCE = 0.1f;
void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd,
AvatarAudioRingBuffer* listeningNodeBuffer) {
@ -107,7 +108,7 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
glm::vec3 relativePosition = bufferToAdd->getPosition() - listeningNodeBuffer->getPosition();
float distanceBetween = glm::length(relativePosition);
if (distanceBetween < EPSILON) {
distanceBetween = EPSILON;
}
@ -124,6 +125,12 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
shouldAttenuate = !bufferToAdd->getListenerUnattenuatedZone()->contains(listeningNodeBuffer->getPosition());
}
if (bufferToAdd->getType() == PositionalAudioRingBuffer::Injector) {
attenuationCoefficient *= reinterpret_cast<InjectedAudioRingBuffer*>(bufferToAdd)->getAttenuationRatio();
}
shouldAttenuate = shouldAttenuate && distanceBetween > ATTENUATION_EPSILON_DISTANCE;
if (shouldAttenuate) {
glm::quat inverseOrientation = glm::inverse(listeningNodeBuffer->getOrientation());
@ -131,9 +138,7 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
float radius = 0.0f;
if (bufferToAdd->getType() == PositionalAudioRingBuffer::Injector) {
InjectedAudioRingBuffer* injectedBuffer = (InjectedAudioRingBuffer*) bufferToAdd;
radius = injectedBuffer->getRadius();
attenuationCoefficient *= injectedBuffer->getAttenuationRatio();
radius = reinterpret_cast<InjectedAudioRingBuffer*>(bufferToAdd)->getRadius();
}
if (radius == 0 || (distanceSquareToSource > radius * radius)) {
@ -158,7 +163,7 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
const float OFF_AXIS_ATTENUATION_FORMULA_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f;
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION +
(OFF_AXIS_ATTENUATION_FORMULA_STEP * (angleOfDelivery / PI_OVER_TWO));
(OFF_AXIS_ATTENUATION_FORMULA_STEP * (angleOfDelivery / PI_OVER_TWO));
// multiply the current attenuation coefficient by the calculated off axis coefficient
attenuationCoefficient *= offAxisCoefficient;

View file

@ -138,11 +138,14 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
// this was a used buffer, push the output pointer forwards
PositionalAudioRingBuffer* audioBuffer = *i;
const int INJECTOR_CONSECUTIVE_NOT_MIXED_THRESHOLD = 100;
if (audioBuffer->willBeAddedToMix()) {
audioBuffer->shiftReadPosition(audioBuffer->getSamplesPerFrame());
audioBuffer->setWillBeAddedToMix(false);
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
&& audioBuffer->hasStarted() && audioBuffer->isStarved()
&& audioBuffer->getConsecutiveNotMixedCount() > INJECTOR_CONSECUTIVE_NOT_MIXED_THRESHOLD) {
// this is an empty audio buffer that has starved, safe to delete
// also delete its sequence number stats
QUuid streamIdentifier = ((InjectedAudioRingBuffer*)audioBuffer)->getStreamIdentifier();

View file

@ -21,7 +21,8 @@
const int SEND_INTERVAL = 50;
MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
ThreadedAssignment(packet) {
ThreadedAssignment(packet),
_sendTimer(this) {
_sendTimer.setSingleShot(true);
connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas()));
@ -91,24 +92,54 @@ void MetavoxelServer::sendDeltas() {
MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) :
Endpoint(node, new PacketRecord(), NULL),
_server(server) {
_server(server),
_reliableDeltaChannel(NULL) {
connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
SLOT(handleMessage(const QVariant&)));
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(checkReliableDeltaReceived()));
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&, Bitstream&)),
SLOT(handleMessage(const QVariant&, Bitstream&)));
}
void MetavoxelSession::update() {
// wait until we have a valid lod
if (_lod.isValid()) {
Endpoint::update();
// wait until we have a valid lod before sending
if (!_lod.isValid()) {
return;
}
}
void MetavoxelSession::writeUpdateMessage(Bitstream& out) {
// if we're sending a reliable delta, wait until it's acknowledged
if (_reliableDeltaChannel) {
Bitstream& out = _sequencer.startPacket();
out << QVariant::fromValue(MetavoxelDeltaPendingMessage());
_sequencer.endPacket();
return;
}
Bitstream& out = _sequencer.startPacket();
int start = _sequencer.getOutputStream().getUnderlying().device()->pos();
out << QVariant::fromValue(MetavoxelDeltaMessage());
PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
_server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
out.flush();
int end = _sequencer.getOutputStream().getUnderlying().device()->pos();
if (end > _sequencer.getMaxPacketSize()) {
// we need to send the delta on the reliable channel
_reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
_reliableDeltaChannel->startMessage();
_reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start);
_reliableDeltaChannel->endMessage();
_reliableDeltaWriteMappings = out.getAndResetWriteMappings();
_reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten();
_reliableDeltaData = _server->getData();
_reliableDeltaLOD = _lod;
// go back to the beginning with the current packet and note that there's a delta pending
_sequencer.getOutputStream().getUnderlying().device()->seek(start);
out << QVariant::fromValue(MetavoxelDeltaPendingMessage());
_sequencer.endPacket();
} else {
_sequencer.endPacket();
}
}
void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
@ -116,7 +147,8 @@ void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
}
PacketRecord* MetavoxelSession::maybeCreateSendRecord() const {
return new PacketRecord(_lod, _server->getData());
return _reliableDeltaChannel ? new PacketRecord(_reliableDeltaLOD, _reliableDeltaData) :
new PacketRecord(_lod, _server->getData());
}
void MetavoxelSession::handleMessage(const QVariant& message) {
@ -134,3 +166,13 @@ void MetavoxelSession::handleMessage(const QVariant& message) {
}
}
}
void MetavoxelSession::checkReliableDeltaReceived() {
if (!_reliableDeltaChannel || _reliableDeltaChannel->getOffset() < _reliableDeltaReceivedOffset) {
return;
}
_sequencer.getOutputStream().persistWriteMappings(_reliableDeltaWriteMappings);
_reliableDeltaWriteMappings = Bitstream::WriteMappings();
_reliableDeltaData = MetavoxelData();
_reliableDeltaChannel = NULL;
}

View file

@ -63,7 +63,6 @@ public:
protected:
virtual void writeUpdateMessage(Bitstream& out);
virtual void handleMessage(const QVariant& message, Bitstream& in);
virtual PacketRecord* maybeCreateSendRecord() const;
@ -71,12 +70,19 @@ protected:
private slots:
void handleMessage(const QVariant& message);
void checkReliableDeltaReceived();
private:
MetavoxelServer* _server;
MetavoxelLOD _lod;
ReliableChannel* _reliableDeltaChannel;
int _reliableDeltaReceivedOffset;
MetavoxelData _reliableDeltaData;
MetavoxelLOD _reliableDeltaLOD;
Bitstream::WriteMappings _reliableDeltaWriteMappings;
};
#endif // hifi_MetavoxelServer_h

View file

@ -26,8 +26,8 @@ else ()
set(RTMIDI_SEARCH_DIRS "${RTMIDI_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/rtmidi")
find_path(RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEARCH_DIRS})
find_file(RTMIDI_CPP NAMES RtMidi.cpp PATH_SUFFIXES src HINTS ${RTMIDI_SEARCH_DIRS})
find_library(RTMIDI_LIBRARY NAMES rtmidi PATH_SUFFIXES lib HINTS ${RTMIDI_SEARCH_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_CPP)
find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_LIBRARY)
endif ()

View file

@ -10,27 +10,34 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function length(v) {
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
var clapAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/ClapAnimations/ClapHands_Standing.fbx";
var startEndFrames = [];
startEndFrames.push({ start: 0, end: 8});
startEndFrames.push({ start: 10, end: 20});
startEndFrames.push({ start: 20, end: 28});
startEndFrames.push({ start: 30, end: 37});
startEndFrames.push({ start: 41, end: 46});
startEndFrames.push({ start: 53, end: 58});
var lastClapFrame = 0;
var lastAnimFrame = 0;
function printVector(v) {
print(v.x + ", " + v.y + ", " + v.z + "\n");
}
var claps = [];
claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap1Reverb.wav"));
claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap2Reverb.wav"));
claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap3Reverb.wav"));
claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap4Reverb.wav"));
claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap5Reverb.wav"));
claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap6Reverb.wav"));
var numberOfSounds = claps.length;
function vMinus(a, b) {
var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
return rval;
}
var clappingNow = false;
var collectedClicks = 0;
// First, load the clap sound from a URL
var clap1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap1.raw");
var clap2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap2.raw");
var clap3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap3.raw");
var clap4 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap4.raw");
var clap5 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap5.raw");
var clap6 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap6.raw");
var clickClappingNow = false;
var CLAP_START_RATE = 15.0;
var clapRate = CLAP_START_RATE;
var startedTimer = false;
var clapping = new Array();
clapping[0] = false;
@ -38,36 +45,94 @@ clapping[1] = false;
function maybePlaySound(deltaTime) {
// Set the location and other info for the sound to play
var palm1Position = Controller.getSpatialControlPosition(0);
var palm2Position = Controller.getSpatialControlPosition(2);
var distanceBetween = length(vMinus(palm1Position, palm2Position));
for (var palm = 0; palm < 2; palm++) {
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1);
var speed = length(palmVelocity);
const CLAP_SPEED = 0.2;
const CLAP_DISTANCE = 0.2;
var animationDetails = MyAvatar.getAnimationDetails(clapAnimation);
if (!clapping[palm] && (distanceBetween < CLAP_DISTANCE) && (speed > CLAP_SPEED)) {
var options = new AudioInjectionOptions();
options.position = palm1Position;
options.volume = speed / 2.0;
if (options.volume > 1.0) options.volume = 1.0;
which = Math.floor((Math.random() * 6) + 1);
if (which == 1) { Audio.playSound(clap1, options); }
else if (which == 2) { Audio.playSound(clap2, options); }
else if (which == 3) { Audio.playSound(clap3, options); }
else if (which == 4) { Audio.playSound(clap4, options); }
else if (which == 5) { Audio.playSound(clap5, options); }
else { Audio.playSound(clap6, options); }
Audio.playSound(clap, options);
clapping[palm] = true;
} else if (clapping[palm] && (speed < (CLAP_SPEED / 4.0))) {
clapping[palm] = false;
}
var frame = Math.floor(animationDetails.frameIndex);
if (frame != lastAnimFrame) {
print("frame " + frame);
lastAnimFrame = frame;
}
for (var i = 0; i < startEndFrames.length; i++) {
if (frame == startEndFrames[i].start && (frame != lastClapFrame)) {
playClap(1.0, Camera.getPosition());
lastClapFrame = frame;
}
}
var palm1Position = MyAvatar.getLeftPalmPosition();
var palm2Position = MyAvatar.getRightPalmPosition();
var distanceBetween = Vec3.length(Vec3.subtract(palm1Position, palm2Position));
var palm1Velocity = Controller.getSpatialControlVelocity(1);
var palm2Velocity = Controller.getSpatialControlVelocity(3);
var closingVelocity = Vec3.length(Vec3.subtract(palm1Velocity, palm2Velocity));
const CLAP_SPEED = 0.7;
const CLAP_DISTANCE = 0.15;
if ((closingVelocity > CLAP_SPEED) && (distanceBetween < CLAP_DISTANCE) && !clappingNow) {
var volume = closingVelocity / 2.0;
if (volume > 1.0) volume = 1.0;
playClap(volume, palm1Position);
clappingNow = true;
} else if (clappingNow && (distanceBetween > CLAP_DISTANCE * 1.2)) {
clappingNow = false;
}
}
function playClap(volume, position) {
var options = new AudioInjectionOptions();
options.position = position;
options.volume = 1.0;
var clip = Math.floor(Math.random() * numberOfSounds);
Audio.playSound(claps[clip], options);
}
function keepClapping() {
playClap(1.0, Camera.getPosition());
}
Controller.keyPressEvent.connect(function(event) {
if(event.text == "SHIFT") {
if (!clickClappingNow) {
playClap(1.0, Camera.getPosition());
var whichClip = Math.floor(Math.random() * startEndFrames.length);
lastClapFrame = 0;
MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false);
clickClappingNow = true;
} else {
clapRate *= 1.25;
MyAvatar.stopAnimation(clapAnimation);
MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false);
collectedClicks = collectedClicks + 1;
}
}
});
var CLAP_END_WAIT_MSECS = 500;
Controller.keyReleaseEvent.connect(function(event) {
if (event.text == "SHIFT") {
if (!startedTimer) {
startedTimer = true;
collectedClicks = 0;
Script.setTimeout(stopClapping, CLAP_END_WAIT_MSECS);
}
}
});
function stopClapping() {
if (collectedClicks == 0) {
startedTimer = false;
MyAvatar.stopAnimation(clapAnimation);
clapRate = CLAP_START_RATE;
clickClappingNow = false;
} else {
startedTimer = false;
}
}
// Connect a call back that happens every frame
Script.update.connect(maybePlaySound);
Script.update.connect(maybePlaySound);
//Controller.keyPressEvent.connect(keyPressEvent);

View file

@ -57,7 +57,7 @@ var LocationMenu = function(opts) {
y: 0,
width: menuWidth + 10,
height: (menuHeight * (pageSize + 1)) + 10,
color: { red: 0, green: 0, blue: 0},
backgroundColor: { red: 0, green: 0, blue: 0},
topMargin: 4,
leftMargin: 4,
text: "",
@ -71,7 +71,7 @@ var LocationMenu = function(opts) {
y: 0,
width: menuWidth,
height: menuHeight,
color: inactiveColor,
backgroundColor: inactiveColor,
topMargin: margin,
leftMargin: margin,
text: (i == 0) ? "Loading..." : "",
@ -85,7 +85,7 @@ var LocationMenu = function(opts) {
y: 0,
width: menuWidth / 2,
height: menuHeight,
color: disabledColor,
backgroundColor: disabledColor,
topMargin: margin,
leftMargin: margin,
text: "Previous",
@ -97,7 +97,7 @@ var LocationMenu = function(opts) {
y: 0,
width: menuWidth / 2,
height: menuHeight,
color: disabledColor,
backgroundColor: disabledColor,
topMargin: margin,
leftMargin: margin,
text: "Next",
@ -175,10 +175,10 @@ var LocationMenu = function(opts) {
if (start + i < this.locations.length) {
location = this.locations[start + i];
update.text = (start + i + 1) + ". " + location.username;
update.color = inactiveColor;
update.backgroundColor = inactiveColor;
} else {
update.text = "";
update.color = disabledColor;
update.backgroundColor = disabledColor;
}
Overlays.editOverlay(this.menuItems[i].overlay, update);
this.menuItems[i].location = location;
@ -187,8 +187,8 @@ var LocationMenu = function(opts) {
this.previousEnabled = pageNumber > 0;
this.nextEnabled = pageNumber < (this.numPages - 1);
Overlays.editOverlay(this.previousButton, { color: this.previousEnabled ? prevNextColor : disabledColor});
Overlays.editOverlay(this.nextButton, { color: this.nextEnabled ? prevNextColor : disabledColor });
Overlays.editOverlay(this.previousButton, { backgroundColor: this.previousEnabled ? prevNextColor : disabledColor});
Overlays.editOverlay(this.nextButton, { backgroundColor: this.nextEnabled ? prevNextColor : disabledColor });
}
this.mousePressEvent = function(event) {
@ -198,17 +198,17 @@ var LocationMenu = function(opts) {
self.toggleMenu();
} else if (clickedOverlay == self.previousButton) {
if (self.previousEnabled) {
Overlays.editOverlay(clickedOverlay, { color: activeColor });
Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor });
}
} else if (clickedOverlay == self.nextButton) {
if (self.nextEnabled) {
Overlays.editOverlay(clickedOverlay, { color: activeColor });
Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor });
}
} else {
for (var i = 0; i < self.menuItems.length; i++) {
if (clickedOverlay == self.menuItems[i].overlay) {
if (self.menuItems[i].location != null) {
Overlays.editOverlay(clickedOverlay, { color: activeColor });
Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor });
}
break;
}
@ -221,19 +221,19 @@ var LocationMenu = function(opts) {
if (clickedOverlay == self.previousButton) {
if (self.previousEnabled) {
Overlays.editOverlay(clickedOverlay, { color: inactiveColor });
Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor });
self.goToPage(self.page - 1);
}
} else if (clickedOverlay == self.nextButton) {
if (self.nextEnabled) {
Overlays.editOverlay(clickedOverlay, { color: inactiveColor });
Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor });
self.goToPage(self.page + 1);
}
} else {
for (var i = 0; i < self.menuItems.length; i++) {
if (clickedOverlay == self.menuItems[i].overlay) {
if (self.menuItems[i].location != null) {
Overlays.editOverlay(clickedOverlay, { color: inactiveColor });
Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor });
var location = self.menuItems[i].location;
Window.location = "hifi://" + location.domain + "/"
+ location.x + "," + location.y + "," + location.z;

View file

@ -18,13 +18,16 @@ var RIGHT = 1;
var lastLeftFrame = 0;
var lastRightFrame = 0;
var LAST_FRAME = 11.0; // What is the number of the last frame we want to use in the animation?
var SMOOTH_FACTOR = 0.80;
var leftDirection = true;
var rightDirection = true;
var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation?
var SMOOTH_FACTOR = 0.0;
var MAX_FRAMES = 30.0;
Script.update.connect(function(deltaTime) {
var leftTriggerValue = Math.sqrt(Controller.getTriggerValue(LEFT));
var rightTriggerValue = Math.sqrt(Controller.getTriggerValue(RIGHT));
var leftTriggerValue = Controller.getTriggerValue(LEFT);
var rightTriggerValue = Controller.getTriggerValue(RIGHT);
var leftFrame, rightFrame;
@ -32,10 +35,31 @@ Script.update.connect(function(deltaTime) {
leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR;
rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR;
if (!leftDirection) {
leftFrame = MAX_FRAMES - leftFrame;
}
if (!rightDirection) {
rightFrame = MAX_FRAMES - rightFrame;
}
if ((leftTriggerValue == 1.0) && (leftDirection == true)) {
leftDirection = false;
lastLeftFrame = MAX_FRAMES - leftFrame;
} else if ((leftTriggerValue == 0.0) && (leftDirection == false)) {
leftDirection = true;
lastLeftFrame = leftFrame;
}
if ((rightTriggerValue == 1.0) && (rightDirection == true)) {
rightDirection = false;
lastRightFrame = MAX_FRAMES - rightFrame;
} else if ((rightTriggerValue == 0.0) && (rightDirection == false)) {
rightDirection = true;
lastRightFrame = rightFrame;
}
if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){
MyAvatar.stopAnimation(leftHandAnimation);
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
}
if ((rightFrame != lastRightFrame) && rightHandAnimation.length) {
MyAvatar.stopAnimation(rightHandAnimation);

View file

@ -111,16 +111,6 @@ if (APPLE)
SET(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/interface.icns")
endif()
# RtMidi for scripted MIDI control
find_package(RtMidi)
if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI)
add_definitions(-DHAVE_RTMIDI)
include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR})
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${RTMIDI_CPP}")
endif ()
# create the executable, make it a bundle on OS X
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
@ -151,6 +141,7 @@ find_package(Sixense)
find_package(Visage)
find_package(ZLIB)
find_package(Qxmpp)
find_package(RtMidi)
# include the Sixense library for Razer Hydra if available
if (SIXENSE_FOUND AND NOT DISABLE_SIXENSE)
@ -223,11 +214,18 @@ if (QXMPP_FOUND AND NOT DISABLE_QXMPP)
target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}")
endif (QXMPP_FOUND AND NOT DISABLE_QXMPP)
# link CoreMIDI if we're using RtMidi
if (RTMIDI_FOUND AND APPLE)
find_library(CoreMIDI CoreMIDI)
add_definitions(-D__MACOSX_CORE__)
target_link_libraries(${TARGET_NAME} ${CoreMIDI})
# and with RtMidi for RtMidi control
if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI)
add_definitions(-DHAVE_RTMIDI)
include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR})
target_link_libraries(${TARGET_NAME} "${RTMIDI_LIBRARY}")
if (APPLE)
find_library(CoreMIDI CoreMIDI)
add_definitions(-D__MACOSX_CORE__)
target_link_libraries(${TARGET_NAME} ${CoreMIDI})
endif()
endif()
# include headers for interface and InterfaceConfig.

View file

@ -7,7 +7,9 @@ Stephen Birarda, June 30, 2014
2. Copy RtMidi.h to externals/rtmidi/include.
3. Copy RtMidi.cpp to externals/rtmidi/src
3. Compile the RtMidi library.
3. Copy either librtmidi.dylib (dynamic) or librtmidi.a (static) to externals/rtmidi/lib
4. Delete your build directory, run cmake and build, and you should be all set.

Binary file not shown.

View file

@ -316,12 +316,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
// Make sure cache on same thread than its parent (NetworkAccessManager)
QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->moveToThread(networkAccessManager.thread());
cache->setParent(&networkAccessManager);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
networkAccessManager.setCache(cache);

View file

@ -67,7 +67,7 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
_proceduralAudioOutput(NULL),
_proceduralOutputDevice(NULL),
_inputRingBuffer(0),
_ringBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO),
_ringBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, false, 100),
_isStereoInput(false),
_averagedLatency(0.0),
_measuredJitter(0),
@ -869,14 +869,16 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
_numFramesDisplayStarve = 10;
}
// if there is anything in the ring buffer, decide what to do
if (_ringBuffer.samplesAvailable() > 0) {
int numNetworkOutputSamples = _ringBuffer.samplesAvailable();
int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio;
QByteArray outputBuffer;
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
int numNetworkOutputSamples;
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableQAudioOutputOverflowCheck)) {
numNetworkOutputSamples = _ringBuffer.samplesAvailable();
} else {
int numSamplesAudioOutputRoomFor = _audioOutput->bytesFree() / sizeof(int16_t);
numNetworkOutputSamples = std::min(_ringBuffer.samplesAvailable(), (int)(numSamplesAudioOutputRoomFor * networkOutputToOutputRatio));
}
// if there is data in the ring buffer and room in the audio output, decide what to do
if (numNetworkOutputSamples > 0) {
int numSamplesNeededToStartPlayback = std::min(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2),
_ringBuffer.getSampleCapacity());
@ -885,6 +887,11 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
// We are still waiting for enough samples to begin playback
// qDebug() << numNetworkOutputSamples << " samples so far, waiting for " << numSamplesNeededToStartPlayback;
} else {
int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio;
QByteArray outputBuffer;
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
// We are either already playing back, or we have enough audio to start playing back.
//qDebug() << "pushing " << numNetworkOutputSamples;
_ringBuffer.setIsStarved(false);

35
interface/src/Hair.cpp Normal file
View file

@ -0,0 +1,35 @@
//
// Hair.cpp
// interface/src
//
// Created by Philip on June 26, 2014
// Copyright 2014 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
//
// Creates single flexible vertlet-integrated strands that can be used for hair/fur/grass
#include "Hair.h"
#include "Util.h"
#include "world.h"
Hair::Hair() {
qDebug() << "Creating Hair";
}
void Hair::simulate(float deltaTime) {
}
void Hair::render() {
//
// Before calling this function, translate/rotate to the origin of the owning object
glPushMatrix();
glColor3f(1.0f, 1.0f, 0.0f);
glutSolidSphere(1.0f, 15, 15);
glPopMatrix();
}

35
interface/src/Hair.h Normal file
View file

@ -0,0 +1,35 @@
//
// Hair.h
// interface/src
//
// Created by Philip on June 26, 2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Hair_h
#define hifi_Hair_h
#include <iostream>
#include <glm/glm.hpp>
#include <SharedUtil.h>
#include "GeometryUtil.h"
#include "InterfaceConfig.h"
#include "Util.h"
class Hair {
public:
Hair();
void simulate(float deltaTime);
void render();
private:
};
#endif // hifi_Hair_h

View file

@ -575,6 +575,8 @@ Menu::Menu() :
Qt::CTRL | Qt::SHIFT | Qt::Key_U,
false);
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::DisableQAudioOutputOverflowCheck, 0, false);
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
this,
@ -1006,7 +1008,6 @@ void Menu::goToDomainDialog() {
domainDialog.setWindowTitle("Go to Domain");
domainDialog.setLabelText("Domain server:");
domainDialog.setTextValue(currentDomainHostname);
domainDialog.setWindowFlags(Qt::Sheet);
domainDialog.resize(domainDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, domainDialog.size().height());
int dialogReturn = domainDialog.exec();
@ -1044,7 +1045,6 @@ void Menu::goTo() {
QString destination = QString();
gotoDialog.setTextValue(destination);
gotoDialog.setWindowFlags(Qt::Sheet);
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
int dialogReturn = gotoDialog.exec();
@ -1160,7 +1160,6 @@ void Menu::goToLocation() {
coordinateDialog.setWindowTitle("Go to Location");
coordinateDialog.setLabelText("Coordinate as x,y,z:");
coordinateDialog.setTextValue(currentLocation);
coordinateDialog.setWindowFlags(Qt::Sheet);
coordinateDialog.resize(coordinateDialog.parentWidget()->size().width() * 0.30, coordinateDialog.size().height());
int dialogReturn = coordinateDialog.exec();
@ -1225,7 +1224,6 @@ void Menu::nameLocation() {
"(wherever you are standing and looking now) as you.\n\n"
"Location name:");
nameDialog.setWindowFlags(Qt::Sheet);
nameDialog.resize((int) (nameDialog.parentWidget()->size().width() * 0.30), nameDialog.size().height());
if (nameDialog.exec() == QDialog::Accepted) {

View file

@ -343,6 +343,7 @@ namespace MenuOption {
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisableNackPackets = "Disable NACK Packets";
const QString DisableQAudioOutputOverflowCheck = "Disable QAudioOutput Overflow Check";
const QString DisplayFrustum = "Display Frustum";
const QString DisplayHands = "Display Hands";
const QString DisplayHandTargets = "Display Hand Targets";

View file

@ -143,6 +143,11 @@ void Avatar::simulate(float deltaTime) {
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
simulateHair(deltaTime);
}
foreach (Hair* hair, _hairs) {
hair->simulate(deltaTime);
}
}
// update position by velocity, and subtract the change added earlier for gravity
@ -380,6 +385,9 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
getHead()->render(1.0f, modelRenderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
renderHair();
foreach (Hair* hair, _hairs) {
hair->render();
}
}
}

View file

@ -19,6 +19,7 @@
#include <AvatarData.h>
#include "Hair.h"
#include "Hand.h"
#include "Head.h"
#include "InterfaceConfig.h"
@ -159,6 +160,7 @@ signals:
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
protected:
QVector<Hair*> _hairs;
SkeletonModel _skeletonModel;
QVector<Model*> _attachmentModels;
float _bodyYawDelta;

View file

@ -199,6 +199,9 @@ void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate");
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
simulateHair(deltaTime);
foreach (Hair* hair, _hairs) {
hair->simulate(deltaTime);
}
}
}
@ -896,6 +899,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
getHead()->render(1.0f, modelRenderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
renderHair();
foreach (Hair* hair, _hairs) {
hair->render();
}
}
}
getHand()->render(true, modelRenderMode);

View file

@ -13,14 +13,13 @@
#include "MIDIManager.h"
#ifdef HAVE_RTMIDI
MIDIManager& MIDIManager::getInstance() {
static MIDIManager sharedInstance;
return sharedInstance;
}
void MIDIManager::midiCallback(double deltaTime, std::vector<unsigned char>* message, void* userData) {
#ifdef HAVE_RTMIDI
MIDIEvent callbackEvent;
callbackEvent.deltaTime = deltaTime;
@ -36,15 +35,19 @@ void MIDIManager::midiCallback(double deltaTime, std::vector<unsigned char>* mes
}
emit getInstance().midiEvent(callbackEvent);
#endif
}
MIDIManager::~MIDIManager() {
#ifdef HAVE_RTMIDI
delete _midiInput;
#endif
}
const int DEFAULT_MIDI_PORT = 0;
void MIDIManager::openDefaultPort() {
#ifdef HAVE_RTMIDI
if (!_midiInput) {
_midiInput = new RtMidiIn();
@ -63,6 +66,5 @@ void MIDIManager::openDefaultPort() {
_midiInput = NULL;
}
}
}
#endif
#endif
}

View file

@ -12,14 +12,14 @@
#ifndef hifi_MIDIManager_h
#define hifi_MIDIManager_h
#ifdef HAVE_RTMIDI
#include <QtCore/QObject>
#include <QtScript/QScriptEngine>
#include <MIDIEvent.h>
#ifdef HAVE_RTMIDI
#include <RtMidi.h>
#endif
class MIDIManager : public QObject {
Q_OBJECT
@ -36,7 +36,9 @@ public:
~MIDIManager();
void openDefaultPort();
#ifdef HAVE_RTMIDI
bool hasDevice() const { return !!_midiInput; }
#endif
public slots:
unsigned int NoteOn() const { return 144; }
unsigned int NoteOff() const { return 128; }
@ -46,10 +48,10 @@ signals:
void midiEvent(const MIDIEvent& event);
private:
#ifdef HAVE_RTMIDI
RtMidiIn* _midiInput;
};
#endif
};
#endif // hifi_MIDIManager_h

View file

@ -22,7 +22,7 @@
Overlay::Overlay() :
_parent(NULL),
_alpha(DEFAULT_ALPHA),
_color(DEFAULT_BACKGROUND_COLOR),
_color(DEFAULT_OVERLAY_COLOR),
_visible(true),
_anchor(NO_ANCHOR)
{

View file

@ -21,7 +21,7 @@
#include <SharedUtil.h> // for xColor
const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 };
const xColor DEFAULT_OVERLAY_COLOR = { 255, 255, 255 };
const float DEFAULT_ALPHA = 0.7f;
class Overlay : public QObject {

View file

@ -18,6 +18,7 @@
#include "ui/TextRenderer.h"
TextOverlay::TextOverlay() :
_backgroundColor(DEFAULT_BACKGROUND_COLOR),
_leftMargin(DEFAULT_MARGIN),
_topMargin(DEFAULT_MARGIN),
_fontSize(DEFAULT_FONTSIZE)
@ -33,7 +34,7 @@ void TextOverlay::render() {
}
const float MAX_COLOR = 255;
glColor4f(0 / MAX_COLOR, 0 / MAX_COLOR, 0 / MAX_COLOR, _alpha);
glColor4f(_backgroundColor.red / MAX_COLOR, _backgroundColor.green / MAX_COLOR, _backgroundColor.blue / MAX_COLOR, _alpha);
glBegin(GL_QUADS);
glVertex2f(_bounds.left(), _bounds.top());
@ -82,6 +83,18 @@ void TextOverlay::setProperties(const QScriptValue& properties) {
setText(text.toVariant().toString());
}
QScriptValue backgroundColor = properties.property("backgroundColor");
if (backgroundColor.isValid()) {
QScriptValue red = backgroundColor.property("red");
QScriptValue green = backgroundColor.property("green");
QScriptValue blue = backgroundColor.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
_backgroundColor.red = red.toVariant().toInt();
_backgroundColor.green = green.toVariant().toInt();
_backgroundColor.blue = blue.toVariant().toInt();
}
}
if (properties.property("leftMargin").isValid()) {
setLeftMargin(properties.property("leftMargin").toVariant().toInt());
}

View file

@ -27,6 +27,7 @@
#include "Overlay.h"
#include "Overlay2D.h"
const xColor DEFAULT_BACKGROUND_COLOR = { 0, 0, 0 };
const int DEFAULT_MARGIN = 10;
const int DEFAULT_FONTSIZE = 11;
@ -54,6 +55,7 @@ public:
private:
QString _text;
xColor _backgroundColor;
int _leftMargin;
int _topMargin;
int _fontSize;

View file

@ -499,17 +499,7 @@ void VoxelSystem::initVoxelMemory() {
_memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels);
// create our simple fragment shader if we're the first system to init
if (!_perlinModulateProgram.isLinked()) {
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath()
+ "shaders/perlin_modulate.vert");
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath()
+ "shaders/perlin_modulate.frag");
_perlinModulateProgram.link();
_perlinModulateProgram.bind();
_perlinModulateProgram.setUniformValue("permutationNormalTexture", 0);
_perlinModulateProgram.release();
if (!_shadowMapProgram.isLinked()) {
_shadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
Application::resourcesPath() + "shaders/shadow_map.vert");
_shadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
@ -1509,7 +1499,7 @@ void VoxelSystem::applyScaleAndBindProgram(bool texture) {
glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID());
} else if (texture) {
_perlinModulateProgram.bind();
bindPerlinModulateProgram();
glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPermutationNormalTextureID());
}
@ -2159,6 +2149,22 @@ unsigned long VoxelSystem::getVoxelMemoryUsageGPU() {
return (_initialMemoryUsageGPU - currentFreeMemory);
}
void VoxelSystem::bindPerlinModulateProgram() {
if (!_perlinModulateProgram.isLinked()) {
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex,
Application::resourcesPath() + "shaders/perlin_modulate.vert");
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment,
Application::resourcesPath() + "shaders/perlin_modulate.frag");
_perlinModulateProgram.link();
_perlinModulateProgram.bind();
_perlinModulateProgram.setUniformValue("permutationNormalTexture", 0);
} else {
_perlinModulateProgram.bind();
}
}
// Swizzle value of bit pairs of the value of index
unsigned short VoxelSystem::_sSwizzledOcclusionBits[64] = {
0x0000, // 00000000

View file

@ -236,6 +236,8 @@ private:
static ProgramObject _cascadedShadowMapProgram;
static int _shadowDistancesLocation;
static void bindPerlinModulateProgram();
int _hookID;
std::vector<glBufferIndex> _freeIndexes;
QMutex _freeIndexLock;

View file

@ -99,7 +99,8 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::
_listenerUnattenuatedZone(NULL),
_desiredJitterBufferFrames(1),
_currentJitterBufferFrames(-1),
_dynamicJitterBuffers(dynamicJitterBuffers)
_dynamicJitterBuffers(dynamicJitterBuffers),
_consecutiveNotMixedCount(0)
{
}
@ -129,7 +130,7 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
numSilentSamples = getSamplesPerFrame();
if (numSilentSamples > 0) {
if (_currentJitterBufferFrames > _desiredJitterBufferFrames) {
if (_dynamicJitterBuffers && _currentJitterBufferFrames > _desiredJitterBufferFrames) {
// our current jitter buffer size exceeds its desired value, so ignore some silent
// frames to get that size as close to desired as possible
int samplesPerFrame = getSamplesPerFrame();
@ -206,11 +207,12 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
// if the buffer was starved, allow it to accrue at least the desired number of
// jitter buffer frames before we start taking frames from it for mixing
if (_shouldOutputStarveDebug) {
_shouldOutputStarveDebug = false;
}
_consecutiveNotMixedCount++;
return false;
} else if (samplesAvailable() < samplesPerFrame) {
// if the buffer doesn't have a full frame of samples to take for mixing, it is starved
@ -222,6 +224,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
// reset our _shouldOutputStarveDebug to true so the next is printed
_shouldOutputStarveDebug = true;
_consecutiveNotMixedCount++;
return false;
}
@ -231,6 +234,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
// minus one (since a frame will be read immediately after this) is the length of the jitter buffer
_currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1;
_isStarved = false;
_consecutiveNotMixedCount = 0;
}
// since we've read data from ring buffer at least once - we've started

View file

@ -83,6 +83,8 @@ public:
int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; }
int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; }
int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; }
protected:
// disallow copying of PositionalAudioRingBuffer objects
PositionalAudioRingBuffer(const PositionalAudioRingBuffer&);
@ -107,9 +109,7 @@ protected:
bool _dynamicJitterBuffers;
// extra stats
int _starveCount;
int _silentFramesDropped;
int _consecutiveNotMixedCount;
};
#endif // hifi_PositionalAudioRingBuffer_h

View file

@ -274,6 +274,26 @@ void Bitstream::persistAndResetReadMappings() {
persistReadMappings(getAndResetReadMappings());
}
void Bitstream::copyPersistentMappings(const Bitstream& other) {
_objectStreamerStreamer.copyPersistentMappings(other._objectStreamerStreamer);
_typeStreamerStreamer.copyPersistentMappings(other._typeStreamerStreamer);
_attributeStreamer.copyPersistentMappings(other._attributeStreamer);
_scriptStringStreamer.copyPersistentMappings(other._scriptStringStreamer);
_sharedObjectStreamer.copyPersistentMappings(other._sharedObjectStreamer);
_sharedObjectReferences = other._sharedObjectReferences;
_weakSharedObjectHash = other._weakSharedObjectHash;
}
void Bitstream::clearPersistentMappings() {
_objectStreamerStreamer.clearPersistentMappings();
_typeStreamerStreamer.clearPersistentMappings();
_attributeStreamer.clearPersistentMappings();
_scriptStringStreamer.clearPersistentMappings();
_sharedObjectStreamer.clearPersistentMappings();
_sharedObjectReferences.clear();
_weakSharedObjectHash.clear();
}
void Bitstream::clearSharedObject(int id) {
SharedObjectPointer object = _sharedObjectStreamer.takePersistentValue(id);
if (object) {
@ -1122,7 +1142,7 @@ Bitstream& Bitstream::operator>(ObjectStreamerPointer& streamer) {
}
if (_metadataType == NO_METADATA) {
if (!metaObject) {
qWarning() << "Unknown class name:" << className;
throw BitstreamException(QString("Unknown class name: ") + className);
}
return *this;
}
@ -1232,7 +1252,7 @@ Bitstream& Bitstream::operator>(TypeStreamerPointer& streamer) {
}
if (_metadataType == NO_METADATA) {
if (!baseStreamer) {
qWarning() << "Unknown type name:" << typeName;
throw BitstreamException(QString("Unknown type name: ") + typeName);
}
return *this;
}
@ -1240,7 +1260,7 @@ Bitstream& Bitstream::operator>(TypeStreamerPointer& streamer) {
*this >> category;
if (category == TypeStreamer::SIMPLE_CATEGORY) {
if (!streamer) {
qWarning() << "Unknown type name:" << typeName;
throw BitstreamException(QString("Unknown type name: ") + typeName);
}
return *this;
}
@ -1441,7 +1461,7 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
_objectStreamerStreamer >> objectStreamer;
if (delta) {
if (!reference) {
qWarning() << "Delta without reference" << id << originID;
throw BitstreamException(QString("Delta without reference [id=%1, originID=%2]").arg(id).arg(originID));
}
objectStreamer->readRawDelta(*this, reference.data(), pointer.data());
} else {
@ -1451,7 +1471,7 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
QObject* rawObject;
if (delta) {
if (!reference) {
qWarning() << "Delta without reference" << id << originID;
throw BitstreamException(QString("Delta without reference [id=%1, originID=%2]").arg(id).arg(originID));
}
readRawDelta(rawObject, (const QObject*)reference.data());
} else {
@ -1682,6 +1702,10 @@ const TypeStreamer* Bitstream::createInvalidTypeStreamer() {
return streamer;
}
BitstreamException::BitstreamException(const QString& description) :
_description(description) {
}
QJsonValue JSONWriter::getData(bool value) {
return value;
}

View file

@ -102,6 +102,9 @@ public:
V takePersistentValue(int id) { V value = _persistentValues.take(id); _valueIDs.remove(value); return value; }
void copyPersistentMappings(const RepeatedValueStreamer& other);
void clearPersistentMappings();
RepeatedValueStreamer& operator<<(K value);
RepeatedValueStreamer& operator>>(V& value);
@ -199,6 +202,29 @@ template<class K, class P, class V> inline RepeatedValueStreamer<K, P, V>&
return *this;
}
template<class K, class P, class V> inline void RepeatedValueStreamer<K, P, V>::copyPersistentMappings(
const RepeatedValueStreamer<K, P, V>& other) {
_lastPersistentID = other._lastPersistentID;
_idStreamer.setBitsFromValue(_lastPersistentID);
_persistentIDs = other._persistentIDs;
_transientOffsets.clear();
_lastTransientOffset = 0;
_persistentValues = other._persistentValues;
_transientValues.clear();
_valueIDs = other._valueIDs;
}
template<class K, class P, class V> inline void RepeatedValueStreamer<K, P, V>::clearPersistentMappings() {
_lastPersistentID = 0;
_idStreamer.setBitsFromValue(_lastPersistentID);
_persistentIDs.clear();
_transientOffsets.clear();
_lastTransientOffset = 0;
_persistentValues.clear();
_transientValues.clear();
_valueIDs.clear();
}
/// A stream for bit-aligned data. Through a combination of code generation, reflection, macros, and templates, provides a
/// serialization mechanism that may be used for both networking and persistent storage. For unreliable networking, the
/// class provides a mapping system that resends mappings for ids until they are acknowledged (and thus persisted). For
@ -303,6 +329,9 @@ public:
Bitstream(QDataStream& underlying, MetadataType metadataType = NO_METADATA,
GenericsMode = NO_GENERICS, QObject* parent = NULL);
/// Returns a reference to the underlying data stream.
QDataStream& getUnderlying() { return _underlying; }
/// Substitutes the supplied metaobject for the given class name's default mapping. This is mostly useful for testing the
/// process of mapping between different types, but may in the future be used for permanently renaming classes.
void addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject);
@ -347,6 +376,12 @@ public:
/// Immediately persists and resets the read mappings.
void persistAndResetReadMappings();
/// Copies the persistent mappings from the specified other stream.
void copyPersistentMappings(const Bitstream& other);
/// Clears the persistent mappings for this stream.
void clearPersistentMappings();
/// Returns a reference to the weak hash storing shared objects for this stream.
const WeakSharedObjectHash& getWeakSharedObjectHash() const { return _weakSharedObjectHash; }
@ -823,6 +858,19 @@ template<class K, class V> inline Bitstream& Bitstream::operator>>(QHash<K, V>&
return *this;
}
/// Thrown for unrecoverable errors.
class BitstreamException {
public:
BitstreamException(const QString& description);
const QString& getDescription() const { return _description; }
private:
QString _description;
};
/// Provides a means of writing Bitstream-able data to JSON rather than the usual binary format in a manner that allows it to
/// be manipulated and re-read, converted to binary, etc. To use, create a JSONWriter, stream values in using the << operator,
/// and call getDocument to obtain the JSON data.

View file

@ -113,17 +113,16 @@ Bitstream& DatagramSequencer::startPacket() {
_outgoingPacketStream << (quint32)record.packetNumber;
}
// write the high-priority messages
_outgoingPacketStream << (quint32)_highPriorityMessages.size();
foreach (const HighPriorityMessage& message, _highPriorityMessages) {
_outputStream << message.data;
}
// return the stream, allowing the caller to write the rest
return _outputStream;
}
void DatagramSequencer::endPacket() {
// write the high-priority messages
_outputStream << _highPriorityMessages.size();
foreach (const HighPriorityMessage& message, _highPriorityMessages) {
_outputStream << message.data;
}
_outputStream.flush();
// if we have space remaining, send some data from our reliable channels
@ -222,22 +221,22 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
_sendRecords.erase(_sendRecords.begin(), it + 1);
}
// alert external parties so that they can read the middle
emit readyToRead(_inputStream);
// read and dispatch the high-priority messages
quint32 highPriorityMessageCount;
_incomingPacketStream >> highPriorityMessageCount;
int highPriorityMessageCount;
_inputStream >> highPriorityMessageCount;
int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages;
for (quint32 i = 0; i < highPriorityMessageCount; i++) {
for (int i = 0; i < highPriorityMessageCount; i++) {
QVariant data;
_inputStream >> data;
if ((int)i >= _receivedHighPriorityMessages) {
if (i >= _receivedHighPriorityMessages) {
emit receivedHighPriorityMessage(data);
}
}
_receivedHighPriorityMessages = highPriorityMessageCount;
// alert external parties so that they can read the middle
emit readyToRead(_inputStream);
// read the reliable data, if any
quint32 reliableChannels;
_incomingPacketStream >> reliableChannels;
@ -253,6 +252,8 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
// record the receipt
ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings(), newHighPriorityMessages };
_receiveRecords.append(record);
emit receiveRecorded();
}
void DatagramSequencer::sendClearSharedObjectMessage(int id) {
@ -274,6 +275,11 @@ void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
}
}
void DatagramSequencer::clearReliableChannel(QObject* object) {
ReliableChannel* channel = static_cast<ReliableChannel*>(object);
(channel->isOutput() ? _reliableOutputChannels : _reliableInputChannels).remove(channel->getIndex());
}
void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
// stop acknowledging the recorded packets
while (!_receiveRecords.isEmpty() && _receiveRecords.first().packetNumber <= record.lastReceivedPacketNumber) {
@ -295,7 +301,10 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
// acknowledge the received spans
foreach (const ChannelSpan& span, record.spans) {
getReliableOutputChannel(span.channel)->spanAcknowledged(span);
ReliableChannel* channel = _reliableOutputChannels.value(span.channel);
if (channel) {
channel->spanAcknowledged(span);
}
}
// increase the packet rate with every ack until we pass the slow start threshold; then, every round trip
@ -310,7 +319,10 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
void DatagramSequencer::sendRecordLost(const SendRecord& record) {
// notify the channels of their lost spans
foreach (const ChannelSpan& span, record.spans) {
getReliableOutputChannel(span.channel)->spanLost(record.packetNumber, _outgoingPacketNumber + 1);
ReliableChannel* channel = _reliableOutputChannels.value(span.channel);
if (channel) {
channel->spanLost(record.packetNumber, _outgoingPacketNumber + 1);
}
}
// halve the rate and remember as threshold
@ -364,6 +376,8 @@ void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector<Chann
_outputStream.getAndResetWriteMappings(), spans };
_sendRecords.append(record);
emit sendRecorded();
// write the sequence number and size, which are the same between all fragments
_outgoingDatagramBuffer.seek(_datagramHeaderSize);
_outgoingDatagramStream << (quint32)_outgoingPacketNumber;
@ -658,16 +672,24 @@ int ReliableChannel::getBytesAvailable() const {
return _buffer.size() - _acknowledged.getTotalSet();
}
void ReliableChannel::sendMessage(const QVariant& message) {
// write a placeholder for the length, then fill it in when we know what it is
int placeholder = _buffer.pos();
_dataStream << (quint32)0;
_bitstream << message;
void ReliableChannel::startMessage() {
// write a placeholder for the length; we'll fill it in when we know what it is
_messageLengthPlaceholder = _buffer.pos();
_dataStream << (quint32)0;
}
void ReliableChannel::endMessage() {
_bitstream.flush();
_bitstream.persistAndResetWriteMappings();
quint32 length = _buffer.pos() - placeholder;
_buffer.writeBytes(placeholder, sizeof(quint32), (const char*)&length);
quint32 length = _buffer.pos() - _messageLengthPlaceholder;
_buffer.writeBytes(_messageLengthPlaceholder, sizeof(quint32), (const char*)&length);
}
void ReliableChannel::sendMessage(const QVariant& message) {
startMessage();
_bitstream << message;
endMessage();
}
void ReliableChannel::sendClearSharedObjectMessage(int id) {
@ -675,7 +697,7 @@ void ReliableChannel::sendClearSharedObjectMessage(int id) {
sendMessage(QVariant::fromValue(message));
}
void ReliableChannel::handleMessage(const QVariant& message) {
void ReliableChannel::handleMessage(const QVariant& message, Bitstream& in) {
if (message.userType() == ClearSharedObjectMessage::Type) {
_bitstream.clearSharedObject(message.value<ClearSharedObjectMessage>().id);
@ -688,6 +710,7 @@ void ReliableChannel::handleMessage(const QVariant& message) {
ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) :
QObject(sequencer),
_index(index),
_output(output),
_dataStream(&_buffer),
_bitstream(_dataStream),
_priority(1.0f),
@ -700,7 +723,9 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
_dataStream.setByteOrder(QDataStream::LittleEndian);
connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
connect(this, SIGNAL(receivedMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
connect(this, SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&)));
sequencer->connect(this, SIGNAL(destroyed(QObject*)), SLOT(clearReliableChannel(QObject*)));
}
void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
@ -843,9 +868,9 @@ void ReliableChannel::readData(QDataStream& in) {
_dataStream.skipRawData(sizeof(quint32));
QVariant message;
_bitstream >> message;
emit receivedMessage(message, _bitstream);
_bitstream.reset();
_bitstream.persistAndResetReadMappings();
emit receivedMessage(message);
continue;
}
}

View file

@ -78,6 +78,15 @@ public:
/// Returns the packet number of the last packet received (or the packet currently being assembled).
int getIncomingPacketNumber() const { return _incomingPacketNumber; }
/// Returns a reference to the stream used to read packets.
Bitstream& getInputStream() { return _inputStream; }
/// Returns a reference to the stream used to write packets.
Bitstream& getOutputStream() { return _outputStream; }
/// Returns a reference to the outgoing packet data.
const QByteArray& getOutgoingPacketData() const { return _outgoingPacketData; }
/// Returns the packet number of the sent packet at the specified index.
int getSentPacketNumber(int index) const { return _sendRecords.at(index).packetNumber; }
@ -126,9 +135,15 @@ signals:
/// Emitted when a packet is available to read.
void readyToRead(Bitstream& input);
/// Emitted when we've received a high-priority message
/// Emitted when we've received a high-priority message.
void receivedHighPriorityMessage(const QVariant& data);
/// Emitted when we've recorded the transmission of a packet.
void sendRecorded();
/// Emitted when we've recorded the receipt of a packet (that is, at the end of packet processing).
void receiveRecorded();
/// Emitted when a sent packet has been acknowledged by the remote side.
/// \param index the index of the packet in our list of send records
void sendAcknowledged(int index);
@ -141,6 +156,7 @@ private slots:
void sendClearSharedObjectMessage(int id);
void handleHighPriorityMessage(const QVariant& data);
void clearReliableChannel(QObject* object);
private:
@ -319,6 +335,9 @@ public:
/// Returns the channel's index in the sequencer's channel map.
int getIndex() const { return _index; }
/// Checks whether this is an output channel.
bool isOutput() const { return _output; }
/// Returns a reference to the buffer used to write/read data to/from this channel.
CircularBuffer& getBuffer() { return _buffer; }
@ -336,22 +355,36 @@ public:
/// Returns the number of bytes available to read from this channel.
int getBytesAvailable() const;
/// Returns the offset, which represents the total number of bytes acknowledged
/// (on the write end) or received completely (on the read end).
int getOffset() const { return _offset; }
/// Returns the total number of bytes written to this channel.
int getBytesWritten() const { return _offset + _buffer.pos(); }
/// Sets whether we expect to write/read framed messages.
void setMessagesEnabled(bool enabled) { _messagesEnabled = enabled; }
bool getMessagesEnabled() const { return _messagesEnabled; }
/// Sends a framed message on this channel.
/// Starts a framed message on this channel.
void startMessage();
/// Ends a framed message on this channel.
void endMessage();
/// Sends a framed message on this channel (convenience function that calls startMessage,
/// writes the message to the bitstream, then calls endMessage).
void sendMessage(const QVariant& message);
signals:
/// Fired when a framed message has been received on this channel.
void receivedMessage(const QVariant& message);
void receivedMessage(const QVariant& message, Bitstream& in);
private slots:
void sendClearSharedObjectMessage(int id);
void handleMessage(const QVariant& message);
void handleMessage(const QVariant& message, Bitstream& in);
private:
@ -370,6 +403,7 @@ private:
void readData(QDataStream& in);
int _index;
bool _output;
CircularBuffer _buffer;
CircularBuffer _assemblyBuffer;
QDataStream _dataStream;
@ -381,6 +415,7 @@ private:
int _writePositionResetPacketNumber;
SpanList _acknowledged;
bool _messagesEnabled;
int _messageLengthPlaceholder;
};
#endif // hifi_DatagramSequencer_h

View file

@ -19,6 +19,8 @@ Endpoint::Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendReco
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
connect(&_sequencer, SIGNAL(sendRecorded()), SLOT(recordSend()));
connect(&_sequencer, SIGNAL(receiveRecorded()), SLOT(recordReceive()));
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
@ -40,9 +42,6 @@ void Endpoint::update() {
Bitstream& out = _sequencer.startPacket();
writeUpdateMessage(out);
_sequencer.endPacket();
// record the send
_sendRecords.append(maybeCreateSendRecord());
}
int Endpoint::parseData(const QByteArray& packet) {
@ -59,8 +58,21 @@ void Endpoint::readMessage(Bitstream& in) {
QVariant message;
in >> message;
handleMessage(message, in);
// record the receipt
}
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
if (message.userType() == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
}
}
}
void Endpoint::recordSend() {
_sendRecords.append(maybeCreateSendRecord());
}
void Endpoint::recordReceive() {
_receiveRecords.append(maybeCreateReceiveRecord());
}
@ -84,14 +96,6 @@ void Endpoint::writeUpdateMessage(Bitstream& out) {
out << QVariant();
}
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
if (message.userType() == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
}
}
}
PacketRecord* Endpoint::maybeCreateSendRecord() const {
return NULL;
}

View file

@ -24,6 +24,9 @@ class Endpoint : public NodeData {
Q_OBJECT
public:
/// The index of the input/output channel used to transmit reliable deltas.
static const int RELIABLE_DELTA_CHANNEL_INDEX = 1;
Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord = NULL,
PacketRecord* baselineReceiveRecord = NULL);
@ -37,6 +40,10 @@ protected slots:
virtual void sendDatagram(const QByteArray& data);
virtual void readMessage(Bitstream& in);
virtual void handleMessage(const QVariant& message, Bitstream& in);
void recordSend();
void recordReceive();
void clearSendRecordsBefore(int index);
void clearReceiveRecordsBefore(int index);
@ -44,7 +51,6 @@ protected slots:
protected:
virtual void writeUpdateMessage(Bitstream& out);
virtual void handleMessage(const QVariant& message, Bitstream& in);
virtual PacketRecord* maybeCreateSendRecord() const;
virtual PacketRecord* maybeCreateReceiveRecord() const;

View file

@ -86,7 +86,11 @@ void MetavoxelClientManager::updateClient(MetavoxelClient* client) {
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) :
Endpoint(node, new PacketRecord(), new PacketRecord()),
_manager(manager) {
_manager(manager),
_reliableDeltaChannel(NULL) {
connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX),
SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&)));
}
void MetavoxelClient::guide(MetavoxelVisitor& visitor) {
@ -112,31 +116,44 @@ void MetavoxelClient::writeUpdateMessage(Bitstream& out) {
out << QVariant::fromValue(state);
}
void MetavoxelClient::readMessage(Bitstream& in) {
Endpoint::readMessage(in);
// reapply local edits
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
if (message.data.userType() == MetavoxelEditMessage::Type) {
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
}
}
}
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
if (message.userType() == MetavoxelDeltaMessage::Type) {
int userType = message.userType();
if (userType == MetavoxelDeltaMessage::Type) {
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
_data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD());
if (_reliableDeltaChannel) {
_remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _remoteDataLOD = _reliableDeltaLOD);
_sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings());
in.clearPersistentMappings();
_reliableDeltaChannel = NULL;
} else {
_remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in,
_remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD());
in.reset();
}
// copy to local and reapply local edits
_data = _remoteData;
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
if (message.data.userType() == MetavoxelEditMessage::Type) {
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
}
}
} else if (userType == MetavoxelDeltaPendingMessage::Type) {
if (!_reliableDeltaChannel) {
_reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
_reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream());
_reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD();
}
} else {
Endpoint::handleMessage(message, in);
}
}
PacketRecord* MetavoxelClient::maybeCreateSendRecord() const {
return new PacketRecord(_manager->getLOD());
return new PacketRecord(_reliableDeltaChannel ? _reliableDeltaLOD : _manager->getLOD());
}
PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const {
return new PacketRecord(getLastAcknowledgedSendRecord()->getLOD(), _data);
return new PacketRecord(_remoteDataLOD, _remoteData);
}

View file

@ -60,7 +60,6 @@ public:
protected:
virtual void writeUpdateMessage(Bitstream& out);
virtual void readMessage(Bitstream& in);
virtual void handleMessage(const QVariant& message, Bitstream& in);
virtual PacketRecord* maybeCreateSendRecord() const;
@ -70,6 +69,11 @@ private:
MetavoxelClientManager* _manager;
MetavoxelData _data;
MetavoxelData _remoteData;
MetavoxelLOD _remoteDataLOD;
ReliableChannel* _reliableDeltaChannel;
MetavoxelLOD _reliableDeltaLOD;
};
#endif // hifi_MetavoxelClientManager_h

View file

@ -61,6 +61,13 @@ class MetavoxelDeltaMessage {
DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaMessage)
/// A message indicating that metavoxel delta information is being sent on a reliable channel.
class MetavoxelDeltaPendingMessage {
STREAMABLE
};
DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaPendingMessage)
/// A simple streamable edit.
class MetavoxelEditMessage {
STREAMABLE

View file

@ -9,153 +9,19 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QMetaObject>
#include <QAbstractNetworkCache>
#include <QThread>
#include <QThreadStorage>
#include "NetworkAccessManager.h"
QThreadStorage<NetworkAccessManager*> networkAccessManagers;
NetworkAccessManager& NetworkAccessManager::getInstance() {
static NetworkAccessManager sharedInstance;
return sharedInstance;
if (!networkAccessManagers.hasLocalData()) {
networkAccessManagers.setLocalData(new NetworkAccessManager());
}
return *networkAccessManagers.localData();
}
NetworkAccessManager::NetworkAccessManager() {
}
QNetworkReply* NetworkAccessManager::get(const QNetworkRequest& request) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"get",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request));
return result;
}
return QNetworkAccessManager::get(request);
}
QNetworkReply* NetworkAccessManager::head(const QNetworkRequest& request) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"head",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request));
return result;
}
return QNetworkAccessManager::head(request);
}
QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, QIODevice* data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"post",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QIODevice*, data));
return result;
}
return QNetworkAccessManager::post(request, data);
}
QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, const QByteArray& data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"post",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(const QByteArray, data));
return result;
}
return QNetworkAccessManager::post(request, data);
}
QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, QHttpMultiPart* multiPart) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"post",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QHttpMultiPart*, multiPart));
return result;
}
return QNetworkAccessManager::post(request, multiPart);
}
QNetworkReply* NetworkAccessManager::put(const QNetworkRequest& request, QIODevice* data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"put",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QIODevice*, data));
return result;
}
return QNetworkAccessManager::put(request, data);
}
QNetworkReply* NetworkAccessManager::put(const QNetworkRequest& request, QHttpMultiPart* multiPart) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"put",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QHttpMultiPart*, multiPart));
return result;
}
return QNetworkAccessManager::put(request, multiPart);
}
QNetworkReply* NetworkAccessManager::put(const QNetworkRequest & request, const QByteArray & data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"put",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(const QByteArray, data));
return result;
}
return QNetworkAccessManager::put(request, data);
}
QNetworkReply* NetworkAccessManager::sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, QIODevice* data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"sendCustomRequest",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(const QByteArray, verb),
Q_ARG(QIODevice*, data));
return result;
}
return QNetworkAccessManager::sendCustomRequest(request, verb, data);
}
void NetworkAccessManager::setCache(QAbstractNetworkCache* cache) {
if (QThread::currentThread() != thread()) {
qRegisterMetaType<QAbstractNetworkCache*>();
QMetaObject::invokeMethod(this,
"setCache",
Qt::QueuedConnection,
Q_ARG(QAbstractNetworkCache*, cache));
}
QNetworkAccessManager::setCache(cache);
}

View file

@ -13,30 +13,13 @@
#define hifi_NetworkAccessManager_h
#include <QNetworkAccessManager>
#include <QNetworkConfiguration>
#include <QNetworkProxy>
/// Wrapper around QNetworkAccessManager wo that we only use one instance
/// For any other method you should need, make sure to be on the right thread
/// or if it is not but is a slot, use QMetaObject::invokeMethod()
/// In the case what you want to call isn't a slot and you aren't on the same thread,
/// then add then method to the method to the wrapper with the Q_INVKABLE flag
/// Wrapper around QNetworkAccessManager to restrict at one instance by thread
class NetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
public:
static NetworkAccessManager& getInstance();
Q_INVOKABLE QNetworkReply* get(const QNetworkRequest& request);
Q_INVOKABLE QNetworkReply* head(const QNetworkRequest& request);
Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, QIODevice* data);
Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, const QByteArray& data);
Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, QHttpMultiPart* multiPart);
Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, QIODevice* data);
Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, QHttpMultiPart* multiPart);
Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, const QByteArray& data);
Q_INVOKABLE QNetworkReply* sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, QIODevice* data = 0);
Q_INVOKABLE void setCache(QAbstractNetworkCache* cache);
private:
NetworkAccessManager();
};

View file

@ -515,8 +515,6 @@ void ScriptEngine::run() {
qint64 now = usecTimestampNow();
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
emit update(deltaTime);
lastUpdate = now;
if (_engine.hasUncaughtException()) {
int line = _engine.uncaughtExceptionLineNumber();
@ -524,6 +522,9 @@ void ScriptEngine::run() {
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + _engine.uncaughtException().toString());
_engine.clearExceptions();
}
emit update(deltaTime);
lastUpdate = now;
}
emit scriptEnding();

View file

@ -646,16 +646,23 @@ TestEndpoint::TestEndpoint(Mode mode) :
Endpoint(SharedNodePointer(), new TestSendRecord(), new TestReceiveRecord()),
_mode(mode),
_highPriorityMessagesToSend(0.0f),
_reliableMessagesToSend(0.0f) {
_reliableMessagesToSend(0.0f),
_reliableDeltaChannel(NULL) {
connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
SLOT(handleHighPriorityMessage(const QVariant&)));
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&, Bitstream&)),
SLOT(handleReliableMessage(const QVariant&, Bitstream&)));
if (mode == METAVOXEL_CLIENT_MODE) {
_lod = MetavoxelLOD(glm::vec3(), 0.01f);
connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX),
SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleReliableMessage(const QVariant&, Bitstream&)));
return;
}
if (mode == METAVOXEL_SERVER_MODE) {
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(checkReliableDeltaReceived()));
_data.expand();
_data.expand();
@ -673,9 +680,6 @@ TestEndpoint::TestEndpoint(Mode mode) :
// create the object that represents out delta-encoded state
_localState = new TestSharedObjectA();
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
SLOT(handleReliableMessage(const QVariant&)));
ReliableChannel* secondInput = _sequencer.getReliableInputChannel(1);
secondInput->setMessagesEnabled(false);
connect(&secondInput->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel()));
@ -867,9 +871,6 @@ bool TestEndpoint::simulate(int iterationNumber) {
maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
// record the send
_sendRecords.append(maybeCreateSendRecord());
}
return false;
@ -880,9 +881,6 @@ bool TestEndpoint::simulate(int iterationNumber) {
out << QVariant::fromValue(state);
_sequencer.endPacket();
// record the send
_sendRecords.append(maybeCreateSendRecord());
} else if (_mode == METAVOXEL_SERVER_MODE) {
// make a random change
MutateVisitor visitor;
@ -907,15 +905,39 @@ bool TestEndpoint::simulate(int iterationNumber) {
if (!_lod.isValid()) {
return false;
}
// if we're sending a reliable delta, wait until it's acknowledged
if (_reliableDeltaChannel) {
Bitstream& out = _sequencer.startPacket();
out << QVariant::fromValue(MetavoxelDeltaPendingMessage());
_sequencer.endPacket();
return false;
}
Bitstream& out = _sequencer.startPacket();
int start = _sequencer.getOutputStream().getUnderlying().device()->pos();
out << QVariant::fromValue(MetavoxelDeltaMessage());
PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
_data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
_sequencer.endPacket();
// record the send
_sendRecords.append(maybeCreateSendRecord());
out.flush();
int end = _sequencer.getOutputStream().getUnderlying().device()->pos();
if (end > _sequencer.getMaxPacketSize()) {
// we need to send the delta on the reliable channel
_reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
_reliableDeltaChannel->startMessage();
_reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start);
_reliableDeltaChannel->endMessage();
_reliableDeltaWriteMappings = out.getAndResetWriteMappings();
_reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten();
_reliableDeltaData = _data;
_reliableDeltaLOD = _lod;
_sequencer.getOutputStream().getUnderlying().device()->seek(start);
out << QVariant::fromValue(MetavoxelDeltaPendingMessage());
_sequencer.endPacket();
} else {
_sequencer.endPacket();
}
} else {
// enqueue some number of high priority messages
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
@ -957,9 +979,6 @@ bool TestEndpoint::simulate(int iterationNumber) {
qDebug() << message;
return true;
}
// record the send
_sendRecords.append(maybeCreateSendRecord());
}
maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
@ -995,7 +1014,7 @@ void TestEndpoint::sendDatagram(const QByteArray& datagram) {
// some are received out of order
const float REORDER_PROBABILITY = 0.1f;
if (randFloat() < REORDER_PROBABILITY * probabilityMultiplier) {
const int MIN_DELAY = 1;
const int MIN_DELAY = 2;
const int MAX_DELAY = 5;
// have to copy the datagram; the one we're passed is a reference to a shared buffer
_delayedDatagrams.append(ByteArrayIntPair(QByteArray(datagram.constData(), datagram.size()),
@ -1008,58 +1027,32 @@ void TestEndpoint::sendDatagram(const QByteArray& datagram) {
}
}
_other->parseData(datagram);
_delayedDatagrams.append(ByteArrayIntPair(QByteArray(datagram.constData(), datagram.size()), 1));
}
void TestEndpoint::readMessage(Bitstream& in) {
if (_mode == CONGESTION_MODE) {
QVariant message;
in >> message;
// record the receipt
_receiveRecords.append(maybeCreateReceiveRecord());
return;
}
if (_mode == METAVOXEL_CLIENT_MODE) {
QVariant message;
in >> message;
handleMessage(message, in);
// deep-compare data to sent version
int packetNumber = _sequencer.getIncomingPacketNumber();
foreach (PacketRecord* record, _other->_sendRecords) {
TestSendRecord* sendRecord = static_cast<TestSendRecord*>(record);
if (sendRecord->getPacketNumber() == packetNumber) {
if (!sendRecord->getData().deepEquals(_data, getLastAcknowledgedSendRecord()->getLOD())) {
qDebug() << "Sent/received metavoxel data mismatch.";
exit(true);
}
break;
}
}
// record the receipt
_receiveRecords.append(maybeCreateReceiveRecord());
return;
}
if (_mode == METAVOXEL_SERVER_MODE) {
QVariant message;
in >> message;
handleMessage(message, in);
// record the receipt
_receiveRecords.append(maybeCreateReceiveRecord());
return;
}
SequencedTestMessage message;
in >> message;
_remoteState = message.state;
// record the receipt
_receiveRecords.append(maybeCreateReceiveRecord());
for (QList<SequencedTestMessage>::iterator it = _other->_unreliableMessagesSent.begin();
it != _other->_unreliableMessagesSent.end(); it++) {
if (it->sequenceNumber == message.sequenceNumber) {
@ -1088,8 +1081,16 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) {
} else if (userType == MetavoxelDeltaMessage::Type) {
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
_data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD());
_data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in,
_dataLOD = getLastAcknowledgedSendRecord()->getLOD());
compareMetavoxelData();
} else if (userType == MetavoxelDeltaPendingMessage::Type) {
if (!_reliableDeltaChannel) {
_reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
_reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream());
_reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD();
}
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
@ -1098,13 +1099,15 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) {
}
PacketRecord* TestEndpoint::maybeCreateSendRecord() const {
if (_reliableDeltaChannel) {
return new TestSendRecord(_reliableDeltaLOD, _reliableDeltaData, _localState, _sequencer.getOutgoingPacketNumber());
}
return new TestSendRecord(_lod, (_mode == METAVOXEL_CLIENT_MODE) ? MetavoxelData() : _data,
_localState, _sequencer.getOutgoingPacketNumber());
}
PacketRecord* TestEndpoint::maybeCreateReceiveRecord() const {
return new TestReceiveRecord(getLastAcknowledgedSendRecord()->getLOD(),
(_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState);
return new TestReceiveRecord(_dataLOD, (_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState);
}
void TestEndpoint::handleHighPriorityMessage(const QVariant& message) {
@ -1121,7 +1124,16 @@ void TestEndpoint::handleHighPriorityMessage(const QVariant& message) {
highPriorityMessagesReceived++;
}
void TestEndpoint::handleReliableMessage(const QVariant& message) {
void TestEndpoint::handleReliableMessage(const QVariant& message, Bitstream& in) {
if (message.userType() == MetavoxelDeltaMessage::Type) {
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
_data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _dataLOD = _reliableDeltaLOD);
_sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings());
in.clearPersistentMappings();
compareMetavoxelData();
_reliableDeltaChannel = NULL;
return;
}
if (message.userType() == ClearSharedObjectMessage::Type ||
message.userType() == ClearMainChannelSharedObjectMessage::Type) {
return;
@ -1150,6 +1162,33 @@ void TestEndpoint::readReliableChannel() {
streamedBytesReceived += bytes.size();
}
void TestEndpoint::checkReliableDeltaReceived() {
if (!_reliableDeltaChannel || _reliableDeltaChannel->getOffset() < _reliableDeltaReceivedOffset) {
return;
}
_sequencer.getOutputStream().persistWriteMappings(_reliableDeltaWriteMappings);
_reliableDeltaWriteMappings = Bitstream::WriteMappings();
_reliableDeltaData = MetavoxelData();
_reliableDeltaChannel = NULL;
}
void TestEndpoint::compareMetavoxelData() {
// deep-compare data to sent version
int packetNumber = _sequencer.getIncomingPacketNumber();
foreach (PacketRecord* record, _other->_sendRecords) {
TestSendRecord* sendRecord = static_cast<TestSendRecord*>(record);
if (sendRecord->getPacketNumber() == packetNumber) {
if (!sendRecord->getData().deepEquals(_data, getLastAcknowledgedSendRecord()->getLOD())) {
qDebug() << "Sent/received metavoxel data mismatch.";
exit(true);
}
return;
}
}
qDebug() << "Received metavoxel data with no corresponding send." << packetNumber;
exit(true);
}
TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) :
_foo(foo),
_baz(baz),

View file

@ -64,17 +64,21 @@ protected:
private slots:
void handleHighPriorityMessage(const QVariant& message);
void handleReliableMessage(const QVariant& message);
void handleReliableMessage(const QVariant& message, Bitstream& in);
void readReliableChannel();
void checkReliableDeltaReceived();
private:
void compareMetavoxelData();
Mode _mode;
SharedObjectPointer _localState;
SharedObjectPointer _remoteState;
MetavoxelData _data;
MetavoxelLOD _dataLOD;
MetavoxelLOD _lod;
SharedObjectPointer _sphere;
@ -94,6 +98,12 @@ private:
float _reliableMessagesToSend;
QVariantList _reliableMessagesSent;
CircularBuffer _dataStreamed;
ReliableChannel* _reliableDeltaChannel;
int _reliableDeltaReceivedOffset;
MetavoxelData _reliableDeltaData;
MetavoxelLOD _reliableDeltaLOD;
Bitstream::WriteMappings _reliableDeltaWriteMappings;
};
/// A simple shared object.