diff --git a/client/scripts/engine.ts b/client/scripts/engine.ts index e50e6c4f0..01f43d336 100644 --- a/client/scripts/engine.ts +++ b/client/scripts/engine.ts @@ -1,13 +1,7 @@ import thothCore from '@latitudegames/thoth-core/dist/server' const { - components: { - moduleInput, - moduleOutput, - moduleTriggerIn, - moduleTriggerOut, - tenseTransformer, - }, + components: { moduleInput, moduleOutput, tenseTransformer }, } = thothCore export const components = [ @@ -26,8 +20,6 @@ export const components = [ // new ModuleComponent(), moduleInput(), moduleOutput(), - moduleTriggerOut(), - moduleTriggerIn(), // new PlaytestPrint(), // new PlaytestInput(), // new RunInputComponent(), diff --git a/core/package.json b/core/package.json index b4a0a949b..4e7eda925 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@latitudegames/thoth-core", - "version": "0.0.61", + "version": "0.0.62", "license": "Apache-2.0", "author": "Michael Sharpe (https://www.project89.org)", "contributors": [ diff --git a/core/src/components/StateWrite.js b/core/src/components/StateWrite.ts similarity index 80% rename from core/src/components/StateWrite.js rename to core/src/components/StateWrite.ts index ceb5b5fee..a9f615544 100644 --- a/core/src/components/StateWrite.js +++ b/core/src/components/StateWrite.ts @@ -1,6 +1,13 @@ import Rete from 'rete' +import { + NodeData, + ThothNode, + ThothWorkerInputs, + ThothWorkerOutputs, +} from '../../types' import { SocketGeneratorControl } from '../dataControls/SocketGenerator' +import { EngineContext } from '../engine' import { triggerSocket } from '../sockets' import { ThothComponent } from '../thoth-component' @@ -8,7 +15,7 @@ const info = `The State Write component allows you to define any number of input Note here that there are a few assumptions made, which will be changed once we have selectable socket types when generating inputs. If the key already exists in the state and it is an array, whatever value you insert will be added to the array. If the existing value is an object, the object will be updated by the incoming value.` -export class StateWrite extends ThothComponent { +export class StateWrite extends ThothComponent { constructor() { // Name of the component super('State Write') @@ -22,7 +29,7 @@ export class StateWrite extends ThothComponent { this.info = info } - builder(node) { + builder(node: ThothNode) { const dataInput = new Rete.Input('trigger', 'Trigger', triggerSocket, true) const inputGenerator = new SocketGeneratorControl({ @@ -37,11 +44,16 @@ export class StateWrite extends ThothComponent { return node } - async worker(node, inputs, outputs, { thoth }) { + async worker( + node: NodeData, + inputs: ThothWorkerInputs, + outputs: ThothWorkerOutputs, + { thoth }: { thoth: EngineContext } + ) { const { getCurrentGameState, updateCurrentGameState } = thoth try { - const gameState = await getCurrentGameState() + const gameState = (await getCurrentGameState()) as Record let value const updates = Object.entries(inputs).reduce((acc, [key, val]) => { @@ -55,7 +67,7 @@ export class StateWrite extends ThothComponent { } // if it is an object, we assume that the incoming data is an object update - value = { ...gameState[key], ...val[0] } + value = { ...gameState[key], ...(val[0] as unknown[]) } break default: @@ -66,7 +78,7 @@ export class StateWrite extends ThothComponent { acc[key] = value return acc - }, {}) + }, {} as Record) await updateCurrentGameState(updates) } catch (err) { diff --git a/core/src/engine.ts b/core/src/engine.ts index 097416198..259ab8d00 100644 --- a/core/src/engine.ts +++ b/core/src/engine.ts @@ -38,6 +38,8 @@ export abstract class ThothEngineComponent { ...args: unknown[] ): WorkerReturnType } + +// TODO separate the engine context out from the editor context for cleaner typing. export type EngineContext = { completion: ( body: ModelCompletionOpts @@ -58,6 +60,9 @@ export type EngineContext = { onAddModule?: Function onUpdateModule?: Function sendToPlaytest?: Function + onInspector?: Function + sendToInspector?: Function + clearTextEditor?: Function } export type InitEngineArguments = { diff --git a/core/src/plugins/debuggerPlugin/index.ts b/core/src/plugins/debuggerPlugin/index.ts index 87354c8a2..5e3e5b378 100644 --- a/core/src/plugins/debuggerPlugin/index.ts +++ b/core/src/plugins/debuggerPlugin/index.ts @@ -1,10 +1,5 @@ -import { NodeEditor } from 'rete/types' -import { EngineContext } from '../../engine' +import { IRunContextEditor } from '../../../types' import { ThothComponent } from '../../thoth-component' -interface IRunContextEditor extends NodeEditor { - thoth: EngineContext - abort: Function -} function install( editor: IRunContextEditor, @@ -48,9 +43,12 @@ function install( if (!server) { node.data.error = true - const nodeView = [...editor.view.nodes.values()].find( - n => n.node.id === node.id - ) + const nodeValues = Array.from(editor.view.nodes) + const foundNode = nodeValues.find(([, n]) => n.node.id === node.id) + + if (!foundNode) return + + const nodeView = foundNode[1] nodeView?.onStart() nodeView?.node.update() diff --git a/core/src/plugins/inspectorPlugin/DataControl.ts b/core/src/plugins/inspectorPlugin/DataControl.ts index 645ed4496..b2a640e4f 100644 --- a/core/src/plugins/inspectorPlugin/DataControl.ts +++ b/core/src/plugins/inspectorPlugin/DataControl.ts @@ -1,4 +1,5 @@ -import { Node, NodeEditor, Component } from 'rete' +import { Node, NodeEditor } from 'rete' +import { ThothComponent } from '../../thoth-component' import { Inspector } from './Inspector' export type RestProps = {} @@ -6,9 +7,10 @@ export abstract class DataControl { inspector: Inspector | null = null editor: NodeEditor | null = null node: Node | null = null - component: Component | null = null + component: ThothComponent | null = null id: string | null = null dataKey: string + key: string name: string defaultValue: unknown componentData: object @@ -16,6 +18,8 @@ export abstract class DataControl { options: object icon: string write: boolean + //Jake added below + data: Record constructor({ dataKey, diff --git a/core/src/plugins/inspectorPlugin/Inspector.js b/core/src/plugins/inspectorPlugin/Inspector.ts similarity index 75% rename from core/src/plugins/inspectorPlugin/Inspector.js rename to core/src/plugins/inspectorPlugin/Inspector.ts index 4b75972de..35dd6932b 100644 --- a/core/src/plugins/inspectorPlugin/Inspector.js +++ b/core/src/plugins/inspectorPlugin/Inspector.ts @@ -1,14 +1,33 @@ import deepEqual from 'deep-equal' -import Rete from 'rete' +import Rete, { Input, Output } from 'rete' import { v4 as uuidv4 } from 'uuid' +import { DataSocketType, IRunContextEditor, ThothNode } from '../../../types' +import { ThothComponent } from '../../thoth-component' import * as socketMap from '../../sockets' +import { DataControl } from './DataControl' + +type InspectorConstructor = { + component: ThothComponent + editor: IRunContextEditor + node: ThothNode +} + +// todo improve this typing +type DataControlData = Record + export class Inspector { // Stub of function. Can be a nodes catch all onData - onData = () => {} - cache = {} - - constructor({ component, editor, node }) { + onData = Function + cache: Record = {} + node: ThothNode + component: ThothComponent + editor: IRunContextEditor + dataControls: Map + category: string + info: string + + constructor({ component, editor, node }: InspectorConstructor) { this.component = component this.editor = editor this.dataControls = new Map() @@ -16,18 +35,18 @@ export class Inspector { this.category = component.category this.info = component.info } - - _add(list, control, prop) { + // addede DataControl[] + _add(list: Map, control: DataControl) { if (list.has(control.key)) throw new Error( `Item with key '${control.key}' already been added to the inspector` ) - if (control[prop] !== null) + if (control['inspector'] !== null) throw new Error('Inspector has already been added to some control') // Attach the inspector to the incoming control instance - control[prop] = this + control['inspector'] = this control.editor = this.editor control.node = this.node control.component = this.component @@ -39,13 +58,17 @@ export class Inspector { list.set(control.dataKey, control) } - add(dataControl) { - this._add(this.dataControls, dataControl, 'inspector') + add(dataControl: DataControl) { + this._add(this.dataControls, dataControl) dataControl.onAdd() return this } - handleSockets(sockets, control, type) { + handleSockets( + sockets: DataSocketType[], + control: DataControlData, + type: 'inputs' | 'outputs' + ) { // we assume all sockets are of the same type here // and that the data key is set to 'inputs' or 'outputs' const isOutput = type === 'outputs' @@ -53,12 +76,13 @@ export class Inspector { this.node.data[type] = sockets // get all sockets currently on the node - const existingSockets = [] - this.node[type].forEach(out => { + const existingSockets: string[] = [] + + this.node[type]?.forEach(out => { existingSockets.push(out.key) }) - const ignored = (control && control?.data?.ignored) || [] + const ignored: string[] = (control && control?.data?.ignored) || [] // outputs that are on the node but not in the incoming sockets is removed existingSockets @@ -74,10 +98,14 @@ export class Inspector { .forEach(key => { const socket = this.node[type].get(key) + if (!socket) return + // we get the connections for the node and remove that connection const connections = this.node .getConnections() - .filter(con => con[type.slice(0, -1)].key === key) + .filter( + con => con[type.slice(0, -1) as 'input' | 'output'].key === key + ) if (connections) connections.forEach(con => { @@ -86,9 +114,9 @@ export class Inspector { // handle removing the socket, either output or input if (isOutput) { - this.node.removeOutput(socket) + this.node.removeOutput(socket as Output) } else { - this.node.removeInput(socket) + this.node.removeInput(socket as Input) } }) @@ -100,7 +128,8 @@ export class Inspector { // Here we are running over and ensuring that the outputs are in the tasks outputs // We only need to do this with outputs, as inputs don't need to be in the task if (isOutput) { - this.component.task.outputs = this.node.data.outputs.reduce( + const dataOutputs = this.node.data.outputs as DataSocketType[] + this.component.task.outputs = dataOutputs.reduce( (acc, out) => { acc[out.socketKey] = out.taskType || 'output' return acc @@ -123,14 +152,14 @@ export class Inspector { ) if (isOutput) { - this.node.addOutput(newSocket) + this.node.addOutput(newSocket as Output) } else { - this.node.addInput(newSocket) + this.node.addInput(newSocket as Input) } }) } - cacheControls(dataControls) { + cacheControls(dataControls: DataControlData) { const cache = Object.entries(dataControls).reduce( (acc, [key, { expanded = true }]) => { acc[key] = { @@ -139,13 +168,13 @@ export class Inspector { return acc }, - {} + {} as Record ) this.node.data.dataControls = cache } - handleData(update) { + handleData(update: Record) { // store all data controls inside the nodes data // WATCH in case our graphs start getting quite large. if (update.dataControls) this.cacheControls(update.dataControls) @@ -156,7 +185,8 @@ export class Inspector { this.onData(data) // go over each data control - for (const [key, control] of this.dataControls) { + const dataControlArray = Array.from(this.dataControls) + for (const [key, control] of dataControlArray) { const isEqual = deepEqual(this.cache[key], data[key]) // compare agains the cache to see if it has changed @@ -210,13 +240,13 @@ export class Inspector { data() { const dataControls = Array.from(this.dataControls.entries()).reduce( (acc, [key, val]) => { - const cache = this.node?.data?.dataControls + const cache = this.node?.data?.dataControls as DataControlData const cachedControl = cache && cache[key] ? cache[key] : {} // use the data method on controls to get data shape acc[key] = { ...val.control, ...cachedControl } return acc }, - {} + {} as Record ) return { diff --git a/core/src/plugins/inspectorPlugin/index.js b/core/src/plugins/inspectorPlugin/index.ts similarity index 77% rename from core/src/plugins/inspectorPlugin/index.js rename to core/src/plugins/inspectorPlugin/index.ts index 03efd8b19..781f600ab 100644 --- a/core/src/plugins/inspectorPlugin/index.js +++ b/core/src/plugins/inspectorPlugin/index.ts @@ -1,12 +1,14 @@ +import { IRunContextEditor, ThothNode } from '../../../types' +import { ThothComponent } from '../../thoth-component' // @seang todo: convert data controls to typescript to remove this // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import { Inspector } from './Inspector' -function install(editor) { +function install(editor: IRunContextEditor) { const { onInspector, sendToInspector, clearTextEditor } = editor.thoth - editor.on('componentregister', component => { + editor.on('componentregister', (component: ThothComponent) => { const builder = component.builder if (!component.info) @@ -15,7 +17,7 @@ function install(editor) { ) // we are going to override the default builder with our own, and will invoke the original builder inside it. - component.builder = node => { + component.builder = (node: ThothNode) => { // This will unsubscribe us // if (node.subscription) node.subscription() // Inspector class which will handle regsistering data controls, serializing, etc. @@ -33,7 +35,9 @@ function install(editor) { // here we attach the default info control to the component which will show up in the inspector - node.subscription = onInspector(node, data => { + if (!onInspector) return + + node.subscription = onInspector(node, (data: Record) => { node.inspector.handleData(data) editor.trigger('nodecreated') // NOTE might still need this. Keep an eye out. @@ -44,11 +48,12 @@ function install(editor) { } }) - let currentNode + let currentNode: ThothNode | undefined // handle publishing and subscribing to inspector - editor.on('nodeselect', node => { + editor.on('nodeselect', (node: ThothNode) => { if (currentNode && node.id === currentNode.id) return + if (!clearTextEditor || !sendToInspector) return currentNode = node clearTextEditor() sendToInspector(node.inspector.data()) diff --git a/core/src/plugins/socketGenerator/index.js b/core/src/plugins/socketGenerator/index.ts similarity index 63% rename from core/src/plugins/socketGenerator/index.js rename to core/src/plugins/socketGenerator/index.ts index 54e3b419c..57853f83d 100644 --- a/core/src/plugins/socketGenerator/index.js +++ b/core/src/plugins/socketGenerator/index.ts @@ -1,21 +1,25 @@ -import Rete from 'rete' +import Rete, { Input, Output } from 'rete' +import { DataSocketType, IRunContextEditor, ThothNode } from '../../../types' import * as sockets from '../../sockets' +import { ThothComponent } from '../../thoth-component' -function install(editor) { - editor.on('componentregister', component => { +function install(editor: IRunContextEditor) { + editor.on('componentregister', (component: ThothComponent) => { const builder = component.builder // we are going to override the default builder with our own, and will invoke the original builder inside it. - component.builder = node => { + component.builder = (node: ThothNode) => { + const nodeOutputs = node.data.outputs as DataSocketType[] + // Handle outputs in the nodes data to repopulate when loading from JSON - if (node.data.outputs && node.data.outputs.length !== 0) { - const outputMap = {} + if (nodeOutputs && nodeOutputs.length !== 0) { + const outputMap = {} as Record node.outputs.forEach((value, key) => { outputMap[key] = value }) - node.data.outputs.forEach(socket => { + nodeOutputs.forEach(socket => { if (!outputMap[socket.socketKey]) { const output = new Rete.Output( socket.socketKey ? socket.socketKey : socket.name, @@ -27,8 +31,8 @@ function install(editor) { }) } - if (node.data.outputs && node.data.outputs.length > 0) { - component.task.outputs = node.data.outputs.reduce( + if (nodeOutputs && nodeOutputs.length > 0) { + component.task.outputs = nodeOutputs.reduce( (acc, out) => { acc[out.socketKey] = out.taskType || 'output' return acc @@ -37,14 +41,16 @@ function install(editor) { ) } - if (node.data.inputs && node.data.inputs.length !== 0) { + const nodeInputs = node.data.inputs as DataSocketType[] + + if (nodeInputs && nodeInputs.length !== 0) { // get inputs from node.inputs - const inputMap = {} + const inputMap = {} as Record node.inputs.forEach((value, key) => { inputMap[key] = value }) - node.data.inputs.forEach(socket => { + nodeInputs.forEach(socket => { // If the input key is already on the node, return if (inputMap[socket.socketKey]) return const input = new Rete.Input( diff --git a/core/src/plugins/taskPlugin/task.ts b/core/src/plugins/taskPlugin/task.ts index e31124154..d9bd9fc3a 100644 --- a/core/src/plugins/taskPlugin/task.ts +++ b/core/src/plugins/taskPlugin/task.ts @@ -102,16 +102,13 @@ export class Task { const inputs = {} as Record /* - This is where we are populating all the input values to be passed into the worker. - We are getting all the input connections that are connected as outputs (ie have values) - We filter out all connections which did not come from the previou node. This is to hgelp support multiple - inputs properly, otherwise we actually back propagate along every input and run it, whichI think is unwanted behaviour. - After we have filtered these out, we need to run the task, which triggers that nodes worker. After the worker runs, - the task has populated output data, which we take and we associate with the tasks input values, which are subsequently + This is where we are populating all the input values to be passed into the worker. We are getting all the input connections that are connected as outputs (ie have values) + We filter out all connections which did not come from the previou node. This is to hgelp support multiple inputs properly, otherwise we actually back propagate along every input and run it, whichI think is unwanted behaviour. + + After we have filtered these out, we need to run the task, which triggers that nodes worker. After the worker runs, the task has populated output data, which we take and we associate with the tasks input values, which are subsequently passed to the nodes worker for processing. - We assume here that his nodes worker does not need to access ALL values simultaneously, but is only interested in one. - There is a task option which enables this functionality just in case we have use cases that don't want this behaviour. + We assume here that his nodes worker does not need to access ALL values simultaneously, but is only interested in one. There is a task option which enables this functionality just in case we have use cases that don't want this behaviour. */ await Promise.all( this.getInputs('output').map(async key => { diff --git a/core/src/thoth-component.ts b/core/src/thoth-component.ts index 442563815..7f613ef65 100644 --- a/core/src/thoth-component.ts +++ b/core/src/thoth-component.ts @@ -53,8 +53,7 @@ export abstract class ThothComponent< constructor(name: string) { super(name) } - - abstract builder(node: ThothNode): Promise | ThothNode + abstract builder(node: ThothNode): Promise | ThothNode | void async build(node: ThothNode) { await this.builder(node) diff --git a/core/types.ts b/core/types.ts index 138a6f846..25f4bf848 100644 --- a/core/types.ts +++ b/core/types.ts @@ -1,5 +1,5 @@ /* eslint-disable camelcase */ -import { Component, Connection, Input, Output } from 'rete' +import { Component, Connection, Input, Output, NodeEditor } from 'rete' import { Node } from 'rete/types' //@seang todo: convert inspector plugin fully to typescript // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -14,6 +14,7 @@ import { Inspector } from './src/plugins/inspectorPlugin/Inspector' import { ModuleGraphData } from './src/plugins/modulePlugin/module-manager' import { TaskOutputTypes } from './src/plugins/taskPlugin/task' import { SocketNameType, SocketType } from './src/sockets' +import { EngineContext } from './src/engine' import { ThothTask } from './src/thoth-component' export type EventsTypes = { @@ -31,6 +32,11 @@ export type EventsTypes = { resetconnection: void } +export interface IRunContextEditor extends NodeEditor { + thoth: EngineContext + abort: Function +} + export type DataSocketType = { name: SocketNameType taskType: 'output' | 'option' @@ -43,6 +49,11 @@ export type ThothNode = Node & { inspector: Inspector display: (content: string) => void outputs: { name: string; [key: string]: unknown }[] + category?: string + deprecated?: boolean + displayName?: string + info: string + subscription: Function } export type ModuleType = { diff --git a/tsconfig.json b/tsconfig.json index 48e1d6471..68ff11207 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,8 +19,7 @@ "strictNullChecks": true, "strict": false, "rootDir": ".", - "noEmit": true, - "downlevelIteration": true + "noEmit": true }, "exclude": ["node_modules", "dist", "*.d.ts"], "typeRoots": ["@types/@thoth"]