Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/vm/src/engine/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Sequencer from './sequencer';
import execute from './execute';
import ScratchBlocksConstants from './scratch-blocks-constants';
import TargetType from '../extension-support/target-type';
import Thread from './thread';
import Thread, {clearStackFrameFreeList} from './thread';
import log from '../util/log';
import maybeFormatMessage from '../util/maybe-format-message';
import StageLayering from './stage-layering';
Expand Down Expand Up @@ -2059,6 +2059,7 @@ class Runtime extends EventEmitter<RuntimeEvents> {
});

this.targets.map(this.disposeTarget, this);
clearStackFrameFreeList();
this._monitorState = OrderedMap({});
this.emit(Runtime.RUNTIME_DISPOSED);
this.ioDevices.clock.resetProjectTimer();
Expand Down Expand Up @@ -2163,6 +2164,7 @@ class Runtime extends EventEmitter<RuntimeEvents> {
if (disposingTarget !== target) return true;
// Allow target to do dispose actions.
target.dispose();
clearStackFrameFreeList(disposingTarget);
// Remove from list of targets.
return false;
});
Expand Down
36 changes: 35 additions & 1 deletion packages/vm/src/engine/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,25 @@ class _StackFrame {
* A context passed to block implementations.
*/
executionContext: unknown = null;
/**
* The target of blocks that this stack frame will execute.
* Stored as a weak reference to make GC happy.
*/
#target: WeakRef<RenderedTarget> | null = null;

/**
* The target of blocks that this thread will execute.
*
* Note that the stack frame only holds the weak reference to the target,
* so it may be null if the target has been garbage collected.
*/
target: RenderedTarget | null = null;
get target (): RenderedTarget | null {
return this.#target?.deref() ?? null;
}

set target (value: RenderedTarget | null) {
this.#target = value ? new WeakRef(value) : null;
}

/**
* @param warpMode Whether this level is in warp mode. Is set by some legacy blocks and
Expand Down Expand Up @@ -412,6 +427,25 @@ class Thread {
}
}

/**
* Clear the stack frame recycle bin.
* @param target If provided, only remove frames referencing this target or whose target is no longer alive.
* If falsy, clear the entire free list.
*/
function clearStackFrameFreeList (target?: RenderedTarget) {
if (!target) {
_stackFrameFreeList.length = 0;
return;
}
for (let i = _stackFrameFreeList.length - 1; i >= 0; --i) {
const frameTarget = _stackFrameFreeList[i].target;
if (!frameTarget || frameTarget === target) {
_stackFrameFreeList.splice(i, 1);
}
}
}

export type {Thread, _StackFrame as ThreadStackFrame};
export {clearStackFrameFreeList};

export default Thread;
Loading