mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 23:12:24 -05:00
6b7582f1c7
- Add stackFrame.justReported Report the block result to a known key `justReported` instead of a dynamic key on `reported`. Assuming blocks with a promised value are relatively infrequent the most common recursive input flow immediately reads the value "just" reported. In the assumed uncommon case of a promised thread status, empty the already argValues assigned values onto the currentStackFrame's reported member. In the next execute call on this stackFrame, values assigned to reported are read back off onto argValues, and execute will returned to the assumed common case. This is a safe assumption since a thread in the promise state will not exit that state until the next frame when javascript has a chance to call the resolve handle, setting the thread's state back to another executable state. Using direct assignment to `justReported` saves building an object dynamically. Instead of always building `reported` and `argValues` only `argValues` is built until a promised state is reached. This also gives a known time when `reported` is used, allowing cleanup of a stackFrame's reported to only happen when it was used to persist already reported values.
279 lines
7.5 KiB
JavaScript
279 lines
7.5 KiB
JavaScript
const test = require('tap').test;
|
|
const Thread = require('../../src/engine/thread');
|
|
const RenderedTarget = require('../../src/sprites/rendered-target');
|
|
const Sprite = require('../../src/sprites/sprite');
|
|
|
|
test('spec', t => {
|
|
t.type(Thread, 'function');
|
|
|
|
const th = new Thread('arbitraryString');
|
|
t.type(th, 'object');
|
|
t.ok(th instanceof Thread);
|
|
t.type(th.pushStack, 'function');
|
|
t.type(th.reuseStackForNextBlock, 'function');
|
|
t.type(th.popStack, 'function');
|
|
t.type(th.stopThisScript, 'function');
|
|
t.type(th.peekStack, 'function');
|
|
t.type(th.peekStackFrame, 'function');
|
|
t.type(th.peekParentStackFrame, 'function');
|
|
t.type(th.pushReportedValue, 'function');
|
|
t.type(th.pushParam, 'function');
|
|
t.type(th.peekStack, 'function');
|
|
t.type(th.getParam, 'function');
|
|
t.type(th.atStackTop, 'function');
|
|
t.type(th.goToNextBlock, 'function');
|
|
t.type(th.isRecursiveCall, 'function');
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('pushStack', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('popStack', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
t.strictEquals(th.popStack(), 'arbitraryString');
|
|
t.strictEquals(th.popStack(), undefined);
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('atStackTop', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
th.pushStack('secondString');
|
|
t.strictEquals(th.atStackTop(), false);
|
|
th.popStack();
|
|
t.strictEquals(th.atStackTop(), true);
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('reuseStackForNextBlock', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
th.reuseStackForNextBlock('secondString');
|
|
t.strictEquals(th.popStack(), 'secondString');
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('peekStackFrame', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
t.strictEquals(th.peekStackFrame().warpMode, false);
|
|
th.popStack();
|
|
t.strictEquals(th.peekStackFrame(), null);
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('peekParentStackFrame', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
th.peekStackFrame().warpMode = true;
|
|
t.strictEquals(th.peekParentStackFrame(), null);
|
|
th.pushStack('secondString');
|
|
t.strictEquals(th.peekParentStackFrame().warpMode, true);
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('pushReportedValue', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
th.pushStack('secondString');
|
|
th.pushReportedValue('value');
|
|
t.strictEquals(th.peekParentStackFrame().justReported, 'value');
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('peekStack', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
t.strictEquals(th.peekStack(), 'arbitraryString');
|
|
th.popStack();
|
|
t.strictEquals(th.peekStack(), null);
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('PushGetParam', t => {
|
|
const th = new Thread('arbitraryString');
|
|
th.pushStack('arbitraryString');
|
|
th.pushParam('testParam', 'testValue');
|
|
t.strictEquals(th.peekStackFrame().params.testParam, 'testValue');
|
|
t.strictEquals(th.getParam('testParam'), 'testValue');
|
|
// Params outside of define stack always evaluate to null
|
|
t.strictEquals(th.getParam('nonExistentParam'), null);
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('goToNextBlock', t => {
|
|
const th = new Thread('arbitraryString');
|
|
const s = new Sprite();
|
|
const rt = new RenderedTarget(s, null);
|
|
const block1 = {fields: Object,
|
|
id: 'arbitraryString',
|
|
inputs: Object,
|
|
STEPS: Object,
|
|
block: 'fakeBlock',
|
|
name: 'STEPS',
|
|
next: 'secondString',
|
|
opcode: 'motion_movesteps',
|
|
parent: null,
|
|
shadow: false,
|
|
topLevel: true,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
const block2 = {fields: Object,
|
|
id: 'secondString',
|
|
inputs: Object,
|
|
STEPS: Object,
|
|
block: 'fakeBlock',
|
|
name: 'STEPS',
|
|
next: null,
|
|
opcode: 'procedures_call',
|
|
mutation: {proccode: 'fakeCode'},
|
|
parent: null,
|
|
shadow: false,
|
|
topLevel: true,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
rt.blocks.createBlock(block1);
|
|
rt.blocks.createBlock(block2);
|
|
rt.blocks.createBlock(block2);
|
|
th.target = rt;
|
|
|
|
t.strictEquals(th.peekStack(), null);
|
|
th.pushStack('secondString');
|
|
t.strictEquals(th.peekStack(), 'secondString');
|
|
th.goToNextBlock();
|
|
t.strictEquals(th.peekStack(), null);
|
|
th.pushStack('secondString');
|
|
th.pushStack('arbitraryString');
|
|
t.strictEquals(th.peekStack(), 'arbitraryString');
|
|
th.goToNextBlock();
|
|
t.strictEquals(th.peekStack(), 'secondString');
|
|
th.goToNextBlock();
|
|
t.strictEquals(th.peekStack(), null);
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('stopThisScript', t => {
|
|
const th = new Thread('arbitraryString');
|
|
const s = new Sprite();
|
|
const rt = new RenderedTarget(s, null);
|
|
const block1 = {fields: Object,
|
|
id: 'arbitraryString',
|
|
inputs: Object,
|
|
STEPS: Object,
|
|
block: 'fakeBlock',
|
|
name: 'STEPS',
|
|
next: null,
|
|
opcode: 'motion_movesteps',
|
|
parent: null,
|
|
shadow: false,
|
|
topLevel: true,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
const block2 = {fields: Object,
|
|
id: 'secondString',
|
|
inputs: Object,
|
|
STEPS: Object,
|
|
block: 'fakeBlock',
|
|
name: 'STEPS',
|
|
next: null,
|
|
opcode: 'procedures_call',
|
|
mutation: {proccode: 'fakeCode'},
|
|
parent: null,
|
|
shadow: false,
|
|
topLevel: true,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
rt.blocks.createBlock(block1);
|
|
rt.blocks.createBlock(block2);
|
|
th.target = rt;
|
|
|
|
th.stopThisScript();
|
|
t.strictEquals(th.peekStack(), null);
|
|
th.pushStack('arbitraryString');
|
|
t.strictEquals(th.peekStack(), 'arbitraryString');
|
|
th.stopThisScript();
|
|
t.strictEquals(th.peekStack(), null);
|
|
th.pushStack('arbitraryString');
|
|
th.pushStack('secondString');
|
|
th.stopThisScript();
|
|
t.strictEquals(th.peekStack(), 'secondString');
|
|
|
|
t.end();
|
|
});
|
|
|
|
test('isRecursiveCall', t => {
|
|
const th = new Thread('arbitraryString');
|
|
const s = new Sprite();
|
|
const rt = new RenderedTarget(s, null);
|
|
const block1 = {fields: Object,
|
|
id: 'arbitraryString',
|
|
inputs: Object,
|
|
STEPS: Object,
|
|
block: 'fakeBlock',
|
|
name: 'STEPS',
|
|
next: null,
|
|
opcode: 'motion_movesteps',
|
|
parent: null,
|
|
shadow: false,
|
|
topLevel: true,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
const block2 = {fields: Object,
|
|
id: 'secondString',
|
|
inputs: Object,
|
|
STEPS: Object,
|
|
block: 'fakeBlock',
|
|
name: 'STEPS',
|
|
next: null,
|
|
opcode: 'procedures_call',
|
|
mutation: {proccode: 'fakeCode'},
|
|
parent: null,
|
|
shadow: false,
|
|
topLevel: true,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
rt.blocks.createBlock(block1);
|
|
rt.blocks.createBlock(block2);
|
|
th.target = rt;
|
|
|
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
|
th.pushStack('secondString');
|
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
|
th.pushStack('arbitraryString');
|
|
t.strictEquals(th.isRecursiveCall('fakeCode'), true);
|
|
th.pushStack('arbitraryString');
|
|
t.strictEquals(th.isRecursiveCall('fakeCode'), true);
|
|
th.popStack();
|
|
t.strictEquals(th.isRecursiveCall('fakeCode'), true);
|
|
th.popStack();
|
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
|
th.popStack();
|
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
|
|
|
t.end();
|
|
});
|