mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 14:40:40 +02:00
baton for moving vesicles"
"
This commit is contained in:
parent
f5ad297986
commit
214723f1bf
6 changed files with 618 additions and 91 deletions
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2016 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() {
|
||||||
|
|
||||||
|
Script.include(Script.resolvePath('virtualBaton.js'));
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var baton;
|
||||||
|
var iOwn = false;
|
||||||
|
var currentInterval;
|
||||||
|
var _entityId;
|
||||||
|
|
||||||
|
function startUpdate() {
|
||||||
|
iOwn = true;
|
||||||
|
print('i am the owner ' + _entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopUpdateAndReclaim() {
|
||||||
|
print('i released the object ' + _entityId)
|
||||||
|
iOwn = false;
|
||||||
|
baton.claim(startUpdate, stopUpdateAndReclaim);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.preload = function(entityId) {
|
||||||
|
this.isConnected = false;
|
||||||
|
this.entityId = entityId;
|
||||||
|
_entityId = entityId;
|
||||||
|
this.minVelocity = 1;
|
||||||
|
this.maxVelocity = 5;
|
||||||
|
this.minAngularVelocity = 0.01;
|
||||||
|
this.maxAngularVelocity = 0.03;
|
||||||
|
baton = virtualBaton({
|
||||||
|
batonName: 'io.highfidelity.vesicles:' + entityId, // One winner for each entity
|
||||||
|
});
|
||||||
|
stopUpdateAndReclaim();
|
||||||
|
currentInterval = Script.setInterval(self.move, self.getTotalWait())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.getTotalWait = function() {
|
||||||
|
return (Math.random() * 5000) * 2;
|
||||||
|
// var avatars = AvatarList.getAvatarIdentifiers();
|
||||||
|
// var avatarCount = avatars.length;
|
||||||
|
// var random = Math.random() * 5000;
|
||||||
|
// var totalWait = random * (avatarCount * 2);
|
||||||
|
// print('cellscience color avatarcount, totalwait: ', avatarCount, totalWait)
|
||||||
|
// return totalWait
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.move = function() {
|
||||||
|
if (!iOwn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var properties = Entities.getEntityProperties(self.entityId);
|
||||||
|
var color = properties.color;
|
||||||
|
|
||||||
|
var newColor;
|
||||||
|
var red = {
|
||||||
|
red: 255,
|
||||||
|
green: 0,
|
||||||
|
blue: 0
|
||||||
|
}
|
||||||
|
var green = {
|
||||||
|
red: 0,
|
||||||
|
green: 255,
|
||||||
|
blue: 0
|
||||||
|
}
|
||||||
|
var blue = {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 255
|
||||||
|
}
|
||||||
|
if (color.red > 0) {
|
||||||
|
newColor = green;
|
||||||
|
}
|
||||||
|
if (color.green > 0) {
|
||||||
|
newColor = blue;
|
||||||
|
}
|
||||||
|
if (color.blue > 0) {
|
||||||
|
newColor = red
|
||||||
|
}
|
||||||
|
Entities.editEntity(self.entityId, {
|
||||||
|
color: newColor
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.unload = function() {
|
||||||
|
baton.release(function() {});
|
||||||
|
Script.clearTimeout(currentInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
|
@ -1,71 +0,0 @@
|
||||||
// Copyright 2016 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() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.preload=function(entityId){
|
|
||||||
this.isConnected = false;
|
|
||||||
this.entityId = entityId;
|
|
||||||
this.minVelocity = 1;
|
|
||||||
this.maxVelocity = 5;
|
|
||||||
this.minAngularVelocity = 0.01;
|
|
||||||
this.maxAngularVelocity = 0.03;
|
|
||||||
Script.setTimeout(self.move,self.getTotalWait())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.getTotalWait = function() {
|
|
||||||
var avatars = AvatarList.getAvatarIdentifiers();
|
|
||||||
var avatarCount = avatars.length;
|
|
||||||
var random = Math.random() * 5000;
|
|
||||||
var totalWait = random * (avatarCount * 2);
|
|
||||||
|
|
||||||
return totalWait
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.move = function() {
|
|
||||||
var magnitudeV = self.maxVelocity;
|
|
||||||
var directionV = {
|
|
||||||
x: Math.random() - 0.5,
|
|
||||||
y: Math.random() - 0.5,
|
|
||||||
z: Math.random() - 0.5
|
|
||||||
};
|
|
||||||
|
|
||||||
//print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x);
|
|
||||||
Entities.editEntity(self.entityId, {
|
|
||||||
velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV))
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var magnitudeAV = self.maxAngularVelocity;
|
|
||||||
|
|
||||||
var directionAV = {
|
|
||||||
x: Math.random() - 0.5,
|
|
||||||
y: Math.random() - 0.5,
|
|
||||||
z: Math.random() - 0.5
|
|
||||||
};
|
|
||||||
//print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x);
|
|
||||||
Entities.editEntity(self.entityId, {
|
|
||||||
angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV))
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Script.setTimeout(self.move,self.getTotalWait())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.unload = function() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright 2016 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() {
|
||||||
|
|
||||||
|
Script.include('virtualBaton.js');
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var baton;
|
||||||
|
var iOwn = false;
|
||||||
|
var currentInterval;
|
||||||
|
var _entityId;
|
||||||
|
|
||||||
|
function startUpdate() {
|
||||||
|
iOwn = true;
|
||||||
|
print('i am the owner ' + _entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopUpdateAndReclaim() {
|
||||||
|
print('i released the object ' + _entityId)
|
||||||
|
iOwn = false;
|
||||||
|
baton.claim(startUpdate, stopUpdateAndReclaim);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.preload = function(entityId) {
|
||||||
|
this.isConnected = false;
|
||||||
|
this.entityId = entityId;
|
||||||
|
_entityId = entityId;
|
||||||
|
this.minVelocity = 1;
|
||||||
|
this.maxVelocity = 5;
|
||||||
|
this.minAngularVelocity = 0.01;
|
||||||
|
this.maxAngularVelocity = 0.03;
|
||||||
|
baton = virtualBaton({
|
||||||
|
batonName: 'io.highfidelity.vesicles:' + entityId, // One winner for each entity
|
||||||
|
});
|
||||||
|
stopUpdateAndReclaim();
|
||||||
|
currentInterval = Script.setInterval(self.move, self.getTotalWait())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.getTotalWait = function() {
|
||||||
|
return (Math.random() * 5000) * 2;
|
||||||
|
// var avatars = AvatarList.getAvatarIdentifiers();
|
||||||
|
// var avatarCount = avatars.length;
|
||||||
|
// var random = Math.random() * 5000;
|
||||||
|
// var totalWait = random * (avatarCount * 2);
|
||||||
|
// print('cellscience color avatarcount, totalwait: ', avatarCount, totalWait)
|
||||||
|
// return totalWait
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.move = function() {
|
||||||
|
if (!iOwn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var magnitudeV = self.maxVelocity;
|
||||||
|
var directionV = {
|
||||||
|
x: Math.random() - 0.5,
|
||||||
|
y: Math.random() - 0.5,
|
||||||
|
z: Math.random() - 0.5
|
||||||
|
};
|
||||||
|
|
||||||
|
//print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x);
|
||||||
|
|
||||||
|
var magnitudeAV = self.maxAngularVelocity;
|
||||||
|
|
||||||
|
var directionAV = {
|
||||||
|
x: Math.random() - 0.5,
|
||||||
|
y: Math.random() - 0.5,
|
||||||
|
z: Math.random() - 0.5
|
||||||
|
};
|
||||||
|
//print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x);
|
||||||
|
Entities.editEntity(self.entityId, {
|
||||||
|
velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)),
|
||||||
|
angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV))
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.unload = function() {
|
||||||
|
baton.release(function() {});
|
||||||
|
Script.clearInterval(currentInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
|
@ -0,0 +1,381 @@
|
||||||
|
"use strict";
|
||||||
|
/*jslint nomen: true, plusplus: true, vars: true */
|
||||||
|
/*global Messages, Script, MyAvatar, AvatarList, Entities, print */
|
||||||
|
|
||||||
|
// Created by Howard Stearns
|
||||||
|
// Copyright 2016 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
|
||||||
|
//
|
||||||
|
// Allows cooperating scripts to pass a "virtual baton" between them,
|
||||||
|
// which is useful when part of a script should only be executed by
|
||||||
|
// the one participant that is holding this particular baton.
|
||||||
|
//
|
||||||
|
// A virtual baton is simply any string agreed upon by the scripts
|
||||||
|
// that use it. Only one script at a time can hold the baton, and it
|
||||||
|
// holds it until that script releases it, or the other scripts
|
||||||
|
// determine that the holding script is not responding. The script
|
||||||
|
// automatically determines who among claimants has the baton, if anyone,
|
||||||
|
// and holds an "election" if necessary.
|
||||||
|
//
|
||||||
|
// See entityScript/tribble.js as an example, and the functions
|
||||||
|
// virtualBaton(), claim(), release().
|
||||||
|
//
|
||||||
|
|
||||||
|
// Answers a new virtualBaton for the given parameters, of which 'key'
|
||||||
|
// is required.
|
||||||
|
function virtualBatonf(options) {
|
||||||
|
// Answer averages (number +/- variability). Avoids having everyone act in lockstep.
|
||||||
|
function randomize(number, variability) {
|
||||||
|
var allowedDeviation = number * variability; // one side of the deviation range
|
||||||
|
var allowedDeviationRange = allowedDeviation * 2; // total range for +/- deviation
|
||||||
|
var randomDeviation = Math.random() * allowedDeviationRange;
|
||||||
|
var result = number - allowedDeviation + randomDeviation;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// Allow testing outside in a harness outside of High Fidelity.
|
||||||
|
// See sourceCodeSandbox/tests/mocha/test/testVirtualBaton.js
|
||||||
|
var globals = options.globals || {},
|
||||||
|
messages = globals.Messages || Messages,
|
||||||
|
myAvatar = globals.MyAvatar || MyAvatar,
|
||||||
|
avatarList = globals.AvatarList || AvatarList,
|
||||||
|
entities = globals.Entities || Entities,
|
||||||
|
timers = globals.Script || Script,
|
||||||
|
log = globals.print || print;
|
||||||
|
|
||||||
|
var batonName = options.batonName, // The identify of the baton.
|
||||||
|
// instanceId is the identify of this particular copy of the script among all copies using the same batonName
|
||||||
|
// in the domain. For example, if you wanted only one entity among multiple entity scripts to hold the baton,
|
||||||
|
// you could specify virtualBaton({batonName: 'someBatonName', instanceId: MyAvatar.sessionUUID + entityID}).
|
||||||
|
instanceId = options.instanceId || myAvatar.sessionUUID,
|
||||||
|
// virtualBaton() returns the exports object with properties. You can pass in an object to be side-effected.
|
||||||
|
exports = options.exports || {},
|
||||||
|
// Handy to set false if we believe the optimizations are wrong, or to use both values in a test harness.
|
||||||
|
useOptimizations = (options.useOptimizations === undefined) ? true : options.useOptimizations,
|
||||||
|
electionTimeout = options.electionTimeout || randomize(500, 0.2), // ms. If no winner in this time, hold a new election.
|
||||||
|
recheckInterval = options.recheckInterval || randomize(500, 0.2), // ms. Check that winners remain connected.
|
||||||
|
// If you supply your own instanceId, you might also supply a connectionTest that answers
|
||||||
|
// truthy iff the given id is still valid and connected, and is run at recheckInterval. You
|
||||||
|
// can use exports.validId (see below), and the default answers truthy if id is valid or a
|
||||||
|
// concatenation of two valid ids. (This handles the most common cases of instanceId being
|
||||||
|
// either (the default) MyAvatar.sessionUUID, an entityID, or the concatenation (in either
|
||||||
|
// order) of both.)
|
||||||
|
connectionTest = options.connectionTest || function connectionTest(id) {
|
||||||
|
var idLength = 38;
|
||||||
|
if (id.length === idLength) {
|
||||||
|
return exports.validId(id);
|
||||||
|
}
|
||||||
|
return (id.length === 2 * idLength) && exports.validId(id.slice(0, idLength)) && exports.validId(id.slice(idLength));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!batonName) {
|
||||||
|
throw new Error("A virtualBaton must specify a batonName.");
|
||||||
|
}
|
||||||
|
// Truthy if id exists as either a connected avatar or valid entity.
|
||||||
|
exports.validId = function validId(id) {
|
||||||
|
var avatar = avatarList.getAvatar(id);
|
||||||
|
if (avatar && (avatar.sessionUUID === id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var properties = entities.getEntityProperties(id, ['type']);
|
||||||
|
return properties && properties.type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Various logging, controllable through options.
|
||||||
|
function debug() { // Display the arguments not just [Object object].
|
||||||
|
log.apply(null, [].map.call(arguments, JSON.stringify));
|
||||||
|
}
|
||||||
|
function debugFlow() {
|
||||||
|
if (options.debugFlow) {
|
||||||
|
debug.apply(null, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function debugSend(destination, operation, data) {
|
||||||
|
if (options.debugSend) {
|
||||||
|
debug('baton:', batonName, instanceId, 's=>', destination, operation, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function debugReceive(senderID, operation, data) { // senderID is client sessionUUID -- not necessarily instanceID!
|
||||||
|
if (options.debugReceive) {
|
||||||
|
debug('baton:', batonName, senderID, '=>r', instanceId, operation, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages: Just synactic sugar for hooking things up to Messages system.
|
||||||
|
// We create separate subchannel strings for each operation within our general channelKey, instead of using
|
||||||
|
// a switch in the receiver.
|
||||||
|
var channelKey = "io.highfidelity.virtualBaton:" + batonName,
|
||||||
|
subchannelHandlers = {}, // Message channel string => {receiver, op}
|
||||||
|
subchannelKeys = {}; // operation => Message channel string
|
||||||
|
function subchannelKey(operation) {
|
||||||
|
return channelKey + ':' + operation;
|
||||||
|
}
|
||||||
|
function receive(operation, handler) { // Record a handler for an operation on our channelKey
|
||||||
|
var subKey = subchannelKey(operation);
|
||||||
|
subchannelHandlers[subKey] = {receiver: handler, op: operation};
|
||||||
|
subchannelKeys[operation] = subKey;
|
||||||
|
messages.subscribe(subKey);
|
||||||
|
}
|
||||||
|
function sendHelper(subchannel, data) {
|
||||||
|
var message = JSON.stringify(data);
|
||||||
|
messages.sendMessage(subchannel, message);
|
||||||
|
}
|
||||||
|
function send1(operation, destination, data) { // Send data for an operation to just one destination on our channelKey.
|
||||||
|
debugSend(destination, operation, data);
|
||||||
|
sendHelper(subchannelKey(operation) + destination, data);
|
||||||
|
}
|
||||||
|
function send(operation, data) { // Send data for an operation on our channelKey.
|
||||||
|
debugSend('-', operation, data);
|
||||||
|
sendHelper(subchannelKeys[operation], data);
|
||||||
|
}
|
||||||
|
function messageHandler(channel, messageString, senderID) {
|
||||||
|
var handler = subchannelHandlers[channel];
|
||||||
|
if (!handler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var data = JSON.parse(messageString);
|
||||||
|
debugReceive(senderID, handler.op, data);
|
||||||
|
handler.receiver(data);
|
||||||
|
}
|
||||||
|
messages.messageReceived.connect(messageHandler);
|
||||||
|
|
||||||
|
var nPromises = 0, nAccepted = 0, electionWatchdog;
|
||||||
|
|
||||||
|
// It would be great if we had a way to know how many subscribers our channel has. Failing that...
|
||||||
|
var nNack = 0, previousNSubscribers = 0, lastGathering = 0, thisTimeout = electionTimeout;
|
||||||
|
function nSubscribers() { // Answer the number of subscribers.
|
||||||
|
// To find nQuorum, we need to know how many scripts are being run using this batonName, which isn't
|
||||||
|
// the same as the number of clients!
|
||||||
|
//
|
||||||
|
// If we overestimate by too much, we may fail to reach consensus, which triggers a new
|
||||||
|
// election proposal, so we take the number of acceptors to be the max(nPromises, nAccepted)
|
||||||
|
// + nNack reported in the previous round.
|
||||||
|
//
|
||||||
|
// If we understimate by too much, there can be different pockets on the Internet that each
|
||||||
|
// believe they have agreement on different holders of the baton, which is precisely what
|
||||||
|
// the virtualBaton is supposed to avoid. Therefore we need to allow 'nack' to gather stragglers.
|
||||||
|
|
||||||
|
var now = Date.now(), elapsed = now - lastGathering;
|
||||||
|
if (elapsed >= thisTimeout) {
|
||||||
|
previousNSubscribers = Math.max(nPromises, nAccepted) + nNack;
|
||||||
|
lastGathering = now;
|
||||||
|
} // ...otherwise we use the previous value unchanged.
|
||||||
|
|
||||||
|
// On startup, we do one proposal that we cannot possibly close, so that we'll
|
||||||
|
// lock things up for the full electionTimeout to gather responses.
|
||||||
|
if (!previousNSubscribers) {
|
||||||
|
var LARGE_INTEGER = Number.MAX_SAFE_INTEGER || (-1 >>> 1); // QT doesn't define the ECMA constant. Max int will do for our purposes.
|
||||||
|
previousNSubscribers = LARGE_INTEGER;
|
||||||
|
}
|
||||||
|
return previousNSubscribers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAIN ALGORITHM
|
||||||
|
//
|
||||||
|
// Internally, this uses the Paxos algorith to hold elections.
|
||||||
|
// Alternatively, we could have the message server pick and maintain a winner, but it would
|
||||||
|
// still have to deal with the same issues of verification in the presence of lost/delayed/reordered messages.
|
||||||
|
// Paxos is known to be optimal under these circumstances, except that its best to have a dedicated proposer
|
||||||
|
// (such as the server).
|
||||||
|
function betterNumber(number, best) {
|
||||||
|
return (number.number || 0) > best.number;
|
||||||
|
}
|
||||||
|
// Paxos Proposer behavior
|
||||||
|
var proposalNumber = 0,
|
||||||
|
nQuorum = 0,
|
||||||
|
bestPromise = {number: 0},
|
||||||
|
claimCallback,
|
||||||
|
releaseCallback;
|
||||||
|
function propose() { // Make a new proposal, so that we learn/update the proposalNumber and winner.
|
||||||
|
// Even though we send back a 'nack' if the proposal is obsolete, with network errors
|
||||||
|
// there's no way to know for certain that we've failed. The electionWatchdog will try a new
|
||||||
|
// proposal if we have not been accepted by a quorum after election Timeout.
|
||||||
|
if (electionWatchdog) {
|
||||||
|
// If we had a means of determining nSubscribers other than by counting, we could just
|
||||||
|
// timers.clearTimeout(electionWatchdog) and not return.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thisTimeout = randomize(electionTimeout, 0.5); // Note use in nSubcribers.
|
||||||
|
electionWatchdog = timers.setTimeout(function () {
|
||||||
|
electionWatchdog = null;
|
||||||
|
propose();
|
||||||
|
}, thisTimeout);
|
||||||
|
var nAcceptors = nSubscribers();
|
||||||
|
nQuorum = Math.floor(nAcceptors / 2) + 1;
|
||||||
|
|
||||||
|
proposalNumber = Math.max(proposalNumber, bestPromise.number) + 1;
|
||||||
|
debugFlow('baton:', batonName, instanceId, 'propose', proposalNumber,
|
||||||
|
'claim:', !!claimCallback, 'nAcceptors:', nAcceptors, nPromises, nAccepted, nNack);
|
||||||
|
nPromises = nAccepted = nNack = 0;
|
||||||
|
send('prepare!', {number: proposalNumber, proposerId: instanceId});
|
||||||
|
}
|
||||||
|
// We create a distinguished promise subchannel for our id, because promises need only be sent to the proposer.
|
||||||
|
receive('promise' + instanceId, function (data) {
|
||||||
|
if (betterNumber(data, bestPromise)) {
|
||||||
|
bestPromise = data;
|
||||||
|
}
|
||||||
|
if ((data.proposalNumber === proposalNumber) && (++nPromises >= nQuorum)) { // Note check for not being a previous round
|
||||||
|
var answer = {number: data.proposalNumber, proposerId: data.proposerId, winner: bestPromise.winner}; // Not data.number.
|
||||||
|
if (!answer.winner || (answer.winner === instanceId)) { // We get to pick.
|
||||||
|
answer.winner = claimCallback ? instanceId : null;
|
||||||
|
}
|
||||||
|
send('accept!', answer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
receive('nack' + instanceId, function (data) { // An acceptor reports more recent data...
|
||||||
|
if (data.proposalNumber === proposalNumber) {
|
||||||
|
nNack++; // For updating nQuorum.
|
||||||
|
// IWBNI if we started our next proposal right now/here, but we need a decent nNack count.
|
||||||
|
// Lets save that optimization for another day...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Paxos Acceptor behavior
|
||||||
|
var bestProposal = {number: 0}, accepted = {};
|
||||||
|
function acceptedId() {
|
||||||
|
return accepted && accepted.winner;
|
||||||
|
}
|
||||||
|
receive('prepare!', function (data) {
|
||||||
|
var response = {proposalNumber: data.number, proposerId: data.proposerId};
|
||||||
|
if (betterNumber(data, bestProposal)) {
|
||||||
|
bestProposal = data;
|
||||||
|
if (accepted.winner && connectionTest(accepted.winner)) {
|
||||||
|
response.number = accepted.number;
|
||||||
|
response.winner = accepted.winner;
|
||||||
|
}
|
||||||
|
send1('promise', data.proposerId, response);
|
||||||
|
} else {
|
||||||
|
send1('nack', data.proposerId, response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
receive('accept!', function (data) {
|
||||||
|
if (!betterNumber(bestProposal, data)) {
|
||||||
|
bestProposal = accepted = data; // Update both with current data. Might have missed the proposal earlier.
|
||||||
|
if (useOptimizations) {
|
||||||
|
// The Paxos literature describes every acceptor sending 'accepted' to
|
||||||
|
// every proposer and learner. In our case, these are the same nodes that received
|
||||||
|
// the 'accept!' message, so we can send to just the originating proposer and invoke
|
||||||
|
// our own accepted handler directly.
|
||||||
|
// Note that this optimization cannot be used with Byzantine Paxos (which needs another
|
||||||
|
// multi-broadcast to detect lying and collusion).
|
||||||
|
debugSend('/', 'accepted', data);
|
||||||
|
debugReceive(instanceId, 'accepted', data); // direct on next line, which doesn't get logging.
|
||||||
|
subchannelHandlers[subchannelKey('accepted') + instanceId].receiver(data);
|
||||||
|
if (data.proposerId !== instanceId) { // i.e., we didn't already do it directly on the line above.
|
||||||
|
send1('accepted', data.proposerId, data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send('accepted', data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send1('nack', data.proposerId, {proposalNumber: data.number});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Paxos Learner behavior.
|
||||||
|
function localRelease() {
|
||||||
|
var callback = releaseCallback;
|
||||||
|
debugFlow('baton:', batonName, 'localRelease', 'callback:', !!releaseCallback);
|
||||||
|
if (!releaseCallback) {
|
||||||
|
return;
|
||||||
|
} // Already released, but we might still receive a stale message. That's ok.
|
||||||
|
releaseCallback = undefined;
|
||||||
|
callback(batonName); // Pass batonName so that clients may use the same handler for different batons.
|
||||||
|
}
|
||||||
|
receive('accepted' + (useOptimizations ? instanceId : ''), function (data) { // See note in 'accept!' regarding use of instanceId here.
|
||||||
|
if (betterNumber(accepted, data)) { // Especially when !useOptimizations, we can receive other acceptances late.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var oldAccepted = accepted;
|
||||||
|
debugFlow('baton:', batonName, instanceId, 'accepted', data.number, data.winner);
|
||||||
|
accepted = data;
|
||||||
|
// If we are proposer, make sure we get a quorum of acceptances.
|
||||||
|
if ((data.proposerId === instanceId) && (data.number === proposalNumber) && (++nAccepted >= nQuorum)) {
|
||||||
|
if (electionWatchdog) {
|
||||||
|
timers.clearTimeout(electionWatchdog);
|
||||||
|
electionWatchdog = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we are the winner -- regardless of whether we were the proposer.
|
||||||
|
if (acceptedId() === instanceId) {
|
||||||
|
if (claimCallback) {
|
||||||
|
var callback = claimCallback;
|
||||||
|
claimCallback = undefined;
|
||||||
|
callback(batonName);
|
||||||
|
} else if (!releaseCallback) { // We won, but have been released and are no longer interested.
|
||||||
|
// Propose that someone else take the job.
|
||||||
|
timers.setTimeout(propose, 0); // Asynchronous to queue message handling if some are synchronous and others not.
|
||||||
|
}
|
||||||
|
} else if (releaseCallback && (oldAccepted.winner === instanceId)) { // We've been released by someone else!
|
||||||
|
localRelease(); // This can happen if enough people thought we'd disconnected.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Public Interface
|
||||||
|
//
|
||||||
|
// Registers an intent to hold the baton:
|
||||||
|
// Calls onElection(batonName) once, if you are elected by the scripts
|
||||||
|
// to be the unique holder of the baton, which may be never.
|
||||||
|
// Calls onRelease(batonName) once, if the baton held by you is released,
|
||||||
|
// whether this is by you calling release(), or by losing
|
||||||
|
// an election when you become disconnected.
|
||||||
|
// You may claim again at any time after the start of onRelease
|
||||||
|
// being called.
|
||||||
|
exports.claim = function claim(onElection, onRelease) {
|
||||||
|
debugFlow('baton:', batonName, instanceId, 'claim');
|
||||||
|
if (claimCallback) {
|
||||||
|
log("Ignoring attempt to claim virtualBaton " + batonName + ", which is already waiting for claim.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (releaseCallback) {
|
||||||
|
log("Ignoring attempt to claim virtualBaton " + batonName + ", which is somehow incorrect released, and that should not happen.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
claimCallback = onElection;
|
||||||
|
releaseCallback = onRelease;
|
||||||
|
propose();
|
||||||
|
return exports; // Allows chaining. e.g., var baton = virtualBaton({batonName: 'foo'}.claim(onClaim, onRelease);
|
||||||
|
};
|
||||||
|
// Release the baton you hold, or just log that you are not holding it.
|
||||||
|
exports.release = function release(optionalReplacementOnRelease) {
|
||||||
|
debugFlow('baton:', batonName, instanceId, 'release');
|
||||||
|
if (optionalReplacementOnRelease) { // If you want to change.
|
||||||
|
releaseCallback = optionalReplacementOnRelease;
|
||||||
|
}
|
||||||
|
if (acceptedId() !== instanceId) {
|
||||||
|
log("Ignoring attempt to release virtualBaton " + batonName + ", which is not being held.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localRelease();
|
||||||
|
if (!claimCallback) { // No claim set in release callback.
|
||||||
|
propose();
|
||||||
|
}
|
||||||
|
return exports;
|
||||||
|
};
|
||||||
|
exports.recheckWatchdog = timers.setInterval(function recheck() {
|
||||||
|
var holder = acceptedId(); // If we're waiting and we notice the holder is gone, ...
|
||||||
|
if (holder && claimCallback && !electionWatchdog && !connectionTest(holder)) {
|
||||||
|
bestPromise.winner = null; // used if the quorum agrees that old winner is not there
|
||||||
|
propose(); // ... propose an election.
|
||||||
|
}
|
||||||
|
}, recheckInterval);
|
||||||
|
exports.unload = function unload() { // Disconnect from everything.
|
||||||
|
messages.messageReceived.disconnect(messageHandler);
|
||||||
|
timers.clearInterval(exports.recheckWatchdog);
|
||||||
|
if (electionWatchdog) {
|
||||||
|
timers.clearTimeout(electionWatchdog);
|
||||||
|
}
|
||||||
|
electionWatchdog = claimCallback = releaseCallback = null;
|
||||||
|
Object.keys(subchannelHandlers).forEach(messages.unsubscribe);
|
||||||
|
debugFlow('baton:', batonName, instanceId, 'unload');
|
||||||
|
return exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gather nAcceptors by making two proposals with some gathering time, even without a claim.
|
||||||
|
propose();
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
if (typeof module !== 'undefined') { // Allow testing in nodejs.
|
||||||
|
module.exports = virtualBatonf;
|
||||||
|
} else {
|
||||||
|
virtualBaton = virtualBatonf;
|
||||||
|
}
|
|
@ -9,19 +9,42 @@
|
||||||
var teleport;
|
var teleport;
|
||||||
var portalDestination;
|
var portalDestination;
|
||||||
var animationURL;
|
var animationURL;
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.entered = true;
|
this.entered = true;
|
||||||
|
Script.include('virtualBaton.js');
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var baton;
|
||||||
|
var iOwn = false;
|
||||||
|
var currentInterval;
|
||||||
|
var _entityId;
|
||||||
|
|
||||||
|
function startUpdate() {
|
||||||
|
iOwn = true;
|
||||||
|
print('i am the owner ' + _entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopUpdateAndReclaim() {
|
||||||
|
print('i released the object ' + _entityId)
|
||||||
|
iOwn = false;
|
||||||
|
baton.claim(startUpdate, stopUpdateAndReclaim);
|
||||||
|
}
|
||||||
|
|
||||||
this.preload = function(entityID) {
|
this.preload = function(entityID) {
|
||||||
this.entityId = entityID;
|
this.entityId = entityID;
|
||||||
|
_entityId = entityID;
|
||||||
this.initialize(entityID);
|
this.initialize(entityID);
|
||||||
this.initTimeout = null;
|
this.initTimeout = null;
|
||||||
this.minVelocity = 1;
|
this.minVelocity = 1;
|
||||||
this.maxVelocity = 5;
|
this.maxVelocity = 5;
|
||||||
this.minAngularVelocity = 0.01;
|
this.minAngularVelocity = 0.01;
|
||||||
this.maxAngularVelocity = 0.03;
|
this.maxAngularVelocity = 0.03;
|
||||||
Script.setTimeout(self.move, self.getTotalWait())
|
baton = virtualBaton({
|
||||||
|
batonName: 'io.highfidelity.vesicles:' + entityId, // One winner for each entity
|
||||||
|
});
|
||||||
|
stopUpdateAndReclaim();
|
||||||
|
currentInterval = Script.setInterval(self.move, self.getTotalWait())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initialize = function(entityID) {
|
this.initialize = function(entityID) {
|
||||||
|
@ -104,6 +127,8 @@
|
||||||
if (this.initTimeout !== null) {
|
if (this.initTimeout !== null) {
|
||||||
Script.clearTimeout(this.initTimeout);
|
Script.clearTimeout(this.initTimeout);
|
||||||
}
|
}
|
||||||
|
baton.release(function() {});
|
||||||
|
Script.clearInterval(currentInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hoverEnterEntity = function(entityID) {
|
this.hoverEnterEntity = function(entityID) {
|
||||||
|
@ -114,16 +139,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getTotalWait = function() {
|
this.getTotalWait = function() {
|
||||||
var avatars = AvatarList.getAvatarIdentifiers();
|
return (Math.random() * 5000) * 2;
|
||||||
var avatarCount = avatars.length;
|
|
||||||
var random = Math.random() * 2000;
|
|
||||||
var totalWait = random * (avatarCount * 2);
|
|
||||||
|
|
||||||
return totalWait
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.move = function() {
|
this.move = function() {
|
||||||
|
if (!iOwn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var magnitudeV = self.maxVelocity;
|
var magnitudeV = self.maxVelocity;
|
||||||
var directionV = {
|
var directionV = {
|
||||||
|
@ -133,11 +155,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
//print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x);
|
//print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x);
|
||||||
Entities.editEntity(self.entityId, {
|
|
||||||
velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV))
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var magnitudeAV = self.maxAngularVelocity;
|
var magnitudeAV = self.maxAngularVelocity;
|
||||||
|
|
||||||
|
@ -148,11 +165,12 @@
|
||||||
};
|
};
|
||||||
//print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x);
|
//print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x);
|
||||||
Entities.editEntity(self.entityId, {
|
Entities.editEntity(self.entityId, {
|
||||||
|
velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)),
|
||||||
angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV))
|
angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV))
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Script.setTimeout(self.move, self.getTotalWait())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
|
@ -5,7 +5,7 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
var version = 1043;
|
var version = 1049;
|
||||||
var cellLayout;
|
var cellLayout;
|
||||||
var baseLocation = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/";
|
var baseLocation = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/";
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ var scenes = [{
|
||||||
grabbable: false
|
grabbable: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
script: "moveRandomly.js?" + version,
|
script: "moveRandomly2.js?" + version,
|
||||||
visible: true
|
visible: true
|
||||||
}, { //golgi vesicles
|
}, { //golgi vesicles
|
||||||
model: "vesicle",
|
model: "vesicle",
|
||||||
|
@ -238,7 +238,7 @@ var scenes = [{
|
||||||
grabbable: false
|
grabbable: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
script: "moveRandomly.js?" + version,
|
script: "moveRandomly2.js?" + version,
|
||||||
visible: true
|
visible: true
|
||||||
}, {
|
}, {
|
||||||
model: "vesicle",
|
model: "vesicle",
|
||||||
|
@ -304,7 +304,7 @@ var scenes = [{
|
||||||
grabbable: false
|
grabbable: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
script: "moveRandomly.js?" + version,
|
script: "moveRandomly2.js?" + version,
|
||||||
visible: true
|
visible: true
|
||||||
}, { //outer vesicles
|
}, { //outer vesicles
|
||||||
model: "vesicle",
|
model: "vesicle",
|
||||||
|
@ -326,7 +326,7 @@ var scenes = [{
|
||||||
grabbable: false
|
grabbable: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
script: "moveRandomly.js?" + version,
|
script: "moveRandomly2.js?" + version,
|
||||||
visible: true
|
visible: true
|
||||||
},
|
},
|
||||||
// {//wigglies
|
// {//wigglies
|
||||||
|
|
Loading…
Reference in a new issue