Skip to content

第9章:插件系统架构

本章概要

核心问题:如何设计一个可扩展的插件系统?

无限画布的功能高度模块化,通过插件系统实现功能的解耦和扩展。本章将深入分析:

  1. PluginSystem 设计:插件的注册、管理和生命周期
  2. 插件分类体系:渲染插件与业务插件的分层设计
  3. 核心插件解析:标尺、参考线、选区高亮等关键插件
  4. 自定义插件开发:插件开发规范和最佳实践

目录


9.1 PluginSystem 设计

9.1.1 核心架构

PluginSystem 是插件管理的核心类,负责插件的注册、查询和销毁:

typescript
// 来源:infinite-renderer/src/plugins/plugin-system.ts

export interface PluginSystemOptions {
    viewport: IViewport<IPageVm>;
    vmEngine: IVmEngine;
    context: IContext;
    eventBoundary: IEventBoundary;
}

class PluginSystem implements IPluginSystem {
    private viewport: IViewport<IPageVm>;
    private vmEngine: IVmEngine;
    private context: IContext;
    private eventBoundary: IEventBoundary;
    private pluginMap: Map<string, IPlugin> = new Map();
    private id = 0;

    constructor(options: PluginSystemOptions) {
        const { viewport, vmEngine, context, eventBoundary } = options;
        this.viewport = viewport;
        this.vmEngine = vmEngine;
        this.context = context;
        this.eventBoundary = eventBoundary;
    }
}

架构图

┌─────────────────────────────────────────────────────────────┐
│                     PluginSystem                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  依赖注入:                                                   │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │
│  │  Viewport   │  │  VmEngine   │  │   Context   │          │
│  └─────────────┘  └─────────────┘  └─────────────┘          │
│                         │                                    │
│                         ▼                                    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │              EventBoundary                           │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
│  插件注册表:                                                 │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  pluginMap: Map<string, IPlugin>                    │    │
│  │                                                      │    │
│  │  ┌──────────────┐  ┌──────────────┐                 │    │
│  │  │ "Outline"    │  │ "Highlight"  │                 │    │
│  │  │ OutlinePlugin│  │HighlightPlugin│                │    │
│  │  └──────────────┘  └──────────────┘                 │    │
│  │                                                      │    │
│  │  ┌──────────────┐  ┌──────────────┐                 │    │
│  │  │ "Ruler"      │  │ "Guide"      │                 │    │
│  │  │ RulerPlugin  │  │ GuidePlugin  │                 │    │
│  │  └──────────────┘  └──────────────┘                 │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

9.1.2 插件注册机制

PluginSystem 提供多种注册方式:

typescript
// 来源:infinite-renderer/src/plugins/plugin-system.ts

class PluginSystem implements IPluginSystem {
    // 创建插件实例(不注册)
    createPlugin<T extends IPlugin>(Class: new (props: IPluginOptions) => T): T {
        const instance = new Class({
            viewport: this.viewport,
            vmEngine: this.vmEngine,
            context: this.context,
            eventBoundary: this.eventBoundary,
            system: this,
        });

        // 触发 onCreated 生命周期钩子
        instance.onCreated();

        return instance;
    }

    // 注册插件(创建 + 添加到注册表)
    registerPlugin<T extends IPlugin>(
        Class: new (props: IPluginOptions) => T,
        name = Class.name,  // 默认使用类名
    ): T {
        const plugin = this.createPlugin(Class);

        // 自动生成名称
        if (!name) {
            name = `plugin-${++this.id}`;
        }

        this.addPlugin(name, plugin);
        return plugin;
    }

    // 直接添加已创建的插件
    addPlugin(name: string, plugin: IPlugin): void {
        if (this.pluginMap.has(name)) {
            throw new Error(`Duplicated plugin name: '${name}'`);
        }

        this.pluginMap.set(name, plugin);

        // 开发模式下挂载到 this 以便调试
        // @ts-expect-error
        if (import.meta.env.APP_NODE_ENV === 'development') {
            this[name] = plugin;
        }
    }
}

注册方式对比

方法用途是否注册是否调用 onCreated
createPlugin创建临时/独立插件
registerPlugin标准注册流程
addPlugin添加已创建的插件❌(已调用)

