UNPKG

ember-legacy-class-transform

Version:
256 lines 8.19 kB
import { clear, move as moveBounds } from '../bounds'; import { ElementStack } from '../builder'; import { Stack, LinkedList, dict, expect } from '@glimmer/util'; import { IteratorSynchronizer, // Tags combine, UpdatableTag, combineSlice, CONSTANT_TAG, INITIAL } from '@glimmer/reference'; import { UpdatingOpcode } from '../opcodes'; import VM, { EvaluationStack } from './append'; export default class UpdatingVM { constructor(env, { alwaysRevalidate = false }) { this.frameStack = new Stack(); this.env = env; this.constants = env.program.constants; this.dom = env.getDOM(); this.alwaysRevalidate = alwaysRevalidate; } execute(opcodes, handler) { let { frameStack } = this; this.try(opcodes, handler); while (true) { if (frameStack.isEmpty()) break; let opcode = this.frame.nextStatement(); if (opcode === null) { this.frameStack.pop(); continue; } opcode.evaluate(this); } } get frame() { return expect(this.frameStack.current, 'bug: expected a frame'); } goto(op) { this.frame.goto(op); } try(ops, handler) { this.frameStack.push(new UpdatingVMFrame(this, ops, handler)); } throw() { this.frame.handleException(); this.frameStack.pop(); } evaluateOpcode(opcode) { opcode.evaluate(this); } } export class BlockOpcode extends UpdatingOpcode { constructor(start, state, bounds, children) { super(); this.start = start; this.type = "block"; this.next = null; this.prev = null; let { env, scope, dynamicScope, stack } = state; this.children = children; this.env = env; this.scope = scope; this.dynamicScope = dynamicScope; this.stack = stack; this.bounds = bounds; } parentElement() { return this.bounds.parentElement(); } firstNode() { return this.bounds.firstNode(); } lastNode() { return this.bounds.lastNode(); } evaluate(vm) { vm.try(this.children, null); } destroy() { this.bounds.destroy(); } didDestroy() { this.env.didDestroy(this.bounds); } toJSON() { let details = dict(); details["guid"] = `${this._guid}`; return { guid: this._guid, type: this.type, details, children: this.children.toArray().map(op => op.toJSON()) }; } } export class TryOpcode extends BlockOpcode { constructor(start, state, bounds, children) { super(start, state, bounds, children); this.type = "try"; this.tag = this._tag = UpdatableTag.create(CONSTANT_TAG); } didInitializeChildren() { this._tag.inner.update(combineSlice(this.children)); } evaluate(vm) { vm.try(this.children, this); } handleException() { let { env, bounds, children, scope, dynamicScope, start, stack, prev, next } = this; children.clear(); let elementStack = ElementStack.resume(env, bounds, bounds.reset(env)); let vm = new VM(env, scope, dynamicScope, elementStack); let updating = new LinkedList(); vm.execute(start, vm => { vm.stack = EvaluationStack.restore(stack); vm.updatingOpcodeStack.push(updating); vm.updateWith(this); vm.updatingOpcodeStack.push(children); }); this.prev = prev; this.next = next; } toJSON() { let json = super.toJSON(); let details = json["details"]; if (!details) { details = json["details"] = {}; } return super.toJSON(); } } class ListRevalidationDelegate { constructor(opcode, marker) { this.opcode = opcode; this.marker = marker; this.didInsert = false; this.didDelete = false; this.map = opcode.map; this.updating = opcode['children']; } insert(key, item, memo, before) { let { map, opcode, updating } = this; let nextSibling = null; let reference = null; if (before) { reference = map[before]; nextSibling = reference['bounds'].firstNode(); } else { nextSibling = this.marker; } let vm = opcode.vmForInsertion(nextSibling); let tryOpcode = null; let { start } = opcode; vm.execute(start, vm => { map[key] = tryOpcode = vm.iterate(memo, item); vm.updatingOpcodeStack.push(new LinkedList()); vm.updateWith(tryOpcode); vm.updatingOpcodeStack.push(tryOpcode.children); }); updating.insertBefore(tryOpcode, reference); this.didInsert = true; } retain(_key, _item, _memo) {} move(key, _item, _memo, before) { let { map, updating } = this; let entry = map[key]; let reference = map[before] || null; if (before) { moveBounds(entry, reference.firstNode()); } else { moveBounds(entry, this.marker); } updating.remove(entry); updating.insertBefore(entry, reference); } delete(key) { let { map } = this; let opcode = map[key]; opcode.didDestroy(); clear(opcode); this.updating.remove(opcode); delete map[key]; this.didDelete = true; } done() { this.opcode.didInitializeChildren(this.didInsert || this.didDelete); } } export class ListBlockOpcode extends BlockOpcode { constructor(start, state, bounds, children, artifacts) { super(start, state, bounds, children); this.type = "list-block"; this.map = dict(); this.lastIterated = INITIAL; this.artifacts = artifacts; let _tag = this._tag = UpdatableTag.create(CONSTANT_TAG); this.tag = combine([artifacts.tag, _tag]); } didInitializeChildren(listDidChange = true) { this.lastIterated = this.artifacts.tag.value(); if (listDidChange) { this._tag.inner.update(combineSlice(this.children)); } } evaluate(vm) { let { artifacts, lastIterated } = this; if (!artifacts.tag.validate(lastIterated)) { let { bounds } = this; let { dom } = vm; let marker = dom.createComment(''); dom.insertAfter(bounds.parentElement(), marker, expect(bounds.lastNode(), "can't insert after an empty bounds")); let target = new ListRevalidationDelegate(this, marker); let synchronizer = new IteratorSynchronizer({ target, artifacts }); synchronizer.sync(); this.parentElement().removeChild(marker); } // Run now-updated updating opcodes super.evaluate(vm); } vmForInsertion(nextSibling) { let { env, scope, dynamicScope } = this; let elementStack = ElementStack.forInitialRender(this.env, this.bounds.parentElement(), nextSibling); return new VM(env, scope, dynamicScope, elementStack); } toJSON() { let json = super.toJSON(); let map = this.map; let inner = Object.keys(map).map(key => { return `${JSON.stringify(key)}: ${map[key]._guid}`; }).join(", "); let details = json["details"]; if (!details) { details = json["details"] = {}; } details["map"] = `{${inner}}`; return json; } } class UpdatingVMFrame { constructor(vm, ops, exceptionHandler) { this.vm = vm; this.ops = ops; this.exceptionHandler = exceptionHandler; this.vm = vm; this.ops = ops; this.current = ops.head(); } goto(op) { this.current = op; } nextStatement() { let { current, ops } = this; if (current) this.current = ops.nextNode(current); return current; } handleException() { if (this.exceptionHandler) { this.exceptionHandler.handleException(); } } }