mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-31 12:48:05 +00:00
将 collectItems/removeTargetsDep 改为整棵树只遍历一次、在每个属性上检查所有 target,把结构遍历开销从 ×targets 降到 ×1,收集结果保持一致。 同时修正 dataSourceMethodDeps 字段命名并补充到 MApp schema。 Co-authored-by: Cursor <cursoragent@cursor.com>
313 lines
8.9 KiB
TypeScript
313 lines
8.9 KiB
TypeScript
/*
|
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
*
|
|
* Copyright (C) 2025 Tencent. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
import { reactive, shallowReactive } from 'vue';
|
|
import { throttle } from 'lodash-es';
|
|
import serialize from 'serialize-javascript';
|
|
|
|
import type { DepData, DepExtendedData, Id, MApp, MNode, Target, TargetNode } from '@tmagic/core';
|
|
import { DepTargetType, traverseTarget, Watcher } from '@tmagic/core';
|
|
import { isPage } from '@tmagic/utils';
|
|
|
|
import { IdleTask } from '@editor/utils/dep/idle-task';
|
|
import Work from '@editor/utils/dep/worker.ts?worker&inline';
|
|
|
|
import BaseService from './BaseService';
|
|
|
|
export interface DepEvents {
|
|
'add-target': [target: Target];
|
|
'remove-target': [id: string | number, type: string | DepTargetType];
|
|
collected: [nodes: MNode[], deep: boolean];
|
|
'ds-collected': [nodes: MNode[], deep: boolean];
|
|
}
|
|
|
|
interface State {
|
|
collecting: boolean;
|
|
taskLength: number;
|
|
}
|
|
|
|
type StateKey = keyof State;
|
|
|
|
class Dep extends BaseService {
|
|
private state = shallowReactive<State>({
|
|
collecting: false,
|
|
taskLength: 0,
|
|
});
|
|
|
|
private idleTask = new IdleTask<{ node: TargetNode; deep: boolean; target: Target }>();
|
|
|
|
private watcher = new Watcher({ initialTargets: reactive({}) });
|
|
|
|
private waitingWorker?: Promise<void>;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.idleTask.on(
|
|
'update-task-length',
|
|
throttle(({ length }) => {
|
|
this.set('taskLength', length);
|
|
}, 1000),
|
|
);
|
|
}
|
|
|
|
public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
|
|
this.state[name] = value;
|
|
}
|
|
|
|
public get<K extends StateKey>(name: K): State[K] {
|
|
return this.state[name];
|
|
}
|
|
|
|
public removeTargets(type: string = DepTargetType.DEFAULT) {
|
|
this.watcher.removeTargets(type);
|
|
|
|
const targets = this.watcher.getTargets(type);
|
|
|
|
if (!targets) return;
|
|
|
|
for (const target of Object.values(targets)) {
|
|
this.emit('remove-target', target.id, type);
|
|
}
|
|
}
|
|
|
|
public getTargets(type: string = DepTargetType.DEFAULT) {
|
|
return this.watcher.getTargets(type);
|
|
}
|
|
|
|
public getTarget(id: Id, type: string = DepTargetType.DEFAULT) {
|
|
return this.watcher.getTarget(id, type);
|
|
}
|
|
|
|
public addTarget(target: Target) {
|
|
this.watcher.addTarget(target);
|
|
this.emit('add-target', target);
|
|
}
|
|
|
|
public removeTarget(id: Id, type: string = DepTargetType.DEFAULT) {
|
|
this.watcher.removeTarget(id, type);
|
|
this.emit('remove-target', id, type);
|
|
}
|
|
|
|
public clearTargets() {
|
|
this.watcher.clearTargets();
|
|
}
|
|
|
|
public collect(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
|
|
this.set('collecting', true);
|
|
|
|
const targets = this.watcher.getCollectableTargets(type);
|
|
|
|
if (targets.length) {
|
|
for (const node of nodes) {
|
|
// 先删除原有依赖,再批量收集
|
|
if (isPage(node)) {
|
|
for (const target of targets) {
|
|
this.removePageDep(target, depExtendedData);
|
|
}
|
|
} else {
|
|
this.watcher.removeTargetsDep(targets, node);
|
|
}
|
|
this.watcher.collectItems(node, targets, depExtendedData, deep);
|
|
}
|
|
}
|
|
|
|
this.set('collecting', false);
|
|
|
|
this.emit('collected', nodes, deep);
|
|
this.emit('ds-collected', nodes, deep);
|
|
}
|
|
|
|
public async collectIdle(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
|
|
if (this.waitingWorker) {
|
|
await this.waitingWorker;
|
|
}
|
|
|
|
this.set('collecting', true);
|
|
let startTask = false;
|
|
this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
|
|
startTask = true;
|
|
|
|
this.enqueueTask(node, target, depExtendedData, deep);
|
|
});
|
|
|
|
return new Promise<void>((resolve) => {
|
|
if (!startTask) {
|
|
this.emit('collected', nodes, deep);
|
|
this.set('collecting', false);
|
|
resolve();
|
|
return;
|
|
}
|
|
this.idleTask.once('finish', () => {
|
|
this.emit('collected', nodes, deep);
|
|
this.set('collecting', false);
|
|
});
|
|
this.idleTask.once('hight-level-finish', () => {
|
|
this.emit('ds-collected', nodes, deep);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
public collectByWorker(dsl: MApp) {
|
|
this.set('collecting', true);
|
|
|
|
const { promise, resolve: waitingResolve } = Promise.withResolvers<void>();
|
|
|
|
this.waitingWorker = promise;
|
|
|
|
return new Promise<Record<string, Record<string, DepData>>>((resolve) => {
|
|
const worker = new Work();
|
|
worker.postMessage({
|
|
dsl: serialize(dsl),
|
|
});
|
|
worker.onmessage = (e) => {
|
|
resolve(e.data);
|
|
};
|
|
worker.onerror = () => {
|
|
resolve({});
|
|
};
|
|
}).then((depsData) => {
|
|
traverseTarget(this.watcher.getTargetsList(), (target) => {
|
|
if (depsData[target.type]?.[target.id]) {
|
|
target.deps = reactive(depsData[target.type][target.id]);
|
|
|
|
if (target.type === DepTargetType.DATA_SOURCE && dsl.dataSourceDeps) {
|
|
dsl.dataSourceDeps[target.id] = target.deps;
|
|
} else if (target.type === DepTargetType.DATA_SOURCE_COND && dsl.dataSourceCondDeps) {
|
|
dsl.dataSourceCondDeps[target.id] = target.deps;
|
|
} else if (target.type === DepTargetType.DATA_SOURCE_METHOD && dsl.dataSourceMethodDeps) {
|
|
dsl.dataSourceMethodDeps[target.id] = target.deps;
|
|
}
|
|
}
|
|
});
|
|
|
|
this.set('collecting', false);
|
|
this.emit('collected', dsl.items, true);
|
|
this.emit('ds-collected', dsl.items, true);
|
|
waitingResolve();
|
|
|
|
return depsData;
|
|
});
|
|
}
|
|
|
|
public collectNode(node: MNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
|
|
// 先删除原有依赖,重新收集
|
|
if (isPage(node)) {
|
|
this.removePageDep(target, depExtendedData);
|
|
} else {
|
|
this.watcher.removeTargetDep(target, node);
|
|
}
|
|
|
|
this.watcher.collectItem(node, target, depExtendedData, deep);
|
|
}
|
|
|
|
public clear(nodes?: MNode[]) {
|
|
return this.watcher.clear(nodes);
|
|
}
|
|
|
|
public clearByType(type: DepTargetType, nodes?: MNode[]) {
|
|
return this.watcher.clearByType(type, nodes);
|
|
}
|
|
|
|
public hasTarget(id: Id, type: string = DepTargetType.DEFAULT) {
|
|
return this.watcher.hasTarget(id, type);
|
|
}
|
|
|
|
public hasSpecifiedTypeTarget(type: string = DepTargetType.DEFAULT): boolean {
|
|
return this.watcher.hasSpecifiedTypeTarget(type);
|
|
}
|
|
|
|
public clearIdleTasks() {
|
|
this.idleTask.clearTasks();
|
|
}
|
|
|
|
public on<Name extends keyof DepEvents, Param extends DepEvents[Name]>(
|
|
eventName: Name,
|
|
listener: (...args: Param) => void | Promise<void>,
|
|
) {
|
|
return super.on(eventName, listener as any);
|
|
}
|
|
|
|
public once<Name extends keyof DepEvents, Param extends DepEvents[Name]>(
|
|
eventName: Name,
|
|
listener: (...args: Param) => void | Promise<void>,
|
|
) {
|
|
return super.once(eventName, listener as any);
|
|
}
|
|
|
|
public reset() {
|
|
this.idleTask.clearTasks();
|
|
|
|
for (const type of Object.keys(this.watcher.getTargetsList())) {
|
|
this.removeTargets(type);
|
|
}
|
|
|
|
this.set('collecting', false);
|
|
}
|
|
|
|
public destroy() {
|
|
this.idleTask.removeAllListeners();
|
|
|
|
this.removeAllListeners();
|
|
this.reset();
|
|
this.removeAllPlugins();
|
|
this.idleTask.removeAllListeners();
|
|
}
|
|
|
|
public emit<Name extends keyof DepEvents, Param extends DepEvents[Name]>(eventName: Name, ...args: Param) {
|
|
return super.emit(eventName, ...args);
|
|
}
|
|
|
|
/**
|
|
* 删除指定 page 在该 target 下的旧依赖:
|
|
* 按 pageId 匹配,可清掉页面内已被删除节点的残留依赖
|
|
*/
|
|
private removePageDep(target: Target, depExtendedData: DepExtendedData = {}) {
|
|
for (const depKey of Object.keys(target.deps)) {
|
|
const dep = target.deps[depKey];
|
|
if (dep.data?.pageId && dep.data.pageId === depExtendedData.pageId) {
|
|
delete target.deps[depKey];
|
|
}
|
|
}
|
|
}
|
|
|
|
private enqueueTask(node: MNode, target: Target, depExtendedData: DepExtendedData, deep: boolean) {
|
|
this.idleTask.enqueueTask(
|
|
({ node, deep, target }) => {
|
|
this.collectNode(node, target, depExtendedData, deep);
|
|
},
|
|
{
|
|
node,
|
|
deep: false,
|
|
target,
|
|
},
|
|
target.type === DepTargetType.DATA_SOURCE,
|
|
);
|
|
|
|
if (deep && Array.isArray(node.items) && node.items.length) {
|
|
node.items.forEach((item) => {
|
|
this.enqueueTask(item, target, depExtendedData, deep);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export type DepService = Dep;
|
|
|
|
export default new Dep();
|