9.1.3 生命周期管理

插件的获取和移除:

typescript
// 来源:infinite-renderer/src/plugins/plugin-system.ts

class PluginSystem implements IPluginSystem {
    // 移除插件
    removePlugin(name: string): IPlugin | undefined;
    removePlugin<T extends IPlugin>(Class: new (props: IPluginOptions) => T): IPlugin | undefined;
    removePlugin(name: Function | string): IPlugin | undefined {
        // 支持按类名或名称移除
        if (typeof name === 'function') {
            name = name.name;
        }

        const plugin = this.pluginMap.get(name);
        this.pluginMap.delete(name);
        
        // 触发 onDestroy 生命周期钩子
        plugin?.onDestroy();
        
        return plugin;
    }

    // 断言获取插件(不存在则抛错)
    assertPlugin(name: string): IPlugin;
    assertPlugin<T extends IPlugin>(Class: T): T;
    assertPlugin(name: Function | string): IPlugin {
        if (typeof name === 'function') {
            name = name.name;
        }

        const plugin = this.pluginMap.get(name);

        if (!plugin) {
            throw new Error(`Plugin not found: '${name}'`);
        }

        return plugin;
    }

    // 销毁所有插件
    destroy(): void {
        for (const [_, value] of this.pluginMap) {
            value.onDestroy();
        }
        this.pluginMap.clear();
    }
}

生命周期流程图

插件生命周期:

┌────────────┐       ┌────────────┐       ┌────────────┐
│  创建阶段  │──────→│  运行阶段  │──────→│  销毁阶段  │
└────────────┘       └────────────┘       └────────────┘
      │                    │                    │
      ▼                    ▼                    ▼
┌────────────┐       ┌────────────┐       ┌────────────┐
│ constructor│       │  toggle()  │       │ onDestroy()│
│            │       │  render()  │       │            │
│ onCreated()│       │  clear()   │       │            │
└────────────┘       └────────────┘       └────────────┘

9.2 插件基类体系

9.2.1 BasePlugin 基类

所有插件的基础类,提供通用功能:

typescript
// 来源:infinite-renderer/src/plugins/base/base-plugin.ts

export class BasePlugin<T extends EventEmitter.ValidEventTypes = object>
    extends EventEmitter<T>
    implements IPlugin
{
    // 插件是否禁用
    get disabled(): boolean {
        return this._disabled;
    }

    protected _disabled = false;

    // 核心依赖注入
    protected viewport: IViewport<IPageVm>;
    protected context: IContext;
    protected vmEngine: IVmEngine;
    protected system: IPluginSystem;
    protected eventBoundary: IEventBoundary;

    constructor(options: IPluginOptions) {
        super();
        const { viewport, context, vmEngine, system, eventBoundary } = options;

        this.viewport = viewport;
        this.context = context;
        this.vmEngine = vmEngine;
        this.system = system;
        this.eventBoundary = eventBoundary;
    }

    // 生命周期钩子
    onCreated(): void {
        // 子类覆写
    }

    onDestroy(): void {
        // 子类覆写
    }

    // 启用/禁用插件
    toggle(value: boolean): void {
        this._disabled = !value;
    }

    // 下一帧执行
    nextTick<T = any>(fn: TickerCallback<T>, context: T, priority?: UPDATE_PRIORITY) {
        this.viewport.app.ticker.addOnce<T>(fn, context, priority);
    }
}

BasePlugin 特性

特性说明
事件发射器继承 EventEmitter,支持自定义事件
依赖注入自动注入 viewport、vmEngine、context 等
禁用控制toggle() 方法控制启用/禁用状态
帧调度nextTick() 在下一渲染帧执行回调

9.2.2 LayerPlugin 图层插件

用于创建渲染图层的插件基类:

typescript
// 来源:infinite-renderer/src/plugins/base/layer-plugin.ts

export abstract class LayerPlugin<
    T extends EventEmitter.ValidEventTypes = object,
