const path = require('path'); const test = require('tap').test; const makeTestStorage = require('../fixtures/make-test-storage'); const extract = require('../fixtures/extract'); const VirtualMachine = require('../../src/index'); const Thread = require('../../src/engine/thread'); const Runtime = require('../../src/engine/runtime'); const projectUri = path.resolve(__dirname, '../fixtures/loudness-hat-block.sb2'); const project = extract(projectUri); const checkIsHatThread = (t, vm, hatThread) => { t.equal(hatThread.stackClick, false); t.equal(hatThread.updateMonitor, false); const blockContainer = hatThread.target.blocks; const opcode = blockContainer.getOpcode(blockContainer.getBlock(hatThread.topBlock)); t.assert(vm.runtime.getIsEdgeActivatedHat(opcode)); }; const checkIsStackClickThread = (t, vm, stackClickThread) => { t.equal(stackClickThread.stackClick, true); t.equal(stackClickThread.updateMonitor, false); }; /** * loudness-hat-block.sb2 contains a single stack * when loudness > 10 * change color effect by 25 * The intention is to make sure that the hat block condition is evaluated * on each frame. */ test('edge activated hat thread runs once every frame', t => { const vm = new VirtualMachine(); vm.attachStorage(makeTestStorage()); // Start VM, load project, and run t.doesNotThrow(() => { // Note: don't run vm.start(), we handle calling _step() manually in this test vm.runtime.currentStepTime = Runtime.THREAD_STEP_INTERVAL; vm.clear(); vm.setCompatibilityMode(false); vm.setTurboMode(false); vm.loadProject(project).then(() => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); t.equal(vm.runtime.threads.length, 1); checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); // Check that the hat thread is again when another step is taken vm.runtime._step(); // There should now be one done hat thread and one new hat thread to run t.equal(vm.runtime.threads.length, 1); checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); t.end(); }); }); }); /** * If the hat doesn't finish evaluating within one frame, it shouldn't be added again * on the next frame. (We skip execution by setting the step time to 0) */ test('edge activated hat thread not added twice', t => { const vm = new VirtualMachine(); vm.attachStorage(makeTestStorage()); // Start VM, load project, and run t.doesNotThrow(() => { // Note: don't run vm.start(), we handle calling _step() manually in this test vm.runtime.currentStepTime = 0; vm.clear(); vm.setCompatibilityMode(false); vm.setTurboMode(false); vm.loadProject(project).then(() => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); t.equal(vm.runtime.threads.length, 1); const prevThread = vm.runtime.threads[0]; checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0].status === Thread.STATUS_RUNNING); // Check that no new threads are added when another step is taken vm.runtime._step(); // There should now be one done hat thread and one new hat thread to run t.equal(vm.runtime.threads.length, 1); checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0] === prevThread); t.end(); }); }); }); /** * When adding a stack click thread first, make sure that the edge activated hat thread and * the stack click thread are both pushed and run (despite having the same top block) */ test('edge activated hat thread does not interrupt stack click thread', t => { const vm = new VirtualMachine(); vm.attachStorage(makeTestStorage()); // Start VM, load project, and run t.doesNotThrow(() => { // Note: don't run vm.start(), we handle calling _step() manually in this test vm.runtime.currentStepTime = Runtime.THREAD_STEP_INTERVAL; vm.clear(); vm.setCompatibilityMode(false); vm.setTurboMode(false); vm.loadProject(project).then(() => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); t.equal(vm.runtime.threads.length, 1); checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); // Add stack click thread on this hat vm.runtime.toggleScript(vm.runtime.threads[0].topBlock, {stackClick: true}); // Check that the hat thread is again when another step is taken vm.runtime._step(); // There should now be one done hat thread and one new hat thread to run t.equal(vm.runtime.threads.length, 2); let hatThread; let stackClickThread; if (vm.runtime.threads[0].stackClick) { stackClickThread = vm.runtime.threads[0]; hatThread = vm.runtime.threads[1]; } else { stackClickThread = vm.runtime.threads[1]; hatThread = vm.runtime.threads[0]; } checkIsHatThread(t, vm, hatThread); checkIsStackClickThread(t, vm, stackClickThread); t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); t.assert(vm.runtime.threads[1].status === Thread.STATUS_DONE); t.end(); }); }); }); /** * When adding the hat thread first, make sure that the edge activated hat thread and * the stack click thread are both pushed and run (despite having the same top block) */ test('edge activated hat thread does not interrupt stack click thread', t => { const vm = new VirtualMachine(); vm.attachStorage(makeTestStorage()); // Start VM, load project, and run t.doesNotThrow(() => { // Note: don't run vm.start(), we handle calling _step() manually in this test vm.runtime.currentStepTime = 0; vm.clear(); vm.setCompatibilityMode(false); vm.setTurboMode(false); vm.loadProject(project).then(() => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); t.equal(vm.runtime.threads.length, 1); checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0].status === Thread.STATUS_RUNNING); vm.runtime.currentStepTime = Runtime.THREAD_STEP_INTERVAL; // Add stack click thread on this hat vm.runtime.toggleScript(vm.runtime.threads[0].topBlock, {stackClick: true}); // Check that the hat thread is again when another step is taken vm.runtime._step(); // There should now be one done hat thread and one new hat thread to run t.equal(vm.runtime.threads.length, 2); let hatThread; let stackClickThread; if (vm.runtime.threads[0].stackClick) { stackClickThread = vm.runtime.threads[0]; hatThread = vm.runtime.threads[1]; } else { stackClickThread = vm.runtime.threads[1]; hatThread = vm.runtime.threads[0]; } checkIsHatThread(t, vm, hatThread); checkIsStackClickThread(t, vm, stackClickThread); t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); t.assert(vm.runtime.threads[1].status === Thread.STATUS_DONE); t.end(); }); }); });