345 lines · 14.1 KB · JavaScript
RawDownload
"use strict";/*jslint vars: true, plusplus: true*//*globals Script, MyAvatar, Quat, Vec3, Render, ScriptDiscoveryService, Window, LODManager, Entities, Messages, AvatarList, Menu, Stats, HMD, location, print*/////  loadedMachine.js//  scripts/developer/tests/////  Created by Howard Stearns on 6/6/16.//  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////  Confirms that the specified domain is operating within specified constraints. var MINIMUM_DESKTOP_FRAMERATE = 57; // frames per secondvar MINIMUM_HMD_FRAMERATE = 86;var EXPECTED_DESKTOP_FRAMERATE = 60;var EXPECTED_HMD_FRAMERATE = 90;var NOMINAL_LOAD_TIME = 30;         // secondsvar MAXIMUM_LOAD_TIME = NOMINAL_LOAD_TIME * 2;var MINIMUM_AVATARS = 25; // changeable by prompt // If we add or remove things too quickly, we get problems (e.g., audio, fogbugz 2095).// For now, spread them out this timing apart.var SPREAD_TIME_MS = 500; var DENSITY = 0.3; // square meters per person. Some say 10 sq ft is arm's length (0.9m^2), 4.5 is crowd (0.4m^2), 2.5 is mosh pit (0.2m^2).var SOUND_DATA = {url: Script.getExternalPath(Script.ExternalPaths.HF_Content, "/howard/sounds/piano1.wav"};var AVATARS_CHATTERING_AT_ONCE = 4; // How many of the agents should we request to play SOUND at once.var NEXT_SOUND_SPREAD = 500; // millisecond range of how long to wait after one sound finishes, before playing the nextvar ANIMATION_DATA = {    "url": Script.getExternalPath(Script.ExternalPaths.HF_Content, "/howard/resources/avatar/animations/idle.fbx"),    // "url": Script.getExternalPath(Script.ExternalPaths.HF_Content, "/howard/resources/avatar/animations/walk_fwd.fbx"), // alternative example    "startFrame": 0.0,    "endFrame": 300.0,    "timeScale": 1.0,    "loopFlag": true}; var version = 4;function debug() {    print.apply(null, [].concat.apply(['hrs fixme', version], [].map.call(arguments, JSON.stringify)));} function canonicalizePlacename(name) {    var prefix = 'dev-';    name = name.toLowerCase();    if (name.indexOf(prefix) === 0) {        name = name.slice(prefix.length);    }    return name;}var cachePlaces = ['localhost', 'welcome'].map(canonicalizePlacename); // For now, list the lighter weight one first.var defaultPlace = location.hostname;var prompt = "domain-check.js version " + version + "\n\nWhat place should we enter?";debug(cachePlaces, defaultPlace, prompt);var entryPlace = Window.prompt(prompt, defaultPlace);var runTribbles = Window.confirm("Run tribbles?\n\n\At most, only one participant should say yes.");MINIMUM_AVATARS = parseInt(Window.prompt("Total avatars (including yourself and any already present)?", MINIMUM_AVATARS.toString()) || "0", 10);AVATARS_CHATTERING_AT_ONCE = MINIMUM_AVATARS ? parseInt(Window.prompt("Number making sound?", Math.min(MINIMUM_AVATARS - 1, AVATARS_CHATTERING_AT_ONCE).toString()) || "0", 10) : 0; function placesMatch(a, b) { // handling case and 'dev-' variations    return canonicalizePlacename(a) === canonicalizePlacename(b);}function isNowIn(place) { // true if currently in specified place    placesMatch(location.hostname, place);} function go(place) { // handle (dev-)welcome in the appropriate version-specific way    debug('go', place);    if (placesMatch(place, 'welcome')) {        location.goToEntry();    } else {        location.handleLookupString(place);    }} var spread = Math.sqrt(MINIMUM_AVATARS * DENSITY); // metersvar turnSpread = 90; // How many degrees should turn from front range over. function coord() { return (Math.random() * spread) - (spread / 2); }  // randomly distribute a coordinate zero += spread/2.function contains(array, item) { return array.indexOf(item) >= 0; }function without(array, itemsToRemove) { return array.filter(function (item) { return !contains(itemsToRemove, item); }); }function nextAfter(array, id) { // Wrapping next element in array after id.    var index = array.indexOf(id) + 1;    return array[(index >= array.length) ? 0 : index];} var summonedAgents = [];var chattering = [];var accumulatedDelay = 0;var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";function messageSend(message) {    Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));}function messageHandler(channel, messageString, senderID) {    if (channel !== MESSAGE_CHANNEL) {        return;    }    debug('message', channel, messageString, senderID);    if (MyAvatar.sessionUUID === senderID) { // ignore my own        return;    }    var message = {}, avatarIdentifiers;    try {        message = JSON.parse(messageString);    } catch (e) {		    }    switch (message.key) {    case "hello":        Script.setTimeout(function () {            // There can be avatars we've summoned that do not yet appear in the AvatarList.            avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents);            debug('present', avatarIdentifiers, summonedAgents);            if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS) {                var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE;                if (chatter) {                    chattering.push(senderID);                }                summonedAgents.push(senderID);                messageSend({                    key: 'SUMMON',                    rcpt: senderID,                    position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}),                    orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0),                    soundData: chatter && SOUND_DATA,                    listen: true,                    skeletonModelURL: Script.getExternalPath(Script.ExternalPaths.HF_Content, "/howard/resources/meshes/defaultAvatar_full.fst"),                    animationData: ANIMATION_DATA                });            }        }, accumulatedDelay);        accumulatedDelay += SPREAD_TIME_MS; // assume we'll get all the hello respsponses more or less together.        break;    case "finishedSound": // Give someone else a chance.        chattering = without(chattering, [senderID]);        Script.setTimeout(function () {            messageSend({                key: 'SUMMON',                rcpt: nextAfter(without(summonedAgents, chattering), senderID),                soundData: SOUND_DATA            });        }, Math.random() * NEXT_SOUND_SPREAD);        break;    case "HELO":        Window.alert("Someone else is summoning avatars.");        break;    default:        print("crowd-agent received unrecognized message");    }}Messages.subscribe(MESSAGE_CHANNEL);Messages.messageReceived.connect(messageHandler);Script.scriptEnding.connect(function () {    debug('stopping agents', summonedAgents);    Messages.messageReceived.disconnect(messageHandler); // don't respond to any messages during shutdown    accumulatedDelay = 0;    summonedAgents.forEach(function (id) {        messageSend({key: 'STOP', rcpt: id, delay: accumulatedDelay});        accumulatedDelay += SPREAD_TIME_MS;    });    debug('agents stopped');    Messages.unsubscribe(MESSAGE_CHANNEL);    debug('unsubscribed');}); var fail = false, results = "";function addResult(label, actual, nominal, minimum, maximum) {    if ((minimum !== undefined) && (actual < minimum)) {        fail = ' FAILED: ' + label + ' below ' + minimum;    }    if ((maximum !== undefined) && (actual > maximum)) {        fail = ' FAILED: ' + label + ' above ' + maximum;    }    results += "\n" + label + ": " + actual.toFixed(0) + " (" + ((100 * actual) / nominal).toFixed(0) + "%)";}function giveReport() {    Window.alert(entryPlace + (fail || " OK") + "\n" + results + "\nwith " + summonedAgents.length + " avatars added,\nand " + AVATARS_CHATTERING_AT_ONCE + " making noise.");} // Tests are performed domain-wide, at full LODvar initialLodIsAutomatic = LODManager.getAutomaticLODAdjust();var LOD = 32768 * 400;LODManager.setAutomaticLODAdjust(false);LODManager.setOctreeSizeScale(LOD);Script.scriptEnding.connect(function () { LODManager.setAutomaticLODAdjust(initialLodIsAutomatic); }); function startTwirl(targetRotation, degreesPerUpdate, interval, strafeDistance, optionalCallback) {    var initialRotation = Quat.safeEulerAngles(MyAvatar.orientation).y;    var accumulatedRotation = 0;    function tick() {        MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, accumulatedRotation + initialRotation, 0);        if (strafeDistance) {            MyAvatar.position = Vec3.sum(MyAvatar.position, Vec3.multiply(strafeDistance, Quat.getRight(MyAvatar.orientation)));        }        accumulatedRotation += degreesPerUpdate;        if (accumulatedRotation >= targetRotation) {            return optionalCallback && optionalCallback();        }        Script.setTimeout(tick, interval);    }    tick();} function doLoad(place, continuationWithLoadTime) { // Go to place and call continuationWithLoadTime(loadTimeInSeconds)    var start = Date.now(), timeout, onDownloadUpdate, finishedTwirl = false, loadTime;    // There are two ways to learn of changes: connect to change signals, or poll.    // Until we get reliable results, we'll poll.    var POLL_INTERVAL = 500, poll;    function setHandlers() {        //Stats.downloadsPendingChanged.connect(onDownloadUpdate); downloadsChanged, and physics...        poll = Script.setInterval(onDownloadUpdate, POLL_INTERVAL);    }    function clearHandlers() {        debug('clearHandlers');        //Stats.downloadsPendingChanged.disconnect(onDownloadUpdate); downloadsChanged, and physics..        Script.clearInterval(poll);    }    function waitForLoad(flag) {        debug('entry', place, 'initial downloads/pending', Stats.downloads, Stats.downloadsPending);        location.hostChanged.disconnect(waitForLoad);        timeout = Script.setTimeout(function () {            debug('downloads timeout', Date());            clearHandlers();            Window.alert("Timeout during " + place + " load. FAILED");            Script.stop();        }, MAXIMUM_LOAD_TIME * 1000);        startTwirl(360, 6, 90, null, function () {            finishedTwirl = true;            if (loadTime) {                continuationWithLoadTime(loadTime);            }        });        setHandlers();    }    function isLoading() {        // FIXME: We should also confirm that textures have loaded.        return Stats.downloads || Stats.downloadsPending || !Window.isPhysicsEnabled();    }    onDownloadUpdate = function onDownloadUpdate() {        debug('update downloads/pending', Stats.downloads, Stats.downloadsPending);        if (isLoading()) {            return;        }        Script.clearTimeout(timeout);        clearHandlers();        loadTime = (Date.now() - start) / 1000;        if (finishedTwirl) {            continuationWithLoadTime(loadTime);        }    };     location.hostChanged.connect(waitForLoad);    go(place);} var config = Render.getConfig("Stats");function doRender(continuation) {    var start = Date.now(), frames = 0;    function onNewStats() { // Accumulates frames on signal during load test        frames++;    }    if (MINIMUM_AVATARS) {        messageSend({key: 'HELO'}); // Ask agents to report in now.    }     config.newStats.connect(onNewStats);    startTwirl(720, 1, 20, 0.08, function () {        var end = Date.now();        config.newStats.disconnect(onNewStats);        addResult('frame rate', 1000 * frames / (end - start),                  HMD.active ? EXPECTED_HMD_FRAMERATE : EXPECTED_DESKTOP_FRAMERATE,                  HMD.active ? MINIMUM_HMD_FRAMERATE : MINIMUM_DESKTOP_FRAMERATE);        var total = AvatarList.getAvatarIdentifiers().length;        if (MINIMUM_AVATARS && !fail) {            if (0 === summonedAgents.length) {                fail = "FAIL: No agents reported.\nPlease run " + MINIMUM_AVATARS + " instances of\n\https://cdn-1.vircadia.com/eu-c-1/howard/scripts/tests/performance/crowd-agent.js?v=3\n\on your domain server.";            } else if (total < MINIMUM_AVATARS) {                fail = "FAIL: Only " + summonedAgents.length + " agents reported. Now missing " + (MINIMUM_AVATARS - total) + " avatars, total.";            }        }        continuation();    });} var TELEPORT_PAUSE = 500;function prepareCache(continuation) {    function loadNext() {        var place = cachePlaces.shift();        doLoad(place, function (prepTime) {            debug(place, 'ready', prepTime);            if (cachePlaces.length) {                loadNext();            } else {                continuation();            }        });    }    // remove entryPlace target from cachePlaces    var targetInCache = cachePlaces.indexOf(canonicalizePlacename(entryPlace));    if (targetInCache !== -1) {        cachePlaces.splice(targetInCache, 1);    }    debug('cachePlaces', cachePlaces);    go(cachePlaces[1] || entryPlace); // Not quite right for entryPlace case (allows some qt pre-caching), but close enough.    Script.setTimeout(function () {        Menu.triggerOption("Reload Content (Clears all caches)");        Script.setTimeout(loadNext, TELEPORT_PAUSE);    }, TELEPORT_PAUSE);} function maybeRunTribbles(continuation) {    if (runTribbles) {        Script.load('http://cdn.highfidelity.com/davidkelly/production/scripts/tests/performance/tribbles.js');        Script.setTimeout(continuation, 3000);    } else {        continuation();    }} if (!entryPlace) {    Window.alert("domain-check.js cancelled");    Script.stop();} else {    prepareCache(function (prepTime) {        debug('cache ready', prepTime);        doLoad(entryPlace, function (loadTime) {            addResult("load time", loadTime, NOMINAL_LOAD_TIME, undefined, MAXIMUM_LOAD_TIME);            LODManager.setAutomaticLODAdjust(initialLodIsAutomatic); // after loading, restore lod.            maybeRunTribbles(function () {                doRender(function () {                    giveReport();                    Script.stop();                });            });        });    });} Script.scriptEnding.connect(function () { print("domain-check completed"); });