> extends BasePlugin<T> {
    // 子类必须提供 view 容器
    protected abstract view: Container;

    onCreated(): void {
        // 添加到视口图层
        this.viewport.addLayer(this.view);
        // 注册每帧更新
        this.viewport.app.ticker.add(this.update, this, UPDATE_PRIORITY.NORMAL);
    }

    onDestroy(): void {
        // 从视口移除图层
        this.viewport.removeLayer(this.view);
        // 取消帧更新注册
        this.viewport.app.ticker.remove(this.update, this);
    }

    @Bind
    update(dt: number) {
        try {
            if (this.disabled) {
                this.view.visible = false;
                this.clear();
            } else {
                this.view.visible = true;
                this.render(dt);
            }
        } catch (error) {
            console.error(error);
        }
    }

    // 子类实现:渲染逻辑
    abstract render(dt: number): void;

    // 子类实现:清空逻辑
    abstract clear(): void;
}

LayerPlugin 渲染流程

每帧更新流程:

Ticker.update()


┌──────────────────┐
│  LayerPlugin.    │
│     update()     │
└────────┬─────────┘


┌──────────────────┐     ┌──────────────────┐
│  disabled === ?  │────→│  Yes: clear()    │
└────────┬─────────┘     │  view.visible =  │
         │ No            │      false       │
         ▼               └──────────────────┘
┌──────────────────┐
│  view.visible =  │
│      true        │
│  render(dt)      │
└──────────────────┘

9.3 双层插件架构

无限画布采用双层插件架构:渲染层插件和业务层插件。

9.3.1 渲染层插件

渲染层插件位于 infinite-renderer 包,直接操作 PixiJS 对象:

渲染层插件目录:
infinite-renderer/src/plugins/
├── background/           # 背景插件
│   ├── background-plugin.ts
│   └── background-vm.ts
└── base/                 # 基类
    ├── base-plugin.ts
    └── layer-plugin.ts

infinite-plugins/src/renderer-plugins/
├── outline/              # 选区高亮
├── highlight/            # Hover 高亮
├── guide/                # 参考线
├── ruler/                # 标尺
├── scrollbar/            # 滚动条
├── connector/            # 连接线
├── magic-brush/          # 魔法画笔
└── ...

渲染层插件特点

特点说明
继承继承 BasePluginLayerPlugin
依赖直接使用 viewportvmEngineeventBoundary
渲染使用 PixiJS Graphics/Container 渲染
注册通过 surface.plugin.registerPlugin() 注册

9.3.2 业务层插件

业务层插件位于 infinite-plugins/src/plugins,封装业务逻辑:

业务层插件目录:
infinite-plugins/src/plugins/
├── viewport-plugin/      # 视口控制
├── hand-plugin/          # 抓手工具
├── hover-plugin/         # Hover 检测
├── hotkeys-plugin/       # 快捷键
├── guide-plugin/         # 参考线业务
├── ruler-plugin/         # 标尺业务
├── scrollbar-plugin/     # 滚动条业务
├── outline-plugin/       # 选区高亮业务
├── connector-plugin/     # 连接线业务
└── ...

业务层插件接口

typescript
// 来源:@editor/framework 的 Plugin 接口

export interface Plugin {
    name: string;
    version: string;
    
    // 注册命令
    commands?(editor: VPEditor): Record<string, Function>;
    
    // 注册钩子
    hooks?(editor: VPEditor): Record<string, Function>;
    
    // 注册事件监听
    events?(editor: VPEditor): Record<string, Function>;
    
    // 挂载时调用
    mount?(editor: VPEditor): void;
    
    // 销毁时调用
    destroy?(): void;
}

9.3.3 两层协作模式

业务层插件通常会创建并管理一个渲染层插件:

typescript
// 来源:infinite-plugins/src/plugins/ruler-plugin/ruler-plugin.ts

function createRulerPlugin(): Plugin {
    let app: BoardSurface | null = null;
    let ruler: RulerPlugin | null = null;  // 渲染层插件实例
    
    return {
        name: 'ruler',
        version: '1.0.0',

        commands(editor) {
            return {
                // 初始化时创建渲染层插件
                initialize(_app: BoardSurface) {
                    app = _app;
                    // 注册渲染层插件
                    ruler = app.plugin.registerPlugin(RulerPlugin);
                    ruler.toggle(visibleRef.value);
                    ruler.setArea(areaRef.value);
                    
                    // 设置业务逻辑
                    useRulerPlugin(editor, ruler, visibleRef);
                    // ...
                },

                toggleVisible(visible: boolean) {
                    visibleRef.value = visible;
                },
            };
        },

        // 销毁时移除渲染层插件
        destroy() {
            app?.plugin.removePlugin(RulerPlugin);
            app = null;
        },
    };
}

