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>>;
@ -22,10 +23,11 @@ export class PluginSystem {
while (needPass) {
needPass = false;
this.waitingQueue.forEach(plugin => {
const localContext = plugin.readiness(this.globalContext);
if (!!localContext) {
const ready = readiness(plugin, this.globalContext);
if (ready) {
try {
plugin.activate(localContext);
plugin.activate(this.globalContext);
checkActivation(plugin, this.globalContext);
needPass = true;
this.plugins.add(plugin);
} catch (error) {
@ -42,7 +44,9 @@ export class PluginSystem {
this.waitingQueue.delete(plugin);
this.plugins.delete(plugin);
try {
plugin.deactivate(this.globalContext);
if (plugin.deactivate) {
plugin.deactivate(this.globalContext);
}
} catch (error) {
console.error(error);
}
@ -54,8 +58,8 @@ export class PluginSystem {
if (!plugin.deactivate) {
return;
}
const localContext = plugin.readiness(this.globalContext);
if (!localContext) {
const isReady = readiness(plugin, this.globalContext);
if (!isReady) {
try {
plugin.deactivate(this.globalContext);
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,
onCancel: () => any,
onOK: () => any,
onKeyDown: (e) => any,
onKeyDown?: (e) => any,
infoText: any
} & WindowProps ) {

View file

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

View file

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

View file

@ -1,43 +1,66 @@
import {FACE, SHELL} from 'cad/model/entities';
import {memoize} from "lodash/function";
import {OperationRequest} from "cad/craft/craftPlugin";
import {FlattenPath, ParamsPath, WizardService} from "cad/craft/wizard/wizardTypes";
import {OperationParamValue} from "cad/craft/schema/schema";
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) {
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;
export type WizardSelectionInputContext = MarkerPluginOutputContext & WizardOutputContext & PickControlOutputContext;
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.services.pickControl.setPickHandler(null);
}
});
export interface WizardSelectionOutputContext {
}
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 arrayValue = (id, arr) => {
if (!arr) {
@ -49,8 +72,6 @@ const arrayValue = (id, arr) => {
return arr;
};
const getEntityParams = memoize(schema => Object.keys(schema).filter(key => schema[key].type === 'entity'));
function createPickHandlerFromSchema(wizardService: WizardService) {
function update(param: ParamsPath, value: OperationParamValue, paramToMakeActive: FlattenPath) {

View file

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

View file

@ -1,20 +1,6 @@
import {contributeComponent} from './components/ContributedComponents';
export function activate(ctx) {
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();
}
});
}
import {Plugin} from "plugable/pluginSystem";
import {AppTabsService} from "cad/dom/appTabsPlugin";
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' {
interface ApplicationContext {
domService: DomService;
}
interface ApplicationContext extends DomOutputContext {}
}
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 AppTabsPlugin from '../dom/appTabsPlugin';
import * as DomPlugin from '../dom/domPlugin';
import {DomPlugin} from '../dom/domPlugin';
import * as PickControlPlugin from '../scene/controls/pickControlPlugin';
import * as MouseEventSystemPlugin from '../scene/controls/mouseEventSystemPlugin';
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 KeyboardPlugin from '../keyboard/keyboardPlugin';
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 OperationPlugin from '../craft/operationPlugin';
import * as ExtensionsPlugin from '../craft/extensionsPlugin';
@ -112,13 +112,15 @@ export function defineStreams(plugins, context) {
function adapter(oldStylePlugin) {
if (oldStylePlugin.readiness) {
if (oldStylePlugin.inputContextSpec) {
return oldStylePlugin;
}
return {
readiness: ctx => ctx,
inputContextSpec: {},
outputContextSpec: {},
activate: oldStylePlugin.activate,

View file

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

View file

@ -5,6 +5,22 @@ import {LOG_FLAGS} from '../../logFlags';
import * as vec from 'math/vec';
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 = {
FACE: mask.type(1),
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 function activate(context) {
const {services, streams} = context;
const {services} = context;
const defaultHandler = (model, event, rayCastData) => {
const defaultHandler = (model, event, rayCastData?) => {
if (LOG_FLAGS.PICK) {
printPickInfo(model, rayCastData);
}
@ -150,6 +166,8 @@ export function activate(context) {
setPickHandler, deselectAll, pick, pickFromRay, simulatePickFromRay
};
services.pickControlService = services.pickControl;
if (LOG_FLAGS.PICK) {
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.dir(model);
console.log("PICK RAYCAST INFO:");

View file

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

View file

@ -1,8 +1,34 @@
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) {
ctx.services.marker = createMarker(ctx.services.cadRegistry.find, ctx.services.viewer.requestRender);
ctx.markerService = ctx.services.marker;
ctx.streams.craft.models.attach(() => {
ctx.services.marker.clear();
});

View file

@ -11,7 +11,7 @@ export const selectionSynchronizer = (entity, findEntity, color) => ([old, curr]
toMark.forEach(id => {
let model = findEntity(entity, id);
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 planeOperation from "cad/craft/primitives/simplePlane/simplePlaneOperation";
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 {WorkbenchService} from "cad/workbench/workbenchService";
import {OperationService} from "cad/craft/operationPlugin";
import {checkAllPropsDefined} from "plugable/utils";
export interface WorkbenchesLoaderContext {
export interface WorkbenchesLoaderInputContext {
workbenchService: WorkbenchService,
operationService: OperationService
}
export const WorkbenchesLoaderPlugin: Plugin<ApplicationContext, WorkbenchesLoaderContext> = {
export const WorkbenchesLoaderPlugin: Plugin<WorkbenchesLoaderInputContext, {}> = {
readiness(ctx: ApplicationContext): WorkbenchesLoaderContext {
const {
workbenchService,
operationService
} = ctx;
return checkAllPropsDefined({
workbenchService,
operationService
})
inputContextSpec: {
workbenchService: 'required',
operationService: 'required'
},
activate(ctx: WorkbenchesLoaderContext) {
outputContextSpec: {},
activate(ctx) {
registerCoreOperations(ctx);
WorkbenchRegistry.forEach(wConfig => ctx.workbenchService.registerWorkbench(wConfig));
ctx.workbenchService.switchToDefaultWorkbench();
},
}
}
function registerCoreOperations(ctx: WorkbenchesLoaderContext) {
function registerCoreOperations(ctx: WorkbenchesLoaderInputContext) {
ctx.operationService.registerOperations([
planeOperation,
@ -71,5 +63,5 @@ function registerCoreOperations(ctx: WorkbenchesLoaderContext) {
intersectionOperation,
subtractOperation,
unionOperation,
]);
] as any);
}