@dark-engine/core
Version:
The lightweight and powerful UI rendering engine without dependencies and written in TypeScript (Browser, Node.js, Android, iOS, Windows, Linux, macOS)
446 lines (445 loc) • 15 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.detectIsBusy = exports.createUpdate = exports.workLoop = exports.Fiber = void 0;
const platform_1 = require('../platform');
const constants_1 = require('../constants');
const utils_1 = require('../utils');
const scope_1 = require('../scope');
const component_1 = require('../component');
const fiber_1 = require('../fiber');
Object.defineProperty(exports, 'Fiber', {
enumerable: true,
get: function () {
return fiber_1.Fiber;
},
});
const view_1 = require('../view');
const memo_1 = require('../memo');
const walk_1 = require('../walk');
const scheduler_1 = require('../scheduler');
const fragment_1 = require('../fragment');
const unmount_1 = require('../unmount');
function workLoop(isAsync) {
const $scope = (0, scope_1.$$scope)();
const wipFiber = $scope.getWorkInProgress();
const isStream = $scope.getIsStream();
const emitter = $scope.getEmitter();
let unit = $scope.getNextUnitOfWork();
let shouldYield = false;
try {
while (unit && !shouldYield) {
const isDeepWalking = $scope.getMountDeep();
unit = performUnitOfWork(unit, wipFiber, isDeepWalking, isStream, emitter, $scope);
shouldYield = isAsync && scheduler_1.scheduler.shouldYield();
$scope.setUnitOfWork(unit);
if (shouldYield && scheduler_1.scheduler.detectIsTransition() && scheduler_1.scheduler.hasNewTask()) {
fork($scope);
return false;
}
}
if (!unit && wipFiber) {
commit($scope);
}
} catch (error) {
if ((0, utils_1.detectIsPromise)(error)) {
return error;
} else {
const emitter = $scope.getEmitter();
$scope.keepRoot();
emitter.emit('error', (0, utils_1.createError)(error));
if (!isAsync) {
throw error;
} else {
(0, utils_1.logError)(error);
}
return false;
}
}
return Boolean(unit);
}
exports.workLoop = workLoop;
function performUnitOfWork(fiber, wipFiber, isDeepWalking, isStream, emitter, $scope) {
fiber.hook && (fiber.hook.idx = 0);
if (isDeepWalking) {
const children = fiber.inst.children;
if (children && children.length > 0) {
const child = mountChild(fiber, $scope);
isStream && emitter.emit('chunk', child);
return child;
}
}
let parent = fiber.parent;
while (parent && fiber !== wipFiber) {
const next = mountSibling(fiber, $scope);
if (isStream) {
emitter.emit('chunk', fiber);
next && emitter.emit('chunk', next);
}
if (next) return next;
fiber = parent;
parent = fiber.parent;
}
return null;
}
function mountChild(parent, $scope) {
$scope.navToChild();
const children = parent.inst?.children || null;
const inst = children ? setupInstance(children, 0) : null;
const fiber = createFiber(getAlternate(parent, inst, 0, $scope), inst, 0);
fiber.hook = parent.child?.hook || fiber.hook;
fiber.parent = parent;
parent.child = fiber;
fiber.eidx = parent.el ? 0 : parent.eidx;
share(fiber, parent, inst, $scope);
return fiber;
}
function mountSibling(left, $scope) {
$scope.navToSibling();
const idx = $scope.getMountIndex();
const children = left.parent.inst.children;
const inst = children ? setupInstance(children, idx) : null;
if (!inst) {
$scope.navToParent();
$scope.setMountDeep(false);
return null;
}
$scope.setMountDeep(true);
const fiber = createFiber(getAlternate(left, inst, idx, $scope), inst, idx);
fiber.hook = left.next?.hook || fiber.hook;
fiber.parent = left.parent;
left.next = fiber;
fiber.eidx = left.eidx + (left.el ? (left.hook?.getIsPortal() ? 0 : 1) : left.cec);
share(fiber, left, inst, $scope);
return fiber;
}
function setupInstance(children, idx) {
if (!children || idx >= children.length) return null;
const child = children[idx];
let inst = null;
children[idx] = (0, utils_1.detectIsArray)(child)
? (0, fragment_1.Fragment)({ slot: child })
: (0, utils_1.detectIsTextBased)(child)
? (0, view_1.Text)(child)
: child || supportConditional(child);
inst = children[idx];
return inst;
}
function share(fiber, prev, inst, $scope) {
const { alt } = fiber;
const shouldMount = alt && (0, memo_1.detectIsMemo)(inst) ? shouldUpdate(fiber, inst, $scope) : true;
$scope.setCursor(fiber);
fiber.inst = inst;
if (alt && alt.mask & constants_1.MOVE_MASK) {
fiber.mask |= constants_1.MOVE_MASK;
alt.mask &= ~constants_1.MOVE_MASK;
}
fiber.hook && (fiber.hook.owner = fiber);
if (shouldMount) {
fiber.inst = mount(fiber, prev, $scope);
alt && $scope.getReconciler().reconcile(fiber, alt, $scope);
setup(fiber, alt);
} else if (fiber.mask & constants_1.MOVE_MASK) {
fiber.tag = constants_1.UPDATE_EFFECT_TAG;
}
$scope.addCandidate(fiber);
}
function createFiber(alt, next, idx) {
const prev = alt ? alt.inst : null;
const fiber = new fiber_1.Fiber(idx, (0, fiber_1.getHook)(alt, prev, next));
fiber.alt = alt || null;
return fiber;
}
function getAlternate(fiber, inst, idx, $scope) {
const isChild = idx === 0;
const parent = isChild ? fiber : fiber.parent;
if (!fiber.hook?.getIsWip() && parent.tag === constants_1.CREATE_EFFECT_TAG) return null;
const parentId = isChild ? fiber.id : fiber.parent.id;
const key = (0, view_1.getElementKey)(inst);
const store = $scope.getReconciler().get(parentId);
let alt = null;
if (key !== null && store) {
const isMove = store.move && Boolean(store.move[key]);
const isStable = store.stable && Boolean(store.stable[key]);
if (isMove || isStable) {
alt = store.map[key];
isMove && (alt.mask |= constants_1.MOVE_MASK);
}
} else {
if (fiber.alt) {
alt = isChild ? fiber.alt.child : fiber.alt.next;
} else {
alt = store ? store.map[(0, utils_1.createIndexKey)(idx)] || null : null;
}
}
return alt;
}
function setup(fiber, alt) {
const inst = fiber.inst;
let isUpdate = false;
fiber.parent.tag === constants_1.CREATE_EFFECT_TAG && (fiber.tag = fiber.parent.tag);
isUpdate =
alt &&
fiber.tag !== constants_1.CREATE_EFFECT_TAG &&
(0, view_1.detectAreSameInstanceTypes)(alt.inst, inst) &&
(0, view_1.getElementKey)(alt.inst) === (0, view_1.getElementKey)(inst);
fiber.tag = isUpdate ? constants_1.UPDATE_EFFECT_TAG : constants_1.CREATE_EFFECT_TAG;
if (!fiber.el) {
if (isUpdate && alt.el) {
fiber.el = alt.el;
} else if ((0, view_1.detectIsVirtualNode)(fiber.inst)) {
fiber.el = platform_1.platform.createElement(fiber.inst);
}
}
fiber.el && !fiber.hook?.getIsPortal() && fiber.increment();
}
function shouldUpdate(fiber, inst, $scope) {
if (process.env.NODE_ENV !== 'production') {
if ($scope.getIsHot()) return true;
}
const alt = fiber.alt;
const pc = alt.inst;
const nc = inst;
if (nc.type !== pc.type || nc.shouldUpdate(pc.props, nc.props)) return true;
$scope.setMountDeep(false);
fiber.tag = constants_1.SKIP_EFFECT_TAG;
fiber.child = alt.child;
fiber.child.parent = fiber;
fiber.hook = alt.hook;
fiber.cc = alt.cc;
fiber.cec = alt.cec;
alt.el && (fiber.el = alt.el);
const diff = fiber.eidx - alt.eidx;
const deep = diff !== 0;
deep && (0, walk_1.walk)(fiber.child, onWalkInShouldUpdate(diff));
(0, walk_1.notifyParents)(fiber, alt);
return false;
}
const onWalkInShouldUpdate = diff => ($fiber, skip) => {
$fiber.eidx += diff;
if ($fiber.el) return skip();
};
function mount(fiber, prev, $scope) {
let inst = fiber.inst;
const isComponent = (0, component_1.detectIsComponent)(inst);
const component = inst;
if (isComponent) {
try {
let result = component.type(component.props);
if ((0, utils_1.detectIsArray)(result)) {
!(0, fragment_1.detectIsFragment)(component) && (result = (0, fragment_1.Fragment)({ slot: result }));
} else if ((0, utils_1.detectIsTextBased)(result)) {
result = (0, view_1.Text)(result);
}
component.children = result;
} catch (err) {
const isSSR = (0, platform_1.detectIsSSR)();
if ((0, utils_1.detectIsPromise)(err)) {
const promise = err;
const reset = createReset(fiber, prev, $scope);
const boundary = (0, walk_1.resolveBoundary)(fiber);
if (!isSSR) {
const suspense = (0, walk_1.resolveSuspense)(fiber);
if (suspense || boundary) {
$scope.getAwaiter().add(suspense, boundary, promise);
} else {
reset();
throw promise;
}
} else {
reset();
throw promise;
}
} else {
component.children = [];
!isSSR && fiber.setError(err);
}
}
} else if ((0, view_1.detectIsVirtualNodeFactory)(inst)) {
inst = inst();
}
if ((0, view_1.hasChildrenProp)(inst)) {
inst.children = (0, utils_1.detectIsArray)(inst.children) ? inst.children : [inst.children];
isComponent && component.children.length === 0 && component.children.push((0, view_1.createReplacer)());
fiber.cc = inst.children.length;
}
return inst;
}
const createReset = (fiber, prev, $scope) => () => {
if (prev) {
fiber.hook.owner = null;
fiber.hook.idx = 0;
$scope.navToPrev();
$scope.setUnitOfWork(prev);
fiber_1.Fiber.setNextId(prev.id);
} else {
fiber.id = fiber_1.Fiber.incrementId();
fiber.cec = fiber.alt.cec;
}
};
function supportConditional(inst) {
return (0, utils_1.detectIsFalsy)(inst) ? (0, view_1.createReplacer)() : inst;
}
function commit($scope) {
const isStream = $scope.getIsStream();
if (process.env.NODE_ENV !== 'production') {
process.env.NODE_ENV === 'development' && $scope.setIsHot(false);
}
if (isStream) {
platform_1.platform.finishCommit();
cleanup($scope);
} else {
const wip = $scope.getWorkInProgress();
const deletions = $scope.getDeletions();
const candidates = $scope.getCandidates();
const isUpdate = $scope.getIsUpdate();
const awaiter = $scope.getAwaiter();
const inst = wip.inst;
for (const fiber of deletions) {
(0, unmount_1.unmountFiber)(fiber);
fiber.tag = constants_1.DELETE_EFFECT_TAG;
platform_1.platform.commit(fiber);
}
isUpdate && sync(wip);
$scope.runInsertionEffects();
for (const fiber of candidates) {
const item = fiber.inst;
fiber.tag !== constants_1.SKIP_EFFECT_TAG && platform_1.platform.commit(fiber);
fiber.alt = null;
item.children && (item.children = null);
}
wip.alt = null;
wip.hook?.setIsWip(false);
inst.children = null;
platform_1.platform.finishCommit();
$scope.runLayoutEffects();
$scope.runAsyncEffects();
awaiter.resolve();
cleanup($scope);
}
}
function cleanup($scope, fromFork = false) {
$scope.cleanup();
!fromFork && $scope.getEmitter().emit('finish');
$scope.runAfterCommit();
}
function sync(fiber) {
const diff = fiber.cec - fiber.alt.cec;
if (diff === 0) return;
const parent = (0, walk_1.getFiberWithElement)(fiber.parent);
const scope = { isRight: false };
fiber.hook.setIsWip(false);
fiber.increment(diff);
(0, walk_1.walk)(parent.child, onWalkInSync(diff, fiber, scope));
}
const onWalkInSync = (diff, fiber, scope) => ($fiber, skip) => {
if ($fiber === fiber) {
scope.isRight = true;
return skip();
}
$fiber.el && skip();
scope.isRight && ($fiber.eidx += diff);
};
function fork($scope) {
const $fork = $scope.fork();
const wip = $scope.getWorkInProgress();
const onRestore = createOnRestore($fork, wip.child);
const { alt } = wip;
wip.child = alt.child;
wip.cc = alt.cc;
wip.cec = alt.cec;
wip.hook?.setIsWip(false);
wip.alt = null;
wip.hook.idx = 0;
wip.hook.owner = wip;
$scope.runInsertionEffects();
$scope.applyCancels();
cleanup($scope, true);
scheduler_1.scheduler.retain(onRestore);
}
const createOnRestore = ($fork, child) => options => {
const { fiber: wip, setValue, resetValue } = options;
const $scope = (0, scope_1.$$scope)();
(0, utils_1.detectIsFunction)(setValue) && setValue();
(0, utils_1.detectIsFunction)(resetValue) && $fork.addCancel(resetValue);
wip.alt = new fiber_1.Fiber().mutate(wip);
wip.tag = constants_1.UPDATE_EFFECT_TAG;
wip.child = child;
wip.hook?.setIsWip(true);
child.parent = wip;
$fork.setRoot($scope.getRoot());
$fork.setWorkInProgress(wip);
(0, scope_1.replaceScope)($fork);
};
function createCallback(options) {
const { rootId, hook, isTransition, getTools = $getTools } = options;
const callback = onRestore => {
(0, scope_1.setRootId)(rootId);
const isRetain = (0, utils_1.detectIsFunction)(onRestore);
const { shouldUpdate, setValue, resetValue } = getTools();
const $scope = (0, scope_1.$$scope)();
const owner = hook.owner;
const fiber = owner.alt || owner;
const isBroken = !fiber.tag;
if (isBroken || !shouldUpdate() || !(0, walk_1.detectIsFiberAlive)(fiber) || isRetain) {
isRetain && onRestore({ fiber, setValue, resetValue });
return;
}
(0, utils_1.detectIsFunction)(setValue) && setValue();
(0, utils_1.detectIsFunction)(resetValue) && isTransition && $scope.addCancel(resetValue);
fiber.alt = null;
fiber.alt = new fiber_1.Fiber().mutate(fiber);
fiber.tag = constants_1.UPDATE_EFFECT_TAG;
fiber.cc = 0;
fiber.cec = 0;
fiber.child = null;
fiber.hook.setIsWip(true);
hook.idx = 0;
hook.owner = fiber;
$scope.setIsUpdate(true);
$scope.resetMount();
$scope.setWorkInProgress(fiber);
$scope.setCursor(fiber);
fiber.inst = mount(fiber, null, $scope);
$scope.setUnitOfWork(fiber);
};
return callback;
}
function createUpdate(rootId, hook) {
const { idx } = hook;
const update = options => {
const { getTools, setupBatch } = options || {};
const $scope = (0, scope_1.$$scope)();
if ($scope.getIsInsertionEffect()) return;
const isTransition = $scope.getIsTransition();
const isEvent = $scope.getIsEvent();
const priority = isTransition
? constants_1.TaskPriority.LOW
: isEvent
? constants_1.TaskPriority.HIGH
: constants_1.TaskPriority.NORMAL;
const forceAsync = isTransition;
const onTransitionEnd = isTransition ? $scope.getOnTransitionEnd() : null;
const isBatch = $scope.getIsBatch();
const callback = createCallback({ rootId, hook, isTransition, getTools });
const loc = (0, walk_1.createLoc)(rootId, idx, hook);
scheduler_1.scheduler.schedule(callback, {
priority,
forceAsync,
isTransition,
isBatch,
setupBatch,
loc,
onTransitionEnd,
});
};
return update;
}
exports.createUpdate = createUpdate;
const $getTools = () => ({
shouldUpdate: utils_1.trueFn,
setValue: null,
resetValue: null,
});
const detectIsBusy = () => Boolean((0, scope_1.$$scope)()?.getWorkInProgress());
exports.detectIsBusy = detectIsBusy;
//# sourceMappingURL=workloop.js.map