双层协作图

┌─────────────────────────────────────────────────────────────┐
│                      业务层插件                              │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  createRulerPlugin()                                 │   │
│  │                                                       │   │
│  │  commands: {                                          │   │
│  │    initialize(app) {                                  │   │
│  │      ruler = app.plugin.registerPlugin(RulerPlugin)  │───┼──┐
│  │    }                                                  │   │  │
│  │    toggleVisible(visible) { ... }                     │   │  │
│  │  }                                                    │   │  │
│  │                                                       │   │  │
│  │  events: { ... }                                      │   │  │
│  │                                                       │   │  │
│  │  destroy() {                                          │   │  │
│  │    app.plugin.removePlugin(RulerPlugin)              │───┼──┤
│  │  }                                                    │   │  │
│  └──────────────────────────────────────────────────────┘   │  │
│                                                              │  │
└─────────────────────────────────────────────────────────────┘  │


┌─────────────────────────────────────────────────────────────┐  │
│                      渲染层插件                              │  │
│                                                              │  │
│  ┌──────────────────────────────────────────────────────┐   │  │
│  │  class RulerPlugin extends LayerPlugin              │◀──┼──┘
│  │                                                       │   │
│  │  protected view = new Container();                    │   │
│  │                                                       │   │
│  │  render() {                                           │   │
│  │    // 绘制标尺刻度                                    │   │
│  │  }                                                    │   │
│  │                                                       │   │
│  │  clear() {                                            │   │
│  │    // 清空绘制                                        │   │
│  │  }                                                    │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

9.4 核心插件解析

9.4.1 OutlinePlugin 选区高亮

用于绘制选中元素的边框:

typescript
// 来源:infinite-plugins/src/renderer-plugins/outline/outline-plugin.ts

export class OutlinePlugin extends LayerPlugin {
    protected view = new Graphics();

    // 选中的元素集合
    private selected = new Set<IBaseElementVm>();
    
    // 协作者选中的元素
    private collaborator = new Map<IBaseElementVm, ColorSource>();

    // 自定义样式
    private customStrokeWidth: number | null = null;
    private customColor: ColorSource | null = null;

    onCreated(): void {
        this.view.name = 'OutlinePlugin';
        super.onCreated();
    }

    // 选中元素
    select(models: BaseElementModel[]): void {
        this.toggle(true);
        this.selected.clear();

        for (const model of models) {
            const view = this.vmEngine.getVm(model);
            if (view) {
                this.selected.add(view);
            }
        }
    }

    // 取消选中
    unselect(): void {
        this.selected.clear();
        this.checkDisable();
    }

    // 渲染选区边框
    render(): void {
        const { screen } = this.viewport.app;
        const graphics = this.view.clear();
        const { zoom } = this.viewport;

        // 区分锁定和未锁定元素
        const locked: IBaseElementVm[] = [];
        const unlocked: IBaseElementVm[] = [];

        for (const element of this.selected) {
            const model = element.getModel();
            if (model.lock) {
                locked.push(element);
            } else {
                unlocked.push(element);
            }
        }

        // 渲染未锁定元素(蓝色边框)
        const strokeWidth = this.customStrokeWidth ?? DEFAULT_STROKE_WIDTH;
        const color = this.customColor ?? DEFAULT_COLOR();
        graphics.strokeStyle(strokeWidth, color);

        for (const element of unlocked) {
            const rect = element.getModelBounds(false, RECTANGLE);
            if (screen.intersects(rect) && element.view.visible) {
                renderOutline(graphics, element, zoom);
            }
        }

        // 渲染协作者选中元素(自定义颜色)
        for (const [element, color] of this.collaborator) {
            const rect = element.getModelBounds(false, RECTANGLE);
            if (screen.intersects(rect) && element.view.visible) {
                graphics.strokeStyle(2, color);
                renderOutline(graphics, element, zoom);
            }
        }

        // 渲染锁定元素(红色边框)
        graphics.strokeStyle(2, 0xf54531);
        for (const element of locked) {
            const rect = element.getModelBounds(false, RECTANGLE);
            if (screen.intersects(rect) && element.view.visible) {
                renderOutline(graphics, element, zoom);
            }
        }
    }

