mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-07 13:12:39 +02:00
yes!
This commit is contained in:
parent
e52b910e53
commit
b08f364447
6 changed files with 265 additions and 30 deletions
|
@ -66,11 +66,6 @@
|
|||
dimensions = Vec3.multiply(scale, properties.dimensions);
|
||||
baton = virtualBaton({
|
||||
batonName: 'io.highfidelity.tribble',
|
||||
connectionTest: function (id) {
|
||||
var connected = baton.validId(id);
|
||||
print(MyAvatar.sessionUUID, id, connected);
|
||||
return connected;
|
||||
},
|
||||
debugFlow: debug.flow,
|
||||
debugSend: debug.send,
|
||||
debugReceive: debug.receive
|
|
@ -31,7 +31,8 @@ function virtualBatonf(options) {
|
|||
var allowedDeviation = number * variability;
|
||||
return number - allowedDeviation + (Math.random() * 2 * allowedDeviation);
|
||||
}
|
||||
// Allow testing outside in a harness of High Fidelity.
|
||||
// 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,
|
||||
|
@ -43,20 +44,20 @@ function virtualBatonf(options) {
|
|||
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', id: MyAvatar.sessionUUID + entityID}).
|
||||
// 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 can also supply a connectionTest to check 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.)
|
||||
// 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); }
|
||||
|
@ -126,7 +127,7 @@ function virtualBatonf(options) {
|
|||
|
||||
// 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 - or zero to indicate that we have to wait.
|
||||
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!
|
||||
//
|
||||
|
@ -136,7 +137,7 @@ function virtualBatonf(options) {
|
|||
//
|
||||
// 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' gather stragglers.
|
||||
// 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) {
|
||||
|
@ -144,8 +145,8 @@ function virtualBatonf(options) {
|
|||
lastGathering = now;
|
||||
} // ...otherwise we use the previous value unchanged.
|
||||
|
||||
// On startup, we do one proposal that we cannot possibly win, so that we'll
|
||||
// lock things up for timeout to gather the number of responses.
|
||||
// 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;
|
||||
|
@ -170,7 +171,7 @@ function virtualBatonf(options) {
|
|||
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 net work errors
|
||||
// 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) {
|
||||
|
@ -178,7 +179,7 @@ function virtualBatonf(options) {
|
|||
// timers.clearTimeout(electionWatchdog) and not return.
|
||||
return;
|
||||
}
|
||||
thisTimeout = randomize(electionTimeout, 0.5); // for accurate nSubcribers counting.
|
||||
thisTimeout = randomize(electionTimeout, 0.5); // Note use in nSubcribers.
|
||||
electionWatchdog = timers.setTimeout(function () {
|
||||
electionWatchdog = null;
|
||||
propose();
|
||||
|
@ -208,7 +209,7 @@ function virtualBatonf(options) {
|
|||
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 now (here, in a setTimeout, but we need a decent nNack count.
|
||||
// IWBNI if we started our next proposal right now/here, but we need a decent nNack count.
|
||||
// Lets save that optimization for another day...
|
||||
}
|
||||
});
|
||||
|
@ -232,7 +233,7 @@ function virtualBatonf(options) {
|
|||
if (!betterNumber(bestProposal, data)) {
|
||||
bestProposal = accepted = data; // Update both with current data. Might have missed the proposal earlier.
|
||||
if (useOptimizations) {
|
||||
// The Paxos literature describe every acceptor sending 'accepted' to
|
||||
// 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.
|
||||
|
@ -288,14 +289,16 @@ function virtualBatonf(options) {
|
|||
}
|
||||
});
|
||||
|
||||
// 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 you release the baton held by you,
|
||||
// whether this is by you calling release(), or by loosing
|
||||
// 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. Otherwise, you will not participate in further elections.
|
||||
// being called.
|
||||
exports.claim = function claim(onElection, onRelease) {
|
||||
debugFlow('baton:', batonName, instanceId, 'claim');
|
||||
if (claimCallback) {
|
||||
|
@ -314,7 +317,7 @@ function virtualBatonf(options) {
|
|||
// 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) { // E.g., maybe normal onRelease reclaims, but at shutdown you explicitly don't.
|
||||
if (optionalReplacementOnRelease) { // If you want to change.
|
||||
releaseCallback = optionalReplacementOnRelease;
|
||||
}
|
||||
if (acceptedId() !== instanceId) {
|
||||
|
@ -323,7 +326,7 @@ function virtualBatonf(options) {
|
|||
}
|
||||
localRelease();
|
||||
if (!claimCallback) { // No claim set in release callback.
|
||||
propose(); // We are the distinguished proposer, but we'll pick anyone else interested, else set it to null.
|
||||
propose();
|
||||
}
|
||||
return exports;
|
||||
};
|
||||
|
@ -348,7 +351,7 @@ function virtualBatonf(options) {
|
|||
propose();
|
||||
return exports;
|
||||
}
|
||||
if (typeof module !== 'undefined') {
|
||||
if (typeof module !== 'undefined') { // Allow testing in nodejs.
|
||||
module.exports = virtualBatonf;
|
||||
} else {
|
||||
virtualBaton = virtualBatonf;
|
|
@ -14,8 +14,8 @@ var Vec3, Quat, MyAvatar, Entities, Camera, Script, print;
|
|||
// The _TIMEOUT parameters can be 0 for no activity, and -1 to be active indefinitely.
|
||||
//
|
||||
|
||||
var NUMBER_TO_CREATE = 200;
|
||||
var LIFETIME = 60; // seconds
|
||||
var NUMBER_TO_CREATE = 1; // FIXME 200;
|
||||
var LIFETIME = 30; // FIXME 60; // seconds
|
||||
var EDIT_RATE = 60; // hz
|
||||
var EDIT_TIMEOUT = -1;
|
||||
var MOVE_RATE = 1; // hz
|
||||
|
@ -68,7 +68,8 @@ Script.setInterval(function () {
|
|||
moveTimeout: MOVE_TIMEOUT,
|
||||
moveRate: MOVE_RATE,
|
||||
editTimeout: EDIT_TIMEOUT,
|
||||
editRate: EDIT_RATE
|
||||
editRate: EDIT_RATE,
|
||||
debug: {flow: true, send: true}
|
||||
});
|
||||
for (i = 0; (i < numToCreate) && (totalCreated < NUMBER_TO_CREATE); i++) {
|
||||
Entities.addEntity({
|
||||
|
|
5
tests/mocha/README.md
Normal file
5
tests/mocha/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
mocha tests of javascript code (e.g., from ../../examples/libraries/).
|
||||
```
|
||||
npm install
|
||||
npm test
|
||||
```
|
11
tests/mocha/package.json
Normal file
11
tests/mocha/package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "HighFidelityTests",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"license": "Apache 2.0",
|
||||
"devDependencies": {
|
||||
"mocha": "^2.2.1"
|
||||
}
|
||||
}
|
220
tests/mocha/test/testVirtualBaton.js
Normal file
220
tests/mocha/test/testVirtualBaton.js
Normal file
|
@ -0,0 +1,220 @@
|
|||
"use strict";
|
||||
/*jslint nomen: true, plusplus: true, vars: true */
|
||||
var assert = require('assert');
|
||||
var mocha = require('mocha'), describe = mocha.describe, it = mocha.it, after = mocha.after;
|
||||
var virtualBaton = require('../../../examples/libraries/virtualBaton.js');
|
||||
|
||||
describe('temp', function () {
|
||||
var messageCount = 0, testStart = Date.now();
|
||||
function makeMessager(nodes, me, mode) { // shim for High Fidelity Message system
|
||||
function noopSend(channel, string, source) { }
|
||||
function hasChannel(node, channel) { return -1 !== node.subscribed.indexOf(channel); }
|
||||
function sendSync(channel, message, nodes, skip) {
|
||||
nodes.forEach(function (node) {
|
||||
if (!hasChannel(node, channel) || (node === skip)) { return; }
|
||||
node.sender(channel, message, me.name);
|
||||
});
|
||||
}
|
||||
nodes.forEach(function (node) {
|
||||
node.sender = node.sender || noopSend;
|
||||
node.subscribed = node.subscribed || [];
|
||||
});
|
||||
return {
|
||||
subscriberCount: function () {
|
||||
var c = 0;
|
||||
nodes.forEach(function (n) { if (n.subscribed.length) { c++; } });
|
||||
return c;
|
||||
},
|
||||
subscribe: function (channel) {
|
||||
me.subscribed.push(channel);
|
||||
},
|
||||
unsubscribe: function (channel) {
|
||||
me.subscribed.splice(me.subscribed.indexOf(channel), 1);
|
||||
},
|
||||
sendMessage: function (channel, message) {
|
||||
if ((mode === 'immediate2Me') && hasChannel(me, channel)) { me.sender(channel, message, me.name); }
|
||||
if (mode === 'immediate') {
|
||||
sendSync(channel, message, nodes, null);
|
||||
} else {
|
||||
process.nextTick(function () {
|
||||
sendSync(channel, message, nodes, (mode === 'immediate2Me') ? me : null);
|
||||
});
|
||||
}
|
||||
},
|
||||
messageReceived: {
|
||||
connect: function (f) {
|
||||
me.sender = function (c, m, i) { messageCount++; f(c, m, i); };
|
||||
},
|
||||
disconnect: function () {
|
||||
me.sender = noopSend;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
var debug = {}; //{flow: true, send: false, receive: false};
|
||||
function makeBaton(testKey, nodes, node, debug, mode, optimize) {
|
||||
debug = debug || {};
|
||||
var baton = virtualBaton({
|
||||
batonName: testKey,
|
||||
debugSend: debug.send,
|
||||
debugReceive: debug.receive,
|
||||
debugFlow: debug.flow,
|
||||
useOptimizations: optimize,
|
||||
connectionTest: function (id) { return baton.validId(id); },
|
||||
globals: {
|
||||
Messages: makeMessager(nodes, node, mode),
|
||||
MyAvatar: {sessionUUID: node.name},
|
||||
Script: {
|
||||
setTimeout: setTimeout,
|
||||
clearTimeout: clearTimeout,
|
||||
setInterval: setInterval,
|
||||
clearInterval: clearInterval
|
||||
},
|
||||
AvatarList: {
|
||||
getAvatar: function (id) { return {sessionUUID: id}; }
|
||||
},
|
||||
Entities: {getEntityProperties: function () { }},
|
||||
print: console.log
|
||||
}
|
||||
});
|
||||
return baton;
|
||||
}
|
||||
function noRelease(batonName) { assert.ok(!batonName, "should not release"); }
|
||||
function defineABunch(mode, optimize) {
|
||||
function makeKey(prefix) { return prefix + mode + (optimize ? '-opt' : ''); }
|
||||
var testKeys = makeKey('single-');
|
||||
it(testKeys, function (done) {
|
||||
var nodes = [{name: 'a'}];
|
||||
var a = makeBaton(testKeys, nodes, nodes[0], debug, mode).claim(function (key) {
|
||||
console.log('claimed a');
|
||||
assert.equal(testKeys, key);
|
||||
a.unload();
|
||||
done();
|
||||
}, noRelease);
|
||||
});
|
||||
var testKeydp = makeKey('dual-parallel-');
|
||||
it(testKeydp, function (done) {
|
||||
this.timeout(10000);
|
||||
var nodes = [{name: 'ap'}, {name: 'bp'}];
|
||||
var a = makeBaton(testKeydp, nodes, nodes[0], debug, mode, optimize),
|
||||
b = makeBaton(testKeydp, nodes, nodes[1], debug, mode, optimize);
|
||||
function accepted(key) { // Under some circumstances of network timing, either a or b can win.
|
||||
console.log('claimed ap');
|
||||
assert.equal(testKeydp, key);
|
||||
done();
|
||||
}
|
||||
a.claim(accepted, noRelease);
|
||||
b.claim(accepted, noRelease);
|
||||
});
|
||||
var testKeyds = makeKey('dual-serial-');
|
||||
it(testKeyds, function (done) {
|
||||
var nodes = [{name: 'as'}, {name: 'bs'}],
|
||||
gotA = false,
|
||||
gotB = false;
|
||||
makeBaton(testKeyds, nodes, nodes[0], debug, mode, optimize).claim(function (key) {
|
||||
console.log('claimed as', key);
|
||||
assert.ok(!gotA, "should not get A after B");
|
||||
gotA = true;
|
||||
done();
|
||||
}, noRelease);
|
||||
setTimeout(function () {
|
||||
makeBaton(testKeyds, nodes, nodes[1], debug, mode, optimize).claim(function (key) {
|
||||
console.log('claimed bs', key);
|
||||
assert.ok(!gotB, "should not get B after A");
|
||||
gotB = true;
|
||||
done();
|
||||
}, noRelease);
|
||||
}, 500);
|
||||
});
|
||||
var testKeydsl = makeKey('dual-serial-long-');
|
||||
it(testKeydsl, function (done) {
|
||||
this.timeout(5000);
|
||||
var nodes = [{name: 'al'}, {name: 'bl'}],
|
||||
gotA = false,
|
||||
gotB = false,
|
||||
releaseA = false;
|
||||
makeBaton(testKeydsl, nodes, nodes[0], debug, mode, optimize).claim(function (key) {
|
||||
console.log('claimed al', key);
|
||||
assert.ok(!gotB, "should not get A after B");
|
||||
gotA = true;
|
||||
if (!gotB) { done(); }
|
||||
}, function () {
|
||||
assert.ok(gotA, "Should claim it first");
|
||||
releaseA = true;
|
||||
if (gotB) { done(); }
|
||||
});
|
||||
setTimeout(function () {
|
||||
makeBaton(testKeydsl, nodes, nodes[1], debug, mode, optimize).claim(function (key) {
|
||||
console.log('claimed bl', key);
|
||||
gotB = true;
|
||||
if (releaseA) { done(); }
|
||||
}, noRelease);
|
||||
}, 3000);
|
||||
});
|
||||
var testKeydsr = makeKey('dual-serial-with-release-');
|
||||
it(testKeydsr, function (done) {
|
||||
this.timeout(5000);
|
||||
var nodes = [{name: 'asr'}, {name: 'bsr'}],
|
||||
gotClaimA = false,
|
||||
gotReleaseA = false,
|
||||
a = makeBaton(testKeydsr, nodes, nodes[0], debug, mode, optimize),
|
||||
b = makeBaton(testKeydsr, nodes, nodes[1], debug, mode, optimize);
|
||||
a.claim(function (key) {
|
||||
console.log('claimed asr');
|
||||
assert.equal(testKeydsr, key);
|
||||
gotClaimA = true;
|
||||
b.claim(function (key) {
|
||||
console.log('claimed bsr');
|
||||
assert.equal(testKeydsr, key);
|
||||
assert.ok(gotReleaseA);
|
||||
done();
|
||||
}, noRelease);
|
||||
a.release();
|
||||
}, function (key) {
|
||||
console.log('released asr');
|
||||
assert.equal(testKeydsr, key);
|
||||
assert.ok(gotClaimA);
|
||||
gotReleaseA = true;
|
||||
});
|
||||
});
|
||||
var testKeydpr = makeKey('dual-parallel-with-release-');
|
||||
it(testKeydpr, function (done) {
|
||||
this.timeout(5000);
|
||||
var nodes = [{name: 'ar'}, {name: 'br'}];
|
||||
var a = makeBaton(testKeydpr, nodes, nodes[0], debug, mode, optimize),
|
||||
b = makeBaton(testKeydpr, nodes, nodes[1], debug, mode, optimize),
|
||||
gotClaimA = false,
|
||||
gotReleaseA = false,
|
||||
gotClaimB = false;
|
||||
a.claim(function (key) {
|
||||
console.log('claimed ar');
|
||||
assert.equal(testKeydpr, key);
|
||||
gotClaimA = true;
|
||||
assert.ok(!gotClaimB, "if b claimed, should not get a");
|
||||
a.release();
|
||||
}, function (key) {
|
||||
console.log('released ar');
|
||||
assert.equal(testKeydpr, key);
|
||||
assert.ok(gotClaimA);
|
||||
gotReleaseA = true;
|
||||
});
|
||||
b.claim(function (key) {
|
||||
console.log('claimed br', gotClaimA ? 'with' : 'without', 'ar first');
|
||||
assert.equal(testKeydpr, key);
|
||||
gotClaimB = true;
|
||||
assert.ok(!gotClaimA || gotReleaseA);
|
||||
done();
|
||||
}, noRelease);
|
||||
});
|
||||
}
|
||||
function defineAllModeTests(optimize) {
|
||||
defineABunch('delayed', optimize);
|
||||
defineABunch('immediate2Me', optimize);
|
||||
defineABunch('immediate', optimize);
|
||||
}
|
||||
defineAllModeTests(true);
|
||||
defineAllModeTests(false);
|
||||
after(function () {
|
||||
console.log(messageCount, 'messages sent over', (Date.now() - testStart), 'ms.');
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue