Skip to content

Commit

Permalink
✨ feat: add CancelableCommand
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangechen committed Jan 29, 2024
1 parent 455fa76 commit b910f72
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 101 deletions.
98 changes: 98 additions & 0 deletions packages/chili-core/src/command/command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.

import { IApplication } from "../application";
import { AsyncController, Observable, PubSub } from "../base";
import { Property } from "../property";

export interface ICommand {
execute(application: IApplication): Promise<void>;
Expand All @@ -15,3 +17,99 @@ export namespace ICommand {
return "cancel" in command;
}
}

export abstract class CancelableCommand extends Observable implements ICanclableCommand {
static readonly #propertiesCache: Map<string, any> = new Map(); // 所有命令共享

#complete: boolean = false;
get complete() {
return this.#complete;
}

#application: IApplication | undefined;
get application() {
if (!this.#application) {
throw new Error("application is not set");
}
return this.#application;
}

get document() {
return this.#application!.activeDocument!;
}

#controller?: AsyncController;
protected get controller() {
return this.#controller;
}
protected set controller(value: AsyncController | undefined) {
if (this.#controller === value) return;
this.#controller?.dispose();
this.#controller = value;
}

@Property.define("common.cancel")
async cancel() {
this.controller?.cancel();
await new Promise(async (resolve) => {
while (true) {
if (this.#complete) {
break;
}
await new Promise((r) => setTimeout(r, 50));
}
resolve(true);
});
}

async execute(application: IApplication): Promise<void> {
if (!application.activeDocument) return;
this.#application = application;
try {
let canExcute = await this.beforeExecute();
if (canExcute) {
await this.executeAsync();
}
} finally {
await this.afterExecute();
}
}

protected abstract executeAsync(): Promise<void>;

protected beforeExecute(): Promise<boolean> {
this.readProperties();
PubSub.default.pub("openCommandContext", this);
return Promise.resolve(true);
}

protected afterExecute(): Promise<void> {
this.saveProperties();
PubSub.default.pub("closeCommandContext");
this.controller?.dispose();
this.#complete = true;
return Promise.resolve();
}

private readProperties() {
Property.getProperties(this).forEach((x) => {
let key = this.cacheKeyOfProperty(x);
if (CancelableCommand.#propertiesCache.has(key)) {
(this as any)[key] = CancelableCommand.#propertiesCache.get(key);
}
});
}

private saveProperties() {
Property.getProperties(this).forEach((x) => {
let key = this.cacheKeyOfProperty(x);
let prop = (this as any)[key];
if (typeof prop === "function") return;
CancelableCommand.#propertiesCache.set(key, prop);
});
}

private cacheKeyOfProperty(property: Property) {
return property.name;
}
}
23 changes: 10 additions & 13 deletions packages/chili/src/commands/create/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import {
AsyncController,
CancelableCommand,
GeometryModel,
IApplication,
ICommand,
IDocument,
IEdge,
IModel,
Expand All @@ -19,16 +18,14 @@ import { SelectModelStep } from "../../step";

let count = 1;

abstract class ConvertCommand implements ICommand {
async execute(application: IApplication): Promise<void> {
let document = application.activeDocument;
if (!document) return;
let models = await this.getOrPickModels(document);
abstract class ConvertCommand extends CancelableCommand {
async executeAsync(): Promise<void> {
let models = await this.getOrPickModels(this.document);
if (!models) return;
Transaction.excute(document, `excute ${Object.getPrototypeOf(this).data.name}`, () => {
let geometryModel = this.create(document!, models!);
document!.addNode(geometryModel);
document!.visual.viewer.update();
Transaction.excute(this.document, `excute ${Object.getPrototypeOf(this).data.name}`, () => {
let geometryModel = this.create(this.document, models!);
this.document.addNode(geometryModel);
this.document.visual.viewer.update();
});
}

Expand All @@ -43,9 +40,9 @@ abstract class ConvertCommand implements ICommand {
let models = this.#getSelectedModels(document, filter);
if (models.length > 0) return models;
document.selection.clearSelected();
let controller = new AsyncController();
let step = new SelectModelStep("prompt.select.models", true);
let data = await step.execute(document, controller);
this.controller = new AsyncController();
let data = await step.execute(document, this.controller);
return data?.models;
}

Expand Down
92 changes: 4 additions & 88 deletions packages/chili/src/commands/multistepCommand.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,22 @@
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.

import { AsyncController, IApplication, ICanclableCommand, Observable, Property, PubSub } from "chili-core";
import { AsyncController, CancelableCommand, Property } from "chili-core";
import { SnapedData } from "../snap";
import { IStep } from "../step";

const PropertiesCache: Map<string, any> = new Map(); // 所有命令共享

export abstract class MultistepCommand extends Observable implements ICanclableCommand {
#complete: boolean = false;
export abstract class MultistepCommand extends CancelableCommand {
protected stepDatas: SnapedData[] = [];

#application: IApplication | undefined;
get application() {
if (!this.#application) {
throw new Error("application is not set");
}
return this.#application;
}

get document() {
return this.#application!.activeDocument!;
}

protected _restarting: boolean = false;
protected get restarting() {
return this._restarting;
}

protected _controller?: AsyncController;
protected get controller() {
return this._controller;
}
protected set controller(value: AsyncController | undefined) {
if (this._controller === value) return;
this._controller?.dispose();
this._controller = value;
}

protected restart() {
this._restarting = true;
this.controller?.cancel();
}

@Property.define("common.cancel")
async cancel() {
this.controller?.cancel();
await new Promise(async (resolve) => {
while (true) {
if (this.#complete) {
break;
}
await new Promise((r) => setTimeout(r, 50));
}
resolve(true);
});
}

private _repeatOperation: boolean = false;
@Property.define("command.mode.repeat")
get repeatOperation() {
Expand All @@ -66,17 +27,8 @@ export abstract class MultistepCommand extends Observable implements ICanclableC
this.setProperty("repeatOperation", value);
}

async execute(application: IApplication): Promise<void> {
if (!application.activeDocument) return;
this.#application = application;
try {
let canExcute = await this.beforeExecute();
if (canExcute) {
await this.executeSteps();
}
} finally {
await this.afterExecute();
}
protected async executeAsync(): Promise<void> {
await this.executeSteps();
}

protected async executeSteps(): Promise<void> {
Expand Down Expand Up @@ -109,40 +61,4 @@ export abstract class MultistepCommand extends Observable implements ICanclableC
protected abstract getSteps(): IStep[];

protected abstract executeMainTask(): void;

protected beforeExecute(): Promise<boolean> {
this.readProperties();
PubSub.default.pub("openCommandContext", this);
return Promise.resolve(true);
}

protected afterExecute(): Promise<void> {
this.saveProperties();
PubSub.default.pub("closeCommandContext");
this.controller?.dispose();
this.#complete = true;
return Promise.resolve();
}

private readProperties() {
Property.getProperties(this).forEach((x) => {
let key = this.cacheKeyOfProperty(x);
if (PropertiesCache.has(key)) {
(this as any)[key] = PropertiesCache.get(key);
}
});
}

private saveProperties() {
Property.getProperties(this).forEach((x) => {
let key = this.cacheKeyOfProperty(x);
let prop = (this as any)[key];
if (typeof prop === "function") return;
PropertiesCache.set(key, prop);
});
}

private cacheKeyOfProperty(property: Property) {
return property.name;
}
}

0 comments on commit b910f72

Please sign in to comment.