    clear(): void {
        this.view.clear();
    }
}

9.4.2 HighlightPlugin Hover 高亮

用于绘制 Hover 状态的元素高亮:

typescript
// 来源:infinite-plugins/src/renderer-plugins/highlight/highlight-plugin.ts

export class HighlightPlugin extends LayerPlugin {
    protected view = new Graphics();
    
    // 激活的元素列表
    protected activated: IBaseElementVm[] = [];
    
    // 高亮颜色
    protected highlightColor: ColorSource = getCssValue('--transform-border-color-regular') || 0x4d7cff;

    onCreated(): void {
        this.view.name = 'HighlightPlugin';
        super.onCreated();
    }

    // 激活元素高亮
    activate(models: BaseElementModel[]): void {
        this.activated.length = 0;
        for (const model of models) {
            const view = this.vmEngine.getVm(model);
            if (view) {
                this.activated.push(view);
            }
        }
        this.checkDisable();
    }

    // 取消激活
    inactivate(): void {
        this.activated.length = 0;
        this.checkDisable();
    }

    // 设置高亮颜色
    setColor(color: ColorSource) {
        this.highlightColor = color;
    }

    render(): void {
        const { screen } = this.viewport.app;
        const temp = new Rectangle();
        const graphics = this.view.clear();

        graphics.strokeStyle(2, this.highlightColor);

        for (const element of this.activated) {
            if (!element || element.destroyed) continue;

            const rect = element.getModelBounds(false, temp);

            if (screen.intersects(rect)) {
                element.view.updateTransform();
                renderOutline(graphics, element, this.viewport.zoom);
            }
        }
    }

    clear(): void {
        this.view.clear();
    }
}

9.4.3 GuidePlugin 参考线

参考线是最复杂的插件之一,支持:

  • 页面级和画板级参考线
  • 拖拽创建/移动/删除
  • Hover/Focus 状态
  • 高亮线段
  • 协作状态
typescript
// 来源:infinite-plugins/src/renderer-plugins/guide/guide-plugin.ts

export class GuidePlugin extends LayerPlugin<GuideEvents> {
    static GUIDE_EPSILON = 1;  // 参考线精度
    
    // 默认样式
    static DEFAULT_STYLE: GuideStyle = {
        lineWidth: 1,
        lineColor: '#E17EFF',
        hoverColor: '#CF33FF',
        focusColor: '#2254F4',
        disabledColor: '#B4B8BF',
    };

    protected view: Container = new Container();
    protected guideLayer = new Graphics();     // 参考线图层
    protected segmentLayer = new Graphics();   // 高亮线段图层
    protected extendLayer = new Graphics();    // 延长线图层

    protected manager: GuideManager;  // 参考线数据管理器
    protected editor: GuideEditor;    // 参考线编辑器
    protected ruler!: RulerPlugin;    // 依赖的标尺插件

    constructor(options: IPluginOptions) {
        super(options);

        this.manager = new GuideManager();
        // 获取依赖的 RulerPlugin
        this.ruler = this.system.assertPlugin(RulerPlugin);
        this.editor = new GuideEditor({
            viewport: this.viewport,
            manager: this.manager,
            ruler: this.ruler,
        });
        // ...
    }

    onCreated(): void {
        this.view.name = 'GuidePlugin';
        this.view.addChild(this.guideLayer, this.segmentLayer, this.extendLayer);

        if (!this.disabled) {
            this.editor.activate();
        }
        
        // 事件转发
        this.editor.on('pointerenter', (event) => this.emit('pointerenter', event));
        this.editor.on('pointerleave', (event) => this.emit('pointerleave', event));
        this.editor.on('focus', (event) => this.emit('focus', event));
        this.editor.on('dragstart', (event) => this.emit('dragstart', event));
        this.editor.on('dragmove', (event) => this.handleDragMove(event));
        this.editor.on('dragend', (event) => this.handleDragEnd(event));
        this.ruler.on('pullout', this.handleRulerPullout, this);

        // 将参考线图层注册为事件遮罩
        this.eventBoundary.addCoverMask(this.guideLayer);
        super.onCreated();
    }

