// entityEditStressTest.js // // Created by Seiji Emery on 8/31/15 // Copyright 2015 High Fidelity, Inc. // // Stress tests the client + server-side entity trees by spawning huge numbers of entities in // close proximity to your avatar and updating them continuously (ie. applying position edits), // with the intent of discovering crashes and other bugs related to the entity, scripting, // rendering, networking, and/or physics subsystems. // // This script was originally created to find + diagnose an a clientside crash caused by improper // locking of the entity tree, but can be reused for other purposes. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // ENTITY_SPAWNER = function (properties) { properties = properties || {}; var RADIUS = properties.radius || 5.0; // Spawn within this radius (square) var Y_OFFSET = properties.yOffset || 1.5; // Spawn at an offset below the avatar var TEST_ENTITY_NAME = properties.entityName || "EntitySpawnTest"; var NUM_ENTITIES = properties.count || 1000; // number of entities to spawn var ENTITY_SPAWN_LIMIT = properties.spawnLimit || 100; var ENTITY_SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 1.0; var UPDATE_INTERVAL = properties.updateInterval || properties.interval || 0.1; // Re-randomize the entity's position every x seconds / ms var ENTITY_LIFETIME = properties.lifetime || 30; // Entity timeout (when/if we crash, we need the entities to delete themselves) var KEEPALIVE_INTERVAL = properties.keepAlive || 5; // Refreshes the timeout every X seconds var UPDATES = properties.updates || false var SHAPES = properties.shapes || ["Icosahedron", "Tetrahedron", "Cube", "Sphere" ]; function makeEntity(properties) { var entity = Entities.addEntity(properties); return { update: function (properties) { Entities.editEntity(entity, properties); }, destroy: function () { Entities.deleteEntity(entity) }, getAge: function () { return Entities.getEntityProperties(entity).age; } }; } function randomPositionXZ(center, radius) { return { x: center.x + (Math.random() * radius * 2.0) - radius, y: center.y, z: center.z + (Math.random() * radius * 2.0) - radius }; } function randomPosition(center, radius) { return { x: center.x + (Math.random() * radius * 2.0) - radius, y: center.y + (Math.random() * radius * 2.0) - radius, z: center.z + (Math.random() * radius * 2.0) - radius }; } function randomColor() { return { red: Math.floor(Math.random() * 255), green: Math.floor(Math.random() * 255), blue: Math.floor(Math.random() * 255), }; } function randomDimensions() { return { x: 0.1 + Math.random() * 0.5, y: 0.1 + Math.random() * 0.1, z: 0.1 + Math.random() * 0.5 }; } var entities = []; var entitiesToCreate = 0; var entitiesSpawned = 0; var spawnTimer = 0.0; var keepAliveTimer = 0.0; var updateTimer = 0.0; function clear () { var ids = Entities.findEntities(MyAvatar.position, 50); var that = this; ids.forEach(function(id) { var properties = Entities.getEntityProperties(id); if (properties.name == TEST_ENTITY_NAME) { Entities.deleteEntity(id); } }, this); } function createEntities () { print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")"); entitiesToCreate = NUM_ENTITIES; Script.update.connect(spawnEntities); } function spawnEntities (dt) { if (entitiesToCreate <= 0) { Script.update.disconnect(spawnEntities); print("Finished spawning entities"); } else if ((spawnTimer -= dt) < 0.0){ spawnTimer = ENTITY_SPAWN_INTERVAL; var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT); print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")"); entitiesToCreate -= n; var center = MyAvatar.position; center.y -= Y_OFFSET; for (; n > 0; --n) { entities.push(makeEntity({ type: "Shape", shape: SHAPES[n % SHAPES.length], name: TEST_ENTITY_NAME, position: randomPosition(center, RADIUS), color: randomColor(), dimensions: randomDimensions(), lifetime: ENTITY_LIFETIME })); } } } function despawnEntities () { print("despawning entities"); entities.forEach(function (entity) { entity.destroy(); }); entities = []; } // Runs the following entity updates: // a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and // b) re-randomizes its position every UPDATE_INTERVAL seconds. // This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again). function updateEntities (dt) { var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false; var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false; if (updateLifetime || updateProperties) { var center = MyAvatar.position; center.y -= Y_OFFSET; entities.forEach((updateLifetime && updateProperties && function (entity) { entity.update({ lifetime: entity.getAge() + ENTITY_LIFETIME, position: randomPosition(center, RADIUS) }); }) || (updateLifetime && function (entity) { entity.update({ lifetime: entity.getAge() + ENTITY_LIFETIME }); }) || (updateProperties && function (entity) { entity.update({ position: randomPosition(center, RADIUS) }); }) || null, this); } } function init () { Script.update.disconnect(init); clear(); createEntities(); Script.update.connect(updateEntities); Script.scriptEnding.connect(despawnEntities); } Script.update.connect(init); };