plugin system refactoring

This commit is contained in:
Val Erastov 2022-03-04 23:58:17 -08:00
parent 02efa9a3dc
commit c7fda1ac27
14 changed files with 228 additions and 111 deletions

View file

@ -1,3 +1,4 @@
import {WizardSelectionContext} from "cad/craft/wizard/wizardSelectionPlugin";
type BagOfPlugins = Set<Plugin<any, any>>; type BagOfPlugins = Set<Plugin<any, any>>;
@ -22,10 +23,11 @@ export class PluginSystem {
while (needPass) { while (needPass) {
needPass = false; needPass = false;
this.waitingQueue.forEach(plugin => { this.waitingQueue.forEach(plugin => {
const localContext = plugin.readiness(this.globalContext); const ready = readiness(plugin, this.globalContext);
if (!!localContext) { if (ready) {
try { try {
plugin.activate(localContext); plugin.activate(this.globalContext);
checkActivation(plugin, this.globalContext);
needPass = true; needPass = true;
this.plugins.add(plugin); this.plugins.add(plugin);
} catch (error) { } catch (error) {
@ -42,7 +44,9 @@ export class PluginSystem {
this.waitingQueue.delete(plugin); this.waitingQueue.delete(plugin);
this.plugins.delete(plugin); this.plugins.delete(plugin);
try { try {
plugin.deactivate(this.globalContext); if (plugin.deactivate) {
plugin.deactivate(this.globalContext);
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -54,8 +58,8 @@ export class PluginSystem {
if (!plugin.deactivate) { if (!plugin.deactivate) {
return; return;
} }
const localContext = plugin.readiness(this.globalContext); const isReady = readiness(plugin, this.globalContext);
if (!localContext) { if (!isReady) {
try { try {
plugin.deactivate(this.globalContext); plugin.deactivate(this.globalContext);
this.plugins.delete(plugin); this.plugins.delete(plugin);
@ -70,13 +74,40 @@ export class PluginSystem {
} }
} }
function readiness(plugin: Plugin<any, any>, globalContext: any) {
const specKeys = Object.keys(plugin.inputContextSpec);
for (let key of specKeys) {
if (!globalContext[key] && plugin.inputContextSpec[key] === 'required') {
return false;
}
}
return true;
}
export interface Plugin<GlobalContext, WorkingContext> { function checkActivation(plugin: Plugin<any, any>, globalContext: any) {
const specKeys = Object.keys(plugin.outputContextSpec);
for (let key of specKeys) {
if (!globalContext[key] && plugin.outputContextSpec[key] === 'required') {
console.error("declared service was never activated: " + key);
}
}
}
readiness(ctx: GlobalContext): WorkingContext; export type Spec = 'required' | 'optional';
activate(ctx: WorkingContext); export type ContextSpec<T> = {
[Property in keyof T]: Spec;
};
deactivate?(ctx: WorkingContext);
export interface Plugin<InputContext, OutputContext, WorkingContext = InputContext&OutputContext> {
inputContextSpec: ContextSpec<InputContext>;
outputContextSpec: ContextSpec<OutputContext>;
activate(ctx: InputContext&OutputContext);
deactivate?(ctx: InputContext&OutputContext);
} }

View file

@ -13,7 +13,7 @@ export function GenericWizard({topicId, title, left, className, children, onCanc
left?: number, left?: number,
onCancel: () => any, onCancel: () => any,
onOK: () => any, onOK: () => any,
onKeyDown: (e) => any, onKeyDown?: (e) => any,
infoText: any infoText: any
} & WindowProps ) { } & WindowProps ) {

View file

@ -29,7 +29,7 @@ export type OperationFlattenSchema = {
export interface BaseSchemaField { export interface BaseSchemaField {
defaultValue?: OperationParamValue, defaultValue?: OperationParamValue,
optional: boolean, optional?: boolean,
label?: string, label?: string,
resolve?: ValueResolver<any, any> resolve?: ValueResolver<any, any>
} }

View file

@ -1,21 +1,16 @@
import {combine, state, StateStream} from 'lstream'; import {combine, state, StateStream} from 'lstream';
import initializeBySchema, {fillUpMissingFields} from '../schema/initializeBySchema'; import initializeBySchema from '../schema/initializeBySchema';
import {clone} from 'gems/objects'; import {clone} from 'gems/objects';
import materializeParams from '../schema/materializeParams'; import materializeParams from '../schema/materializeParams';
import {createFunctionList} from 'gems/func'; import {createFunctionList} from 'gems/func';
import {CraftHints, CraftHistory, OperationRequest} from "cad/craft/craftPlugin"; import {CraftHistory, OperationRequest} from "cad/craft/craftPlugin";
import {NewOperationCall, ParamsPath, WizardService, WizardState} from "cad/craft/wizard/wizardTypes"; import {NewOperationCall, ParamsPath, WizardService, WizardState, WorkingRequest} from "cad/craft/wizard/wizardTypes";
import _ from "lodash"; import _ from "lodash";
import {OperationParamValue} from "cad/craft/schema/schema"; import {OperationParamValue} from "cad/craft/schema/schema";
import {ApplicationContext} from "context"; import {ApplicationContext} from "context";
import {Operation} from "cad/craft/operationPlugin"; import {Operation} from "cad/craft/operationPlugin";
import produce from "immer" import produce from "immer"
type WorkingRequest = OperationRequest & {
hints?: CraftHints,
requestKey: number
}
export function activate(ctx: ApplicationContext) { export function activate(ctx: ApplicationContext) {
let {streams, services} = ctx; let {streams, services} = ctx;
@ -85,7 +80,6 @@ export function activate(ctx: ApplicationContext) {
// reset effect // reset effect
workingRequest$.pairwise().attach(([old, curr]) => { workingRequest$.pairwise().attach(([old, curr]) => {
if (old !== null && old.requestKey !== curr?.requestKey) { if (old !== null && old.requestKey !== curr?.requestKey) {
console.log("=========> DISPOSE")
disposerList.call(); disposerList.call();
disposerList = createFunctionList(); disposerList = createFunctionList();
state$.next({}); state$.next({});
@ -169,9 +163,12 @@ export function activate(ctx: ApplicationContext) {
ctx.wizardService = services.wizard = wizardService; ctx.wizardService = services.wizard = wizardService;
} }
export interface WizardOutputContext {
wizardService: WizardService
}
declare module 'context' { declare module 'context' {
interface ApplicationContext { interface ApplicationContext extends WizardOutputContext {
wizardService: WizardService
} }
} }

View file

@ -1,43 +1,66 @@
import {FACE, SHELL} from 'cad/model/entities'; import {FACE, SHELL} from 'cad/model/entities';
import {memoize} from "lodash/function";
import {OperationRequest} from "cad/craft/craftPlugin"; import {OperationRequest} from "cad/craft/craftPlugin";
import {FlattenPath, ParamsPath, WizardService} from "cad/craft/wizard/wizardTypes"; import {FlattenPath, ParamsPath, WizardService} from "cad/craft/wizard/wizardTypes";
import {OperationParamValue} from "cad/craft/schema/schema"; import {OperationParamValue} from "cad/craft/schema/schema";
import {EntityReference} from "cad/craft/operationPlugin"; import {EntityReference} from "cad/craft/operationPlugin";
import {ApplicationContext} from "context"; import {ContextSpec, Plugin, Spec} from "plugable/pluginSystem";
import {MarkerPluginOutputContext} from "cad/scene/selectionMarker/markerPlugin";
import {WizardOutputContext} from "cad/craft/wizard/wizardPlugin";
import {PickControlOutputContext} from "cad/scene/controls/pickControlPlugin";
export function activate(ctx: ApplicationContext) { export type WizardSelectionInputContext = MarkerPluginOutputContext & WizardOutputContext & PickControlOutputContext;
const wizardService = ctx.wizardService;
wizardService.workingRequest$.attach((opRequest: OperationRequest) => {
ctx.services.marker.clear();
if (opRequest) {
const wizardPickHandler = createPickHandlerFromSchema(wizardService);
ctx.services.pickControl.setPickHandler(wizardPickHandler);
const marker = ctx.services.marker;
marker.startSession();
let {schemaIndex} = wizardService.operation;
schemaIndex.entities.forEach(entityRef => {
//TODO: move to uiDefinition
let color = entityRef.metadata.markColor;
let val = wizardService.readParam(entityRef.field.path); export interface WizardSelectionOutputContext {
if (Array.isArray(val)) {
val.forEach(id => marker.mark(id, color));
} else {
if (val) {
marker.mark(val, color);
}
}
});
marker.commit();
} else {
ctx.services.pickControl.setPickHandler(null);
}
});
} }
export type WizardSelectionContext = WizardSelectionInputContext & WizardSelectionOutputContext;
export const WizardSelectionPlugin: Plugin<WizardSelectionInputContext, WizardSelectionOutputContext, WizardSelectionContext> = {
inputContextSpec: {
markerService: 'required',
pickControlService: 'required',
wizardService: 'required'
},
outputContextSpec: {
},
activate(ctx: WizardSelectionContext) {
const wizardService = ctx.wizardService;
wizardService.workingRequest$.attach((opRequest: OperationRequest) => {
ctx.markerService.clear();
if (opRequest) {
const wizardPickHandler = createPickHandlerFromSchema(wizardService);
ctx.pickControlService.setPickHandler(wizardPickHandler);
const marker = ctx.markerService;
marker.startSession();
let {schemaIndex} = wizardService.operation;
schemaIndex.entities.forEach(entityRef => {
//TODO: move to uiDefinition
let color = entityRef.metadata.markColor;
let val = wizardService.readParam(entityRef.field.path);
if (Array.isArray(val)) {
val.forEach(id => marker.mark(id, color));
} else {
if (val) {
marker.mark(val, color);
}
}
});
marker.commit();
} else {
ctx.pickControlService.setPickHandler(null);
}
});
},
}
const singleValue = (id, current) => id; const singleValue = (id, current) => id;
const arrayValue = (id, arr) => { const arrayValue = (id, arr) => {
if (!arr) { if (!arr) {
@ -49,8 +72,6 @@ const arrayValue = (id, arr) => {
return arr; return arr;
}; };
const getEntityParams = memoize(schema => Object.keys(schema).filter(key => schema[key].type === 'entity'));
function createPickHandlerFromSchema(wizardService: WizardService) { function createPickHandlerFromSchema(wizardService: WizardService) {
function update(param: ParamsPath, value: OperationParamValue, paramToMakeActive: FlattenPath) { function update(param: ParamsPath, value: OperationParamValue, paramToMakeActive: FlattenPath) {

View file

@ -14,9 +14,14 @@ export type WizardState = {
error?: any error?: any
}; };
export type WorkingRequest = OperationRequest & {
hints?: CraftHints,
requestKey: number
}
export interface WizardService { export interface WizardService {
workingRequest$: StateStream<OperationRequest&{hints?: CraftHints}>; workingRequest$: StateStream<WorkingRequest>;
materializedWorkingRequest$: StateStream<MaterializedOperationParams>; materializedWorkingRequest$: StateStream<MaterializedOperationParams>;

View file

@ -1,20 +1,6 @@
import {contributeComponent} from './components/ContributedComponents'; import {contributeComponent} from './components/ContributedComponents';
import {Plugin} from "plugable/pluginSystem";
export function activate(ctx) { import {AppTabsService} from "cad/dom/appTabsPlugin";
ctx.domService = {
viewerContainer: document.getElementById('viewer-container'),
contributeComponent
};
ctx.services.dom = ctx.domService;
ctx.appTabsService.tabs$.attach(({activeTab}) => {
if (activeTab === 0) {
ctx.services.viewer.sceneSetup.updateViewportSize();
}
});
}
export interface DomService { export interface DomService {
@ -24,12 +10,48 @@ export interface DomService {
} }
export interface DomInputContext {
appTabsService: AppTabsService;
services: any;
}
export interface DomOutputContext {
domService: DomService;
}
export type DomContext = DomInputContext&DomOutputContext;
declare module 'context' { declare module 'context' {
interface ApplicationContext extends DomOutputContext {}
interface ApplicationContext { }
domService: DomService;
} export const DomPlugin: Plugin<DomInputContext, DomOutputContext, DomContext> = {
inputContextSpec: {
appTabsService: 'required',
services: 'required',
},
outputContextSpec: {
domService: 'required',
},
activate(ctx: DomInputContext&DomOutputContext) {
ctx.domService = {
viewerContainer: document.getElementById('viewer-container'),
contributeComponent
};
ctx.services.dom = ctx.domService;
ctx.appTabsService.tabs$.attach(({activeTab}) => {
if (activeTab === 0) {
ctx.services.viewer.sceneSetup.updateViewportSize();
}
});
},
} }

View file

@ -1,6 +1,6 @@
import * as LifecyclePlugin from './lifecyclePlugin'; import * as LifecyclePlugin from './lifecyclePlugin';
import * as AppTabsPlugin from '../dom/appTabsPlugin'; import * as AppTabsPlugin from '../dom/appTabsPlugin';
import * as DomPlugin from '../dom/domPlugin'; import {DomPlugin} from '../dom/domPlugin';
import * as PickControlPlugin from '../scene/controls/pickControlPlugin'; import * as PickControlPlugin from '../scene/controls/pickControlPlugin';
import * as MouseEventSystemPlugin from '../scene/controls/mouseEventSystemPlugin'; import * as MouseEventSystemPlugin from '../scene/controls/mouseEventSystemPlugin';
import * as ScenePlugin from '../scene/scenePlugin'; import * as ScenePlugin from '../scene/scenePlugin';
@ -10,7 +10,7 @@ import * as UiPlugin from '../dom/uiPlugin';
import * as MenuPlugin from '../dom/menu/menuPlugin'; import * as MenuPlugin from '../dom/menu/menuPlugin';
import * as KeyboardPlugin from '../keyboard/keyboardPlugin'; import * as KeyboardPlugin from '../keyboard/keyboardPlugin';
import * as WizardPlugin from '../craft/wizard/wizardPlugin'; import * as WizardPlugin from '../craft/wizard/wizardPlugin';
import * as WizardSelectionPlugin from '../craft/wizard/wizardSelectionPlugin'; import {WizardSelectionPlugin} from '../craft/wizard/wizardSelectionPlugin';
import * as PreviewPlugin from '../preview/previewPlugin'; import * as PreviewPlugin from '../preview/previewPlugin';
import * as OperationPlugin from '../craft/operationPlugin'; import * as OperationPlugin from '../craft/operationPlugin';
import * as ExtensionsPlugin from '../craft/extensionsPlugin'; import * as ExtensionsPlugin from '../craft/extensionsPlugin';
@ -112,13 +112,15 @@ export function defineStreams(plugins, context) {
function adapter(oldStylePlugin) { function adapter(oldStylePlugin) {
if (oldStylePlugin.readiness) { if (oldStylePlugin.inputContextSpec) {
return oldStylePlugin; return oldStylePlugin;
} }
return { return {
readiness: ctx => ctx, inputContextSpec: {},
outputContextSpec: {},
activate: oldStylePlugin.activate, activate: oldStylePlugin.activate,

View file

@ -79,7 +79,7 @@ export function LocationDialog() {
}; };
return <GenericWizard return <GenericWizard
left={15} left={535}
title='PART LOCATION' title='PART LOCATION'
onClose={close} onClose={close}
className='location-dialog' className='location-dialog'

View file

@ -5,6 +5,22 @@ import {LOG_FLAGS} from '../../logFlags';
import * as vec from 'math/vec'; import * as vec from 'math/vec';
import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug"; import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";
export interface PickControlService {
setPickHandler(wizardPickHandler: (model) => boolean)
deselectAll()
pick()
pickFromRay()
simulatePickFromRay()
}
export interface PickControlOutputContext {
pickControlService: PickControlService;
}
export const PICK_KIND = { export const PICK_KIND = {
FACE: mask.type(1), FACE: mask.type(1),
SKETCH: mask.type(2), SKETCH: mask.type(2),
@ -27,9 +43,9 @@ const DEFAULT_SELECTION_MODE = Object.freeze({
export const ALL_EXCLUDING_SOLID_KINDS = PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE | PICK_KIND.DATUM_AXIS | PICK_KIND.LOOP; export const ALL_EXCLUDING_SOLID_KINDS = PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE | PICK_KIND.DATUM_AXIS | PICK_KIND.LOOP;
export function activate(context) { export function activate(context) {
const {services, streams} = context; const {services} = context;
const defaultHandler = (model, event, rayCastData) => { const defaultHandler = (model, event, rayCastData?) => {
if (LOG_FLAGS.PICK) { if (LOG_FLAGS.PICK) {
printPickInfo(model, rayCastData); printPickInfo(model, rayCastData);
} }
@ -150,6 +166,8 @@ export function activate(context) {
setPickHandler, deselectAll, pick, pickFromRay, simulatePickFromRay setPickHandler, deselectAll, pick, pickFromRay, simulatePickFromRay
}; };
services.pickControlService = services.pickControl;
if (LOG_FLAGS.PICK) { if (LOG_FLAGS.PICK) {
initRayCastDebug(); initRayCastDebug();
} }
@ -216,7 +234,7 @@ export function traversePickResults(event, pickResults, kind, visitor) {
} }
} }
function printPickInfo(model, rayCastData) { function printPickInfo(model, rayCastData?) {
console.log("PICKED MODEL:"); console.log("PICKED MODEL:");
console.dir(model); console.dir(model);
console.log("PICK RAYCAST INFO:"); console.log("PICK RAYCAST INFO:");

View file

@ -1,7 +1,7 @@
import Viewer from './viewer'; import Viewer from './viewer';
import CadScene from './cadScene'; import CadScene from './cadScene';
import {externalState, stream} from 'lstream'; import {externalState, stream} from 'lstream';
import {AppTabsService} from "../dom/appTabsPlugin"; import {ApplicationContext} from "context";
export function defineStreams({streams, services}) { export function defineStreams({streams, services}) {
streams.cadScene = { streams.cadScene = {
@ -10,7 +10,8 @@ export function defineStreams({streams, services}) {
}; };
} }
export function activate({streams, services}) { export function activate(ctx: ApplicationContext) {
const {streams, services} = ctx;
let {dom} = services; let {dom} = services;
const onRendered = () => streams.cadScene.sceneRendered.next(); const onRendered = () => streams.cadScene.sceneRendered.next();
@ -19,8 +20,10 @@ export function activate({streams, services}) {
services.viewer = viewer; services.viewer = viewer;
services.cadScene = new CadScene(viewer.sceneSetup.rootGroup); services.cadScene = new CadScene(viewer.sceneSetup.rootGroup);
ctx.viewer = viewer;
ctx.cadScene = services.cadScene;
// let sketcher3D = new Sketcher3D(dom.viewerContainer); // let sketcher3D = new Sketcher3D(dom.viewerContainer);
// services.viewer.setCameraMode(CAMERA_MODE.ORTHOGRAPHIC); // services.viewer.setCameraMode(CAMERA_MODE.ORTHOGRAPHIC);

View file

@ -1,8 +1,34 @@
import {OrderedMap} from 'gems/linkedMap'; import {OrderedMap} from 'gems/linkedMap';
import {eventStream} from 'lstream'; import {eventStream, Stream} from 'lstream';
import {MObject} from "cad/model/mobject";
export interface MarkerService {
clear();
startSession()
mark(id: any, color: any)
commit()
markExclusively()
markArrayExclusively()
markAdding()
isMarked()
$markedEntities: Stream<MObject>
}
export interface MarkerPluginOutputContext {
markerService: MarkerService;
}
export function activate(ctx) { export function activate(ctx) {
ctx.services.marker = createMarker(ctx.services.cadRegistry.find, ctx.services.viewer.requestRender); ctx.services.marker = createMarker(ctx.services.cadRegistry.find, ctx.services.viewer.requestRender);
ctx.markerService = ctx.services.marker;
ctx.streams.craft.models.attach(() => { ctx.streams.craft.models.attach(() => {
ctx.services.marker.clear(); ctx.services.marker.clear();
}); });

View file

@ -11,7 +11,7 @@ export const selectionSynchronizer = (entity, findEntity, color) => ([old, curr]
toMark.forEach(id => { toMark.forEach(id => {
let model = findEntity(entity, id); let model = findEntity(entity, id);
if (model) { if (model) {
model.ext.view.mark(color); model.ext.view.mark(id, color);
} }
}); });
}; };

View file

@ -1,4 +1,3 @@
import {ApplicationContext, CoreContext} from "context";
import {WorkbenchRegistry} from "workbenches/registry"; import {WorkbenchRegistry} from "workbenches/registry";
import planeOperation from "cad/craft/primitives/simplePlane/simplePlaneOperation"; import planeOperation from "cad/craft/primitives/simplePlane/simplePlaneOperation";
import boxOperation from "cad/craft/primitives/box/boxOperation"; import boxOperation from "cad/craft/primitives/box/boxOperation";
@ -19,37 +18,30 @@ import {intersectionOperation, subtractOperation, unionOperation} from "cad/craf
import {Plugin} from "plugable/pluginSystem"; import {Plugin} from "plugable/pluginSystem";
import {WorkbenchService} from "cad/workbench/workbenchService"; import {WorkbenchService} from "cad/workbench/workbenchService";
import {OperationService} from "cad/craft/operationPlugin"; import {OperationService} from "cad/craft/operationPlugin";
import {checkAllPropsDefined} from "plugable/utils";
export interface WorkbenchesLoaderContext { export interface WorkbenchesLoaderInputContext {
workbenchService: WorkbenchService, workbenchService: WorkbenchService,
operationService: OperationService operationService: OperationService
} }
export const WorkbenchesLoaderPlugin: Plugin<ApplicationContext, WorkbenchesLoaderContext> = { export const WorkbenchesLoaderPlugin: Plugin<WorkbenchesLoaderInputContext, {}> = {
readiness(ctx: ApplicationContext): WorkbenchesLoaderContext { inputContextSpec: {
workbenchService: 'required',
const { operationService: 'required'
workbenchService,
operationService
} = ctx;
return checkAllPropsDefined({
workbenchService,
operationService
})
}, },
activate(ctx: WorkbenchesLoaderContext) { outputContextSpec: {},
activate(ctx) {
registerCoreOperations(ctx); registerCoreOperations(ctx);
WorkbenchRegistry.forEach(wConfig => ctx.workbenchService.registerWorkbench(wConfig)); WorkbenchRegistry.forEach(wConfig => ctx.workbenchService.registerWorkbench(wConfig));
ctx.workbenchService.switchToDefaultWorkbench(); ctx.workbenchService.switchToDefaultWorkbench();
}, }
} }
function registerCoreOperations(ctx: WorkbenchesLoaderContext) { function registerCoreOperations(ctx: WorkbenchesLoaderInputContext) {
ctx.operationService.registerOperations([ ctx.operationService.registerOperations([
planeOperation, planeOperation,
@ -71,5 +63,5 @@ function registerCoreOperations(ctx: WorkbenchesLoaderContext) {
intersectionOperation, intersectionOperation,
subtractOperation, subtractOperation,
unionOperation, unionOperation,
]); ] as any);
} }