    onDestroy(): void {
        this.editor.inactivate();
        this.ruler.off('pullout', this.handleRulerPullout, this);
        this.eventBoundary.removeCoverMask(this.guideLayer);
        super.onDestroy();
    }

    render(): void {
        this.guideLayer.clear();
        this.segmentLayer.clear();
        this.extendLayer.clear();
        this.manager.clear();

        // 添加页面参考线
        this.addGuides(this.manager, this.viewport.page, null);

        // 添加画板参考线
        for (const child of this.viewport.page.children) {
            const model = child.getModel();
            if (model.type !== 'layout') continue;
            
            const bounds = child.getBounds(false);
            if (screen.intersects(bounds)) {
                this.addGuides(this.manager, child, bounds);
            }
        }

        // 渲染参考线
        this.guideLayer.beginFill(this.style.lineColor);
        this.renderGuides(this.manager);

        // 渲染 Hover 状态
        if (this.editor.hoverState && !this.editor.hoverState.disabled) {
            // ...
        }

        // 渲染 Focus 状态
        if (this.editor.focusState && !this.editor.focusState.disabled) {
            // ...
        }
    }
}

9.4.4 RulerPlugin 标尺

标尺插件用于显示刻度尺:

typescript
// 核心功能
export class RulerPlugin extends LayerPlugin {
    protected view = new Container();
    
    // X/Y 轴标尺
    protected xRuler: Ruler;
    protected yRuler: Ruler;
    
    // 设置标尺区域
    setArea(area: Rectangle | null): void;
    
    // 设置标尺作用域(画板)
    setScope(scope: LayoutModel | null): void;
    
    // 判断是否超出标尺范围
    isBeyondXRuler(y: number): boolean;
    isBeyondYRuler(x: number): boolean;
}

9.4.5 ScrollbarPlugin 滚动条

虚拟滚动条插件:

typescript
// 来源:infinite-plugins/src/plugins/scrollbar-plugin/scrollbar-plugin.ts

export function createScrollbarPlugin(options: ScrollbarPluginOptions = {}): Plugin {
    let app: BoardSurface | undefined;
    let scrollbar: ScrollbarPlugin;

    return {
        name: 'scrollbar',
        version: '1.0.0',

        commands(editor) {
            return {
                initialize(_app: BoardSurface) {
                    app = _app;
                    scrollbar = app.plugin.registerPlugin(ScrollbarPlugin);
                    useScrollbar(editor, scrollbar);
                    useExpand(editor, scrollbar);
                },

                // 调整视口位置
                adjustPosition(x, y, frame, expand, clientWidth, clientHeight, padding) {
                    return adjustPosition(x, y, frame, expand, clientWidth, clientHeight, padding);
                },
            };
        },

        // 钩子:拦截位置调整
        hooks(editor) {
            return {
                adjustPosition(next, x, y, model) {
                    // ... 加入扩展区域后的二次矫正
                },
            };
        },

        destroy() {
            app?.plugin.removePlugin(ScrollbarPlugin);
        },
    };
}

9.4.6 BackgroundPlugin 背景

画布背景插件:

typescript
// 来源:infinite-renderer/src/plugins/background/background-plugin.ts

export class BackgroundPlugin extends BasePlugin {
    private backgroundVm: BackgroundVm = new BackgroundVm();
    private backgroundMode: BackgroundMode = 'none';

    onCreated(): void {
        super.onCreated();
        this.viewport.addBackgroundLayer(this.backgroundVm.view);
        this.viewport.on('transform', this.handleTransform, this);
        this.viewport.on('resize', this.handleResize, this);
    }

    onDestroy(): void {
        super.onDestroy();
        this.backgroundVm.destroy(true);
        this.viewport.off('transform', this.handleTransform, this);
        this.viewport.off('resize', this.handleResize, this);
    }

    private updateBackground(): void {
        const { position, zoom, size } = this.viewport;
        const { resolution } = this.viewport.app.renderer;

        this.backgroundVm.setState({
            x: position.x,
            y: position.y,
            width: size.width,
            height: size.height,
            zoom,
            resolution,
            background: this.backgroundMode,
        });
    }

    // 设置背景模式
    setBackground(mode: BackgroundMode): void {
        this.backgroundMode = mode;
        this.updateBackground();
    }

    // 清空背景
    clear(): void {
        this.backgroundMode = 'none';
        this.updateBackground();
    }
}

9.5 自定义插件开发

9.5.1 插件开发流程

开发一个自定义插件的步骤:

1. 确定插件类型
   ├── 需要渲染图层? → 继承 LayerPlugin
   └── 只需要功能? → 继承 BasePlugin

2. 创建渲染层插件(如需要)
   ├── 定义 view 容器
   ├── 实现 render() 方法
   └── 实现 clear() 方法

3. 创建业务层插件
   ├── 定义 commands
   ├── 定义 events
   └── 管理渲染层插件生命周期

4. 注册插件
   └── 在编辑器初始化时注册

9.5.2 开发规范

命名规范

typescript
// 渲染层插件:PascalCase + Plugin 后缀
export class MyCustomPlugin extends LayerPlugin { }

// 业务层插件:createXxxPlugin 工厂函数
export function createMyCustomPlugin(): Plugin { }

生命周期规范

typescript
// 正确:在 onCreated 中初始化资源
onCreated(): void {
    super.onCreated();
    this.bindEvents();
}

// 正确:在 onDestroy 中清理资源
onDestroy(): void {
    this.unbindEvents();
    super.onDestroy();
}

性能规范

typescript
// 避免在 render 中创建对象
const tempRect = new Rectangle();  // 复用对象

render(): void {
    // 使用预分配的对象
    const bounds = element.getBounds(false, tempRect);
    
    // 视口剔除
    if (!screen.intersects(bounds)) return;
}

9.5.3 完整示例

创建一个显示元素信息的调试插件:

typescript
// debug-info-plugin.ts - 渲染层插件

import { LayerPlugin } from '@editor/infinite-renderer';
import { Graphics, Text, Container } from '@piso/piso-js';

export class DebugInfoPlugin extends LayerPlugin {
    protected view = new Container();
    
    private graphics = new Graphics();
    private text = new Text('', { fontSize: 12, fill: 0x333333 });
    
    private targetElement: IBaseElementVm | null = null;

    onCreated(): void {
        this.view.name = 'DebugInfoPlugin';
        this.view.addChild(this.graphics, this.text);
        super.onCreated();
    }

    setTarget(element: IBaseElementVm | null): void {
        this.targetElement = element;
        this.toggle(!!element);
    }

    render(): void {
        if (!this.targetElement) return;

        const bounds = this.targetElement.getBounds();
        const model = this.targetElement.getModel();

        // 绘制背景
        this.graphics.clear();
        this.graphics.beginFill(0xffffff, 0.9);
        this.graphics.drawRoundedRect(bounds.right + 10, bounds.top, 200, 80, 4);
        this.graphics.endFill();

        // 显示信息
        this.text.text = [
            `Type: ${model.type}`,
            `UUID: ${model.uuid.slice(0, 8)}...`,
            `Size: ${model.width.toFixed(0)} × ${model.height.toFixed(0)}`,
            `Position: (${model.left.toFixed(0)}, ${model.top.toFixed(0)})`,
        ].join('\n');
        
        this.text.position.set(bounds.right + 20, bounds.top + 10);
    }

    clear(): void {
        this.graphics.clear();
        this.text.text = '';
    }
}
typescript
// debug-info-plugin.ts - 业务层插件

import type { Plugin } from '@editor/framework';
import { DebugInfoPlugin } from '../renderer-plugins/debug-info';

export function createDebugInfoPlugin(): Plugin {
    let app: BoardSurface | null = null;
    let debugInfo: DebugInfoPlugin | null = null;

    return {
        name: 'debug-info',
        version: '1.0.0',

        commands(editor) {
            return {
                initialize(_app: BoardSurface) {
                    app = _app;
                    debugInfo = app.plugin.registerPlugin(DebugInfoPlugin);
                },

                show(element: BaseElementModel) {
                    const vm = app?.vmEngine.getVm(element);
                    debugInfo?.setTarget(vm || null);
                },

                hide() {
                    debugInfo?.setTarget(null);
                },
            };
        },

        events(editor) {
            return {
                'element.select': (elements: BaseElementModel[]) => {
                    if (elements.length === 1) {
                        editor.plugins.invokeCommand('debug-info:show', elements[0]);
                    } else {
                        editor.plugins.invokeCommand('debug-info:hide');
                    }
                },
            };
        },

        destroy() {
            app?.plugin.removePlugin(DebugInfoPlugin);
            app = null;
            debugInfo = null;
        },
    };
}

9.6 本章小结

插件系统架构图

┌─────────────────────────────────────────────────────────────┐
│                      插件系统架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                   业务层插件                         │    │
│  │                                                      │    │
│  │  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐       │    │
│  │  │viewport│ │  hand  │ │ hover  │ │hotkeys │       │    │
│  │  │-plugin │ │-plugin │ │-plugin │ │-plugin │       │    │
│  │  └───┬────┘ └────────┘ └───┬────┘ └────────┘       │    │
│  │      │                     │                        │    │
│  │  ┌───┴────┐ ┌────────┐ ┌───┴────┐ ┌────────┐       │    │
│  │  │ ruler  │ │ guide  │ │outline │ │scrollbar│       │    │
│  │  │-plugin │ │-plugin │ │-plugin │ │-plugin │       │    │
│  │  └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘       │    │
│  │      │          │          │          │             │    │
│  └──────┼──────────┼──────────┼──────────┼─────────────┘    │
│         │          │          │          │                   │
│         ▼          ▼          ▼          ▼                   │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                  PluginSystem                        │    │
│  │                                                      │    │
│  │  registerPlugin() / removePlugin() / assertPlugin()  │    │
│  │                                                      │    │
│  └─────────────────────────────────────────────────────┘    │
│         │          │          │          │                   │
│         ▼          ▼          ▼          ▼                   │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                   渲染层插件                         │    │
│  │                                                      │    │
│  │  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐       │    │
│  │  │ Ruler  │ │ Guide  │ │Outline │ │Scrollbar│       │    │
│  │  │ Plugin │ │ Plugin │ │ Plugin │ │ Plugin │       │    │
│  │  └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘       │    │
│  │      │          │          │          │             │    │
│  │      └──────────┴──────────┴──────────┘             │    │
│  │                     │                                │    │
│  │                     ▼                                │    │
│  │              LayerPlugin / BasePlugin               │    │
│  │                                                      │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

关键代码路径

模块文件路径
PluginSysteminfinite-renderer/src/plugins/plugin-system.ts
BasePlugininfinite-renderer/src/plugins/base/base-plugin.ts
LayerPlugininfinite-renderer/src/plugins/base/layer-plugin.ts
OutlinePlugininfinite-plugins/src/renderer-plugins/outline/outline-plugin.ts
HighlightPlugininfinite-plugins/src/renderer-plugins/highlight/highlight-plugin.ts
GuidePlugininfinite-plugins/src/renderer-plugins/guide/guide-plugin.ts
RulerPlugininfinite-plugins/src/renderer-plugins/ruler/ruler-plugin.ts
BackgroundPlugininfinite-renderer/src/plugins/background/background-plugin.ts

核心要点

  1. 双层架构:渲染层负责绑定,业务层封装逻辑
  2. 依赖注入:通过 IPluginOptions 注入核心依赖
  3. 生命周期onCreated() 初始化,onDestroy() 清理
  4. LayerPlugin:自动管理图层添加/移除和帧更新
  5. 插件协作:通过 system.assertPlugin() 获取依赖插件

下一章预告

第10章将讨论性能优化策略,包括:

  • 虚拟化渲染
  • 纹理管理
  • 渲染优化
  • 内存优化
  • 性能监控

如有转载或 CV 的请标注本站原文地址