mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-07 18:02:31 +02:00
add headers, add virtual baton, try another way of disabling grab while trigger already held when using music box
This commit is contained in:
parent
d849154466
commit
6781a7cb91
17 changed files with 617 additions and 68 deletions
|
@ -1,3 +1,14 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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('../utils.js');
|
||||
Script.include('../virtualBaton.js');
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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
|
||||
//
|
||||
// when you drop a doll and it hits the dressing room platform it transforms into a big version.
|
||||
// the small doll is destroyed and a new small doll is put on the shelf
|
||||
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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
|
||||
//
|
||||
|
||||
var TRANSFORMER_SCRIPT = Script.resolvePath('transformer.js');
|
||||
|
||||
var AVATAR_COLLISION_HULL = 'atp:/dressingRoom/Avatar-Hull-4.obj';
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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
|
||||
//
|
||||
|
||||
var fishTankPath = Script.resolvePath('wrapper.js');
|
||||
Script.include(fishTankPath);
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
//
|
||||
//if someone happens to fall below the home, they'll get caught by this surface and teleported back to the living room
|
||||
//
|
||||
// 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 _this;
|
||||
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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() {
|
||||
|
||||
|
||||
//tells this
|
||||
var _this;
|
||||
|
||||
function BaseBox() {
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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() {
|
||||
|
||||
//TODO -- At the part of the animation where the user starts to close the lid we need to rewind any frames past the one where it is aligned for going up/down before switching to the down animation
|
||||
|
@ -63,11 +75,15 @@
|
|||
},
|
||||
|
||||
createMyInteval: function() {
|
||||
var handToDisable = _this.hand;
|
||||
print("disabling hand: " + handToDisable);
|
||||
Messages.sendLocalMessage('Hifi-Hand-Disabler', handToDisable);
|
||||
_this.myInterval = Script.setInterval(function() {
|
||||
_this.handleTrigger();
|
||||
}, 16);
|
||||
},
|
||||
clearMyInterval: function() {
|
||||
Messages.sendLocalMessage('Hifi-Hand-Disabler','none');
|
||||
if (_this.myInterval !== null) {
|
||||
Script.clearInterval(_this.myInterval)
|
||||
}
|
||||
|
@ -398,6 +414,7 @@
|
|||
},
|
||||
|
||||
unload: function() {
|
||||
Messages.sendLocalMessage('Hifi-Hand-Disabler','none');
|
||||
Controller.disableMapping(MAPPING_NAME);
|
||||
print('DISABLED MAPPING:: ' + MAPPING_NAME);
|
||||
if (this.musicInjector !== null) {
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
var musicBoxPath = Script.resolvePath('wrapper.js?' + Math.random());
|
||||
Script.include(musicBoxPath);
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(1, Quat.getFront(Camera.getOrientation())));
|
||||
var musicBox = new HomeMusicBox(center, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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
|
||||
//
|
||||
//
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
musicBox.cleanup()
|
||||
})
|
||||
var musicBoxPath = Script.resolvePath('wrapper.js?' + Math.random());
|
||||
Script.include(musicBoxPath);
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(1, Quat.getFront(Camera.getOrientation())));
|
||||
var musicBox = new HomeMusicBox(center, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
musicBox.cleanup()
|
||||
})
|
|
@ -1,3 +1,14 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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
|
||||
//
|
||||
|
||||
HomeMusicBox = function(spawnPosition, spawnRotation) {
|
||||
|
||||
var SHOULD_CLEANUP = false;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
|
||||
(function() {
|
||||
|
||||
Script.include("../utils.js");
|
||||
|
|
|
@ -5,8 +5,17 @@
|
|||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This cleanups up and creates content for the home.
|
||||
// To begin, it finds any entity with the home reset key in its user data and deletes it.
|
||||
// Next, it creates 'scripted entities', or objects that have scripts attached to them.
|
||||
// Then it creates 'kinetic entities', or objects that need to be reset but have no scripts attached.
|
||||
// Finally it sets up the 'dressing room' - an area that turns small models into big models on collision with a platform.
|
||||
//
|
||||
//
|
||||
// The code is organized into a folder for each scripted entity containing a 'wrapper' file that instantiates the entity at a given location and rotation;
|
||||
// The rest of the folder contains the scripts needed for that entity.
|
||||
// Additionally, there is a folder for the kinetic entities that contains JSON files describing their content, and a single 'wrapper' file that contains constructors for each the various objects.
|
||||
// A utilities file and the virtualBaton are included at the root level for all scripts to access as necessary.
|
||||
//
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
@ -60,7 +69,9 @@
|
|||
var TRANSFORMER_URL_STYLIZED_FEMALE = 'atp:/dressingRoom/stylized_female.fbx';
|
||||
|
||||
var TRANSFORMER_URL_REALISTIC_MALE = '';
|
||||
|
||||
var TRANSFORMER_URL_REALISTIC_FEMALE = '';
|
||||
|
||||
Reset.prototype = {
|
||||
tidying: false,
|
||||
|
||||
|
@ -123,7 +134,7 @@
|
|||
|
||||
Script.setTimeout(function() {
|
||||
_this.createKineticEntities();
|
||||
_this.createDynamicEntities();
|
||||
_this.createScriptedEntities();
|
||||
_this.setupDressingRoom();
|
||||
}, 750)
|
||||
|
||||
|
@ -143,7 +154,39 @@
|
|||
_this.toggleButton();
|
||||
},
|
||||
|
||||
createDynamicEntities: function() {
|
||||
findAndDeleteHomeEntities: function() {
|
||||
print('HOME trying to find home entities to delete')
|
||||
var resetProperties = Entities.getEntityProperties(_this.entityID);
|
||||
var results = Entities.findEntities(resetProperties.position, 1000);
|
||||
var found = [];
|
||||
results.forEach(function(result) {
|
||||
var properties = Entities.getEntityProperties(result);
|
||||
|
||||
if (properties.userData === "" || properties.userData === undefined) {
|
||||
print('no userdata -- its blank or undefined')
|
||||
return;
|
||||
}
|
||||
|
||||
var userData = null;
|
||||
try {
|
||||
userData = JSON.parse(properties.userData);
|
||||
} catch (err) {
|
||||
print('error parsing json in resetscript for: ' + properties.name);
|
||||
//print('properties are:' + properties.userData);
|
||||
return;
|
||||
}
|
||||
if (userData.hasOwnProperty('hifiHomeKey')) {
|
||||
if (userData.hifiHomeKey.reset === true) {
|
||||
Entities.deleteEntity(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
print('HOME after deleting home entities')
|
||||
},
|
||||
|
||||
createScriptedEntities: function() {
|
||||
var fishTank = new FishTank({
|
||||
x: 1099.2200,
|
||||
y: 460.5460,
|
||||
|
@ -201,11 +244,10 @@
|
|||
});
|
||||
|
||||
// var musicBox = new MusicBox();
|
||||
|
||||
print('HOME after creating scripted entities')
|
||||
|
||||
},
|
||||
|
||||
|
||||
createKineticEntities: function() {
|
||||
|
||||
var blocks = new Blocks({
|
||||
|
@ -286,7 +328,7 @@
|
|||
y: 461,
|
||||
z: -73.3
|
||||
});
|
||||
|
||||
print('HOME after creating kinetic entities')
|
||||
},
|
||||
|
||||
setupDressingRoom: function() {
|
||||
|
@ -439,41 +481,8 @@
|
|||
|
||||
},
|
||||
|
||||
findAndDeleteHomeEntities: function() {
|
||||
print('HOME trying to find home entities to delete')
|
||||
var resetProperties = Entities.getEntityProperties(_this.entityID);
|
||||
var results = Entities.findEntities(resetProperties.position, 1000);
|
||||
var found = [];
|
||||
results.forEach(function(result) {
|
||||
var properties = Entities.getEntityProperties(result);
|
||||
|
||||
if (properties.userData === "" || properties.userData === undefined) {
|
||||
print('no userdata -- its blank or undefined')
|
||||
return;
|
||||
}
|
||||
|
||||
var userData = null;
|
||||
try {
|
||||
userData = JSON.parse(properties.userData);
|
||||
} catch (err) {
|
||||
print('error parsing json in resetscript for: ' + properties.name);
|
||||
//print('properties are:' + properties.userData);
|
||||
return;
|
||||
}
|
||||
if (userData.hasOwnProperty('hifiHomeKey')) {
|
||||
if (userData.hifiHomeKey.reset === true) {
|
||||
Entities.deleteEntity(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
print('HOME after deleting home entities')
|
||||
},
|
||||
|
||||
unload: function() {
|
||||
this.findAndDeleteHomeEntities();
|
||||
|
||||
// this.findAndDeleteHomeEntities();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// this finds lights and toggles thier visibility, and flips the emissive texture of some light models
|
||||
//
|
||||
// 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 SEARCH_RADIUS = 100;
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// this finds lights and toggles thier visibility, and flips the emissive texture of some light models
|
||||
//
|
||||
// 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 SEARCH_RADIUS = 10;
|
||||
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// this finds lights and toggles thier visibility, and flips the emissive texture of some light models
|
||||
//
|
||||
// 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 SEARCH_RADIUS = 100;
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// this switch finds a fan entity and changes its angular damping so that it spins or doesn't spin as appropriate.
|
||||
//
|
||||
// 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 SEARCH_RADIUS = 100;
|
||||
var _this;
|
||||
|
|
381
unpublishedScripts/DomainContent/Home/virtualBaton.js
Normal file
381
unpublishedScripts/DomainContent/Home/virtualBaton.js
Normal file
|
@ -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;
|
||||
}
|
|
@ -1,12 +1,23 @@
|
|||
var whiteboardPath = Script.resolvePath('wrapper.js');
|
||||
//
|
||||
//
|
||||
// Created by The Content Team 4/10/216
|
||||
// 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
|
||||
//
|
||||
|
||||
var whiteboardPath = Script.resolvePath('wrapper.js');
|
||||
Script.include(whiteboardPath);
|
||||
|
||||
var whiteboard = new Whiteboard({
|
||||
x: 1104,
|
||||
y: 460.5,
|
||||
z: -77
|
||||
}, {
|
||||
x: 0,
|
||||
y: -133,
|
||||
z: 0
|
||||
});
|
||||
var whiteboard = new Whiteboard({
|
||||
x: 1104,
|
||||
y: 460.5,
|
||||
z: -77
|
||||
}, {
|
||||
x: 0,
|
||||
y: -133,
|
||||
z: 0
|
||||
});
|
Loading…
Reference in a new issue