From 639f4b5aa01e2d7a15fc711d6bdf26b4d6fc36b5 Mon Sep 17 00:00:00 2001 From: Matei Adriel Date: Tue, 23 Jul 2019 14:56:11 +0300 Subject: [PATCH] integrated circuits --- package-lock.json | 22 ++++ package.json | 3 +- src/common/canvas/helpers/clearCanvas.ts | 9 +- src/common/canvas/helpers/drawPolygon.ts | 7 + .../canvas/helpers/drawRotatedSquare.ts | 39 +++--- src/common/canvas/helpers/drawRoundedImage.ts | 35 +++++ .../canvas/helpers/drawRoundedSquare.ts | 8 +- src/common/canvas/helpers/useTransform.ts | 5 + src/modules/activation/types/Context.ts | 9 ++ .../integrated-circuits/helpers/compileIc.ts | 40 ++++++ .../helpers/simulationIoCount.ts | 34 +++++ .../logic-gates/components/LogicGateModal.tsx | 26 ++-- src/modules/logic-gates/constants.ts | 7 + .../logic-gates/helpers/completeTemplate.ts | 9 ++ src/modules/logic-gates/helpers/getAllIcs.ts | 26 ---- .../logic-gates/subjects/LogicGateList.ts | 27 +--- src/modules/saving/constants.ts | 12 +- src/modules/saving/helpers/fromState.ts | 11 +- src/modules/saving/helpers/save.ts | 5 + src/modules/simulation/classes/Gate.ts | 122 +++++++++++++++--- src/modules/simulation/classes/Pin.ts | 6 +- src/modules/simulation/classes/Simulation.ts | 10 +- src/modules/simulation/classes/Wire.ts | 12 +- src/modules/simulation/constants.ts | 13 +- src/modules/simulation/types/GateTemplate.ts | 11 +- .../classes/SimulationRenderer.ts | 7 +- src/modules/simulationRenderer/constants.ts | 3 + .../simulationRenderer/helpers/renderGate.ts | 33 ++++- .../simulationRenderer/stores/imageStore.ts | 54 ++++++++ webpack.config.js | 18 ++- 30 files changed, 497 insertions(+), 126 deletions(-) create mode 100644 src/common/canvas/helpers/drawRoundedImage.ts create mode 100644 src/modules/integrated-circuits/helpers/compileIc.ts create mode 100644 src/modules/integrated-circuits/helpers/simulationIoCount.ts create mode 100644 src/modules/logic-gates/constants.ts create mode 100644 src/modules/logic-gates/helpers/completeTemplate.ts delete mode 100644 src/modules/logic-gates/helpers/getAllIcs.ts create mode 100644 src/modules/simulationRenderer/stores/imageStore.ts diff --git a/package-lock.json b/package-lock.json index d8632ae..e6255f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4268,6 +4268,28 @@ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, + "file-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.1.0.tgz", + "integrity": "sha512-ajDk1nlByoalZAGR4b0H6oD+EGlWnyW1qbSxzaUc7RFiqmn+RbXQQRbTc72jsiUIlVusJ4Et58ltds8ZwTfnAw==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "schema-utils": "^2.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz", + "integrity": "sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", diff --git a/package.json b/package.json index 639bdbc..69c2f93 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "webpack-dev-server --open --mode development", "build": "cross-env NODE_ENV=production webpack", "deploy": "ts-node deploy", - "show": "gource -f --start-date \"2019-07-01 12:00\" --key --hide dirnames,filenames,bloom -s 3" + "show": "gource -f --start-date \"2019-07-01 12:00\" --key --hide dirnames,filenames,bloom -s 1" }, "devDependencies": { "@babel/core": "^7.5.5", @@ -24,6 +24,7 @@ "babel-plugin-transform-runtime": "^6.23.0", "babel-regenerator-runtime": "^6.5.0", "css-loader": "^3.0.0", + "file-loader": "^4.1.0", "html-webpack-inline-source-plugin": "0.0.10", "html-webpack-plugin": "^3.2.0", "mini-css-extract-plugin": "^0.8.0", diff --git a/src/common/canvas/helpers/clearCanvas.ts b/src/common/canvas/helpers/clearCanvas.ts index d39ec16..fca0c01 100644 --- a/src/common/canvas/helpers/clearCanvas.ts +++ b/src/common/canvas/helpers/clearCanvas.ts @@ -1,8 +1,15 @@ -import { SimulationRenderer } from '../../../modules/simulationRenderer/classes/SimulationRenderer' import { Screen } from '../../../modules/core/classes/Screen' +/** + * A screen instance used for the canvas clearing + */ const screen = new Screen() +/** + * Clears the used portion of the canvas + * + * @param ctx the context to clear + */ export const clearCanvas = (ctx: CanvasRenderingContext2D) => { ctx.clearRect(0, 0, screen.x, screen.y) } diff --git a/src/common/canvas/helpers/drawPolygon.ts b/src/common/canvas/helpers/drawPolygon.ts index a5ab378..f73d1c3 100644 --- a/src/common/canvas/helpers/drawPolygon.ts +++ b/src/common/canvas/helpers/drawPolygon.ts @@ -1,5 +1,12 @@ import { vector2 } from '../../math/types/vector2' +/** + * + * @param ctx The context to draw on + * @param points an array of points to draw + * @param fill if true the polygon will be filled + * @param stroke if true the polygno will be stroked + */ export const drawPolygon = ( ctx: CanvasRenderingContext2D, points: vector2[], diff --git a/src/common/canvas/helpers/drawRotatedSquare.ts b/src/common/canvas/helpers/drawRotatedSquare.ts index f5e4ebf..1458f5b 100644 --- a/src/common/canvas/helpers/drawRotatedSquare.ts +++ b/src/common/canvas/helpers/drawRotatedSquare.ts @@ -1,33 +1,34 @@ import { Transform } from '../../math/classes/Transform' -import { Material, Shape } from '../../../modules/simulation/types/GateTemplate' +import { Shape } from '../../../modules/simulation/types/GateTemplate' import { roundRect } from './drawRoundedSquare' +import { useTransform } from './useTransform' +/** + * Draws a square from a transform + * + * @param ctx The context to draw on + * @param transform -The transform to use + * @param shape - The shae object to use + */ export const drawRotatedSquare = ( ctx: CanvasRenderingContext2D, - { position, scale, rotation }: Transform, + transform: Transform, shape: Shape ) => { ctx.save() - ctx.translate(...position) - ctx.translate(scale[0] / 2, scale[1] / 2) + const relative = useTransform(ctx, transform) - ctx.rotate(rotation) + roundRect( + ctx, + relative.x, + relative.y, + relative.width, + relative.height, + shape.radius ? shape.radius : 0 + ) - if (shape.rounded) { - roundRect( - ctx, - -scale[0] / 2, - -scale[1] / 2, - scale[0], - scale[1], - shape.radius - ) - - ctx.fill() - } else { - ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale) - } + ctx.fill() ctx.restore() } diff --git a/src/common/canvas/helpers/drawRoundedImage.ts b/src/common/canvas/helpers/drawRoundedImage.ts new file mode 100644 index 0000000..cc69daf --- /dev/null +++ b/src/common/canvas/helpers/drawRoundedImage.ts @@ -0,0 +1,35 @@ +/** + * + * @param ctx The context to draw on + * @param x the x of the rect + * @param y the y of the rect + * @param width the width of the rect + * @param height the height of the rect + * @param radius the radius of the corners + */ +export function roundImage( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement, + x: number = 0, + y: number = 0, + width: number = 100, + height: number = 100, + radius: number = 5 +) { + ctx.beginPath() + ctx.moveTo(x + radius, y) + ctx.lineTo(x + width - radius, y) + ctx.quadraticCurveTo(x + width, y, x + width, y + radius) + ctx.lineTo(x + width, y + height - radius) + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) + ctx.lineTo(x + radius, y + height) + ctx.quadraticCurveTo(x, y + height, x, y + height - radius) + ctx.lineTo(x, y + radius) + ctx.quadraticCurveTo(x, y, x + radius, y) + ctx.closePath() + + ctx.save() + ctx.clip() + ctx.drawImage(image, x, y, width, height) + ctx.restore() +} diff --git a/src/common/canvas/helpers/drawRoundedSquare.ts b/src/common/canvas/helpers/drawRoundedSquare.ts index 70f2c00..89b115c 100644 --- a/src/common/canvas/helpers/drawRoundedSquare.ts +++ b/src/common/canvas/helpers/drawRoundedSquare.ts @@ -3,10 +3,10 @@ */ export function roundRect( ctx: CanvasRenderingContext2D, - x: number, - y: number, - width: number, - height: number, + x: number = 0, + y: number = 0, + width: number = 100, + height: number = 100, radius: number = 5 ) { ctx.beginPath() diff --git a/src/common/canvas/helpers/useTransform.ts b/src/common/canvas/helpers/useTransform.ts index 485be0d..57e9290 100644 --- a/src/common/canvas/helpers/useTransform.ts +++ b/src/common/canvas/helpers/useTransform.ts @@ -1,6 +1,11 @@ import { Transform } from '../../math/classes/Transform' import { multiply } from '../../../modules/vector2/helpers/basic' +/** + * + * @param ctx The context to use + * @param transform The transform to move relative to + */ export const useTransform = ( ctx: CanvasRenderingContext2D, { position, rotation, scale }: Transform diff --git a/src/modules/activation/types/Context.ts b/src/modules/activation/types/Context.ts index 973583c..815e869 100644 --- a/src/modules/activation/types/Context.ts +++ b/src/modules/activation/types/Context.ts @@ -1,6 +1,15 @@ +import { Simulation, SimulationEnv } from '../../simulation/classes/Simulation' +import { SimulationError } from '../../errors/classes/SimulationError' +import { fromSimulationState } from '../../saving/helpers/fromState' + export interface Context { memory: Record get: (index: number) => boolean set: (index: number, state: boolean) => void color: (color: string) => void + enviroment: SimulationEnv +} + +export interface InitialisationContext { + memory: Record } diff --git a/src/modules/integrated-circuits/helpers/compileIc.ts b/src/modules/integrated-circuits/helpers/compileIc.ts new file mode 100644 index 0000000..0211eb4 --- /dev/null +++ b/src/modules/integrated-circuits/helpers/compileIc.ts @@ -0,0 +1,40 @@ +import { SimulationState } from '../../saving/types/SimulationSave' +import { SimulationError } from '../../errors/classes/SimulationError' +import { GateTemplate } from '../../simulation/types/GateTemplate' +import { + simulationInputCount, + simulationOutputCount +} from './simulationIoCount' +import { InitialisationContext } from '../../activation/types/Context' +import { templateStore } from '../../saving/stores/templateStore' + +/** + * Compiles a simulation into a logicGate + * + * @param simulaton The simulation to compile + */ +export const compileIc = ({ mode, name, gates }: SimulationState) => { + if (mode === 'project') { + throw new SimulationError('Cannot compile project') + } + + const inputCount = simulationInputCount(gates) + const outputCount = simulationOutputCount(gates) + + const result: DeepPartial = { + metadata: { + name + }, + tags: ['integrated'], + pins: { + inputs: { + count: inputCount + }, + outputs: { + count: outputCount + } + } + } + + templateStore.set(name, result) +} diff --git a/src/modules/integrated-circuits/helpers/simulationIoCount.ts b/src/modules/integrated-circuits/helpers/simulationIoCount.ts new file mode 100644 index 0000000..8d44a20 --- /dev/null +++ b/src/modules/integrated-circuits/helpers/simulationIoCount.ts @@ -0,0 +1,34 @@ +import { GateState } from '../../saving/types/SimulationSave' +import { GateTemplate } from '../../simulation/types/GateTemplate' +import { templateStore } from '../../saving/stores/templateStore' + +/** + * Any type of gate wich has a template + */ +export type hasTemplate = { template: string } + +/** + * Counts the number of ic inputs inside an array of gate states + * + * @param gates The state to count the inputs in + */ +export const simulationInputCount = (gates: hasTemplate[]) => { + return gates.filter(gate => { + const template = templateStore.get(gate.template) + + return template && template.integration && template.integration.input + }).length +} + +/** + * Counts the number of ic outputs inside an array of gate states + * + * @param gates The state to count the outputs for + */ +export const simulationOutputCount = (gates: hasTemplate[]) => { + return gates.filter(gate => { + const template = templateStore.get(gate.template) + + return template && template.integration && template.integration.output + }).length +} diff --git a/src/modules/logic-gates/components/LogicGateModal.tsx b/src/modules/logic-gates/components/LogicGateModal.tsx index 6bad1fc..588787c 100644 --- a/src/modules/logic-gates/components/LogicGateModal.tsx +++ b/src/modules/logic-gates/components/LogicGateModal.tsx @@ -10,6 +10,8 @@ import { rendererSubject } from '../../core/subjects/rendererSubject' import { SimulationError } from '../../errors/classes/SimulationError' import { templateStore } from '../../saving/stores/templateStore' import { randomItem } from '../../internalisation/helpers/randomItem' +import { completeTemplate } from '../helpers/completeTemplate' +import { gateIcons } from '../constants' /** * Subject containing the open state of the modal @@ -43,12 +45,11 @@ const LogicGateModal = () => { throw new SimulationError(`Renderer not found`) } - const template = - gate.source === 'base' ? templateStore.get(gate.name) : '' + const template = completeTemplate(templateStore.get(gate) || {}) - if (gate.source === 'base' && !template) { + if (!template) { throw new SimulationError( - `Template ${gate.name} cannot be found` + `Template ${gate} cannot be found` ) } @@ -57,29 +58,28 @@ const LogicGateModal = () => { key={index} className="logic-gate-item" onClick={e => { - addGate(renderer.simulation, gate.name) + addGate(renderer.simulation, gate) }} > - {gate.source === 'base' ? 'sd_storage' : 'memory'} + {gateIcons[template.tags[0]]} - {gate.name} + {gate} - {template && template.info && template.info.length && ( + {template.info.length && ( { + e.stopPropagation() + e.preventDefault() + }} > info )} - {gate.source === 'ic' && ( - - delete - - )} ) })} diff --git a/src/modules/logic-gates/constants.ts b/src/modules/logic-gates/constants.ts new file mode 100644 index 0000000..f4eb32b --- /dev/null +++ b/src/modules/logic-gates/constants.ts @@ -0,0 +1,7 @@ +import { GateTag } from '../simulation/types/GateTemplate' + +export const gateIcons: Record = { + base: 'house', + imported: 'share', + integrated: 'memory' +} diff --git a/src/modules/logic-gates/helpers/completeTemplate.ts b/src/modules/logic-gates/helpers/completeTemplate.ts new file mode 100644 index 0000000..98b43d8 --- /dev/null +++ b/src/modules/logic-gates/helpers/completeTemplate.ts @@ -0,0 +1,9 @@ +import merge from 'deepmerge' +import { GateTemplate } from '../../simulation/types/GateTemplate' +import { DefaultGateTemplate } from '../../simulation/constants' + +export const completeTemplate = (template: DeepPartial) => { + return merge(DefaultGateTemplate, template, { + arrayMerge: (a: unknown[], b: unknown[]) => b + }) as GateTemplate +} diff --git a/src/modules/logic-gates/helpers/getAllIcs.ts b/src/modules/logic-gates/helpers/getAllIcs.ts deleted file mode 100644 index bb1a74a..0000000 --- a/src/modules/logic-gates/helpers/getAllIcs.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { saveStore } from '../../saving/stores/saveStore' -import { SimulationError } from '../../errors/classes/SimulationError' - -/** - * Helper to get the names of all integrated circuits - * - * @throws SimulationError if a save cannot be found in localsStorage - */ -export const getAllics = () => { - const saves = saveStore.ls() - const result: string[] = [] - - for (const save of saves) { - const saveState = saveStore.get(save) - - if (saveState) { - if (saveState.simulation.mode === 'ic') { - result.push(saveState.simulation.name) - } - } else { - throw new SimulationError(`Cannot find save ${save}`) - } - } - - return result -} diff --git a/src/modules/logic-gates/subjects/LogicGateList.ts b/src/modules/logic-gates/subjects/LogicGateList.ts index d141c1f..3af01b7 100644 --- a/src/modules/logic-gates/subjects/LogicGateList.ts +++ b/src/modules/logic-gates/subjects/LogicGateList.ts @@ -1,37 +1,14 @@ import { BehaviorSubject } from 'rxjs' import { templateStore } from '../../saving/stores/templateStore' -import { getAllics } from '../helpers/getAllIcs' - -/** - * The interface for the items in the list - */ -export interface LogicGateNameWrapper { - source: 'base' | 'ic' - name: string -} /** * Subject containing a list with the names of all logic gate templates */ -export const LogicGateList = new BehaviorSubject([]) +export const LogicGateList = new BehaviorSubject([]) /** * Helper method to update the list of logic gate templates. */ export const updateLogicGateList = () => { - const ics = getAllics().map( - (name): LogicGateNameWrapper => ({ - source: 'ic', - name - }) - ) - - const templates = templateStore.ls().map( - (name): LogicGateNameWrapper => ({ - source: 'base', - name - }) - ) - - LogicGateList.next([...ics, ...templates]) + LogicGateList.next(templateStore.ls()) } diff --git a/src/modules/saving/constants.ts b/src/modules/saving/constants.ts index ab956a8..d748cb1 100644 --- a/src/modules/saving/constants.ts +++ b/src/modules/saving/constants.ts @@ -8,7 +8,8 @@ export const baseTemplates: DeepPartial[] = [ name: 'and' }, material: { - value: 'green' + type: 'image', + value: require('../../assets/and_gate') }, code: { activation: `context.set(0, context.get(0) && context.get(1))` @@ -42,7 +43,8 @@ export const baseTemplates: DeepPartial[] = [ name: 'xor' }, material: { - value: 'white' + type: 'image', + value: require('../../assets/xor_gate') }, code: { activation: ` @@ -94,6 +96,9 @@ export const baseTemplates: DeepPartial[] = [ count: 0 } }, + integration: { + input: true + }, info: ['https://en.wikipedia.org/wiki/Push-button'] }, { @@ -111,6 +116,9 @@ export const baseTemplates: DeepPartial[] = [ context.color(context.get(0) ? 'yellow' : 'white') ` }, + integration: { + output: true + }, info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'], pins: { outputs: { diff --git a/src/modules/saving/helpers/fromState.ts b/src/modules/saving/helpers/fromState.ts index 02bd080..b935767 100644 --- a/src/modules/saving/helpers/fromState.ts +++ b/src/modules/saving/helpers/fromState.ts @@ -6,7 +6,7 @@ import { } from '../types/SimulationSave' import { Transform } from '../../../common/math/classes/Transform' import { Camera } from '../../simulationRenderer/classes/Camera' -import { Simulation } from '../../simulation/classes/Simulation' +import { Simulation, SimulationEnv } from '../../simulation/classes/Simulation' import { Wire } from '../../simulation/classes/Wire' import { templateStore } from '../stores/templateStore' @@ -26,8 +26,11 @@ export const fromCameraState = (state: CameraState): Camera => { return camera } -export const fromSimulationState = (state: SimulationState): Simulation => { - const simulation = new Simulation(state.mode, state.name) +export const fromSimulationState = ( + state: SimulationState, + env: SimulationEnv = 'global' +): Simulation => { + const simulation = new Simulation(state.mode, state.name, env) for (const gateState of state.gates) { const gate = new Gate( @@ -60,7 +63,7 @@ export const fromSimulationState = (state: SimulationState): Simulation => { value: endGateNode.data._pins.inputs[wireState.to.index] } - const wire = new Wire(start, end, wireState.id) + const wire = new Wire(start, end, false, wireState.id) simulation.wires.push(wire) } diff --git a/src/modules/saving/helpers/save.ts b/src/modules/saving/helpers/save.ts index 3ba0821..65830aa 100644 --- a/src/modules/saving/helpers/save.ts +++ b/src/modules/saving/helpers/save.ts @@ -6,6 +6,7 @@ import { saveStore } from '../stores/saveStore' import { toast } from 'react-toastify' import { createToastArguments } from '../../toasts/helpers/createToastArguments' import { CurrentLanguage } from '../../internalisation/stores/currentLanguage' +import { compileIc } from '../../integrated-circuits/helpers/compileIc' /** * Saves the state from a renderer in localStorage, @@ -25,6 +26,10 @@ export const save = (renderer: SimulationRenderer) => { saveStore.set(current, state) + if (state.simulation.mode === 'ic') { + compileIc(state.simulation) + } + toast( ...createToastArguments( translation.messages.savedSimulation(current), diff --git a/src/modules/simulation/classes/Gate.ts b/src/modules/simulation/classes/Gate.ts index 5bbbd11..d6c3d5a 100644 --- a/src/modules/simulation/classes/Gate.ts +++ b/src/modules/simulation/classes/Gate.ts @@ -1,15 +1,18 @@ import { Transform } from '../../../common/math/classes/Transform' import { Pin } from './Pin' -import merge from 'deepmerge' import { GateTemplate, PinCount } from '../types/GateTemplate' -import { DefaultGateTemplate } from '../constants' import { idStore } from '../stores/idStore' -import { Context } from '../../activation/types/Context' +import { Context, InitialisationContext } from '../../activation/types/Context' import { toFunction } from '../../activation/helpers/toFunction' -import { Subscription, combineLatest } from 'rxjs' +import { Subscription } from 'rxjs' import { SimulationError } from '../../errors/classes/SimulationError' -import { throttleTime, debounce, debounceTime } from 'rxjs/operators' import { getGateTimePipes } from '../helpers/getGateTimePipes' +import { ImageStore } from '../../simulationRenderer/stores/imageStore' +import { completeTemplate } from '../../logic-gates/helpers/completeTemplate' +import { Simulation, SimulationEnv } from './Simulation' +import { fromSimulationState } from '../../saving/helpers/fromState' +import { saveStore } from '../../saving/stores/saveStore' +import { Wire } from './Wire' export interface GatePins { inputs: Pin[] @@ -47,8 +50,14 @@ export class Gate { private subscriptions: Subscription[] = [] private memory: Record = {} + // Related to integration + private ghostSimulation: Simulation + private ghostWires: Wire[] = [] + private isIntegrated = false + public env: SimulationEnv = 'global' + public constructor(template: DeepPartial = {}, id?: number) { - this.template = merge(DefaultGateTemplate, template) as GateTemplate + this.template = completeTemplate(template) this.transform.scale = this.template.shape.scale @@ -73,6 +82,10 @@ export class Gate { this ) + if (this.template.material.type === 'image') { + ImageStore.set(this.template.material.value) + } + this.id = id !== undefined ? id : idStore.generate() for (const pin of this._pins.inputs) { @@ -84,6 +97,81 @@ export class Gate { this.subscriptions.push(subscription) } + + this.init() + + if (this.template.tags.includes('integrated')) { + this.isIntegrated = true + } + + if (this.isIntegrated) { + const state = saveStore.get(this.template.metadata.name) + + if (!state) { + throw new SimulationError( + `Cannot run ic ${ + this.template.metadata.name + } - save not found` + ) + } + + this.ghostSimulation = fromSimulationState(state.simulation, 'gate') + + const sortByPosition = (x: Gate, y: Gate) => + x.transform.position[1] - y.transform.position[1] + + const gates = Array.from(this.ghostSimulation.gates) + + const inputs = gates + .filter(gate => gate.template.integration.input) + .sort(sortByPosition) + .map(gate => gate.wrapPins(gate._pins.outputs)) + .flat() + + const outputs = gates + .filter(gate => gate.template.integration.output) + .sort(sortByPosition) + .map(gate => gate.wrapPins(gate._pins.inputs)) + .flat() + + if (inputs.length !== this._pins.inputs.length) { + throw new SimulationError( + `Input count needs to match with the container gate` + ) + } + + if (outputs.length !== this._pins.outputs.length) { + throw new SimulationError( + `Output count needs to match with the container gate` + ) + } + + const wrappedInputs = this.wrapPins(this._pins.inputs) + const wrappedOutputs = this.wrapPins(this._pins.outputs) + + for (let index = 0; index < inputs.length; index++) { + this.ghostWires.push( + new Wire(wrappedInputs[index], inputs[index], true) + ) + } + + for (let index = 0; index < outputs.length; index++) { + this.ghostWires.push( + new Wire(outputs[index], wrappedOutputs[index], true) + ) + } + + this.ghostSimulation.wires.push(...this.ghostWires) + } + } + + private init() { + toFunction<[InitialisationContext]>( + this.template.code.initialisation, + 'context' + )({ + memory: this.memory + }) } public onClick() { @@ -100,15 +188,22 @@ export class Gate { for (const subscription of this.subscriptions) { subscription.unsubscribe() } + + if (this.isIntegrated) { + this.ghostSimulation.dispose() + } } public update() { - const context = this.getContext() + if (this.template.tags.includes('integrated')) { + } else { + const context = this.getContext() - if (!this.functions.activation) - throw new SimulationError('Activation function is missing') + if (!this.functions.activation) + throw new SimulationError('Activation function is missing') - this.functions.activation(context) + this.functions.activation(context) + } } public getContext(): Context { @@ -124,14 +219,11 @@ export class Gate { if (this.template.material.type === 'color') { this.template.material.value = color } - } + }, + enviroment: this.env } } - private getInputsStates() { - return this._pins.inputs.map(pin => pin.state) - } - private wrapPins(pins: Pin[]) { const result: PinWrapper[] = [] const length = pins.length diff --git a/src/modules/simulation/classes/Pin.ts b/src/modules/simulation/classes/Pin.ts index 7d61728..2dd934b 100644 --- a/src/modules/simulation/classes/Pin.ts +++ b/src/modules/simulation/classes/Pin.ts @@ -16,8 +16,10 @@ export class Pin { public constructor(public type = 0b01, public gate: Gate) {} - public addPair(pin: Pin, subscribe = false) { - this.pairs.add(pin) + public addPair(pin: Pin, subscribe = false, remember = true) { + if (remember) { + this.pairs.add(pin) + } if (subscribe) { const rawSubscription = pin.state.subscribe(state => { diff --git a/src/modules/simulation/classes/Simulation.ts b/src/modules/simulation/classes/Simulation.ts index 6e6f038..90a9550 100644 --- a/src/modules/simulation/classes/Simulation.ts +++ b/src/modules/simulation/classes/Simulation.ts @@ -4,17 +4,25 @@ import { LruCacheNode } from '@eix-js/utils' import { Wire } from './Wire' import { simulationMode } from '../../saving/types/SimulationSave' +/** + * The env a simulation can run in + */ +export type SimulationEnv = 'gate' | 'global' + export class Simulation { public gates = new GateStorage() public wires: Wire[] = [] public constructor( public mode: simulationMode = 'project', - public name: string + public name: string, + public env: SimulationEnv = 'global' ) {} public push(...gates: Gate[]) { for (const gate of gates) { + gate.env = this.env + const node = new LruCacheNode(gate.id, gate) this.gates.set(gate.id, node) diff --git a/src/modules/simulation/classes/Wire.ts b/src/modules/simulation/classes/Wire.ts index 5abcffe..65ac442 100644 --- a/src/modules/simulation/classes/Wire.ts +++ b/src/modules/simulation/classes/Wire.ts @@ -9,14 +9,20 @@ export class Wire { public constructor( public start: PinWrapper, public end: PinWrapper, + ic: boolean = false, id?: number ) { - if (end.value.pairs.size !== 0) { + if (!ic && end.value.pairs.size !== 0) { throw new SimulationError('An input pin can only have 1 pair') } - end.value.addPair(start.value, true) - start.value.addPair(end.value) + end.value.addPair(start.value, true, !ic) + start.value.addPair(end.value, false, !ic) + + // if (ic) { + // start.value.state.subscribe(console.log) + // end.value.state.subscribe(console.log) + // } this.id = id !== undefined ? id : idStore.generate() } diff --git a/src/modules/simulation/constants.ts b/src/modules/simulation/constants.ts index bb2c815..aaaa637 100644 --- a/src/modules/simulation/constants.ts +++ b/src/modules/simulation/constants.ts @@ -24,8 +24,9 @@ export const DefaultGateTemplate: GateTemplate = { scale: [100, 100] }, code: { - activation: 'context.set(0,true)', - onClick: '' + activation: '', + onClick: '', + initialisation: '' }, simulation: { debounce: { @@ -36,5 +37,11 @@ export const DefaultGateTemplate: GateTemplate = { enabled: false } }, - info: [] + integration: { + allowed: true, + input: false, + output: false + }, + info: [], + tags: ['base'] } diff --git a/src/modules/simulation/types/GateTemplate.ts b/src/modules/simulation/types/GateTemplate.ts index 17af852..1759811 100644 --- a/src/modules/simulation/types/GateTemplate.ts +++ b/src/modules/simulation/types/GateTemplate.ts @@ -6,7 +6,7 @@ export interface PinCount { } export interface Material { - type: 'color' + type: 'color' | 'image' value: string } @@ -28,6 +28,8 @@ export type TimePipe = Enabled<{ time: number }> +export type GateTag = 'base' | 'imported' | 'integrated' + export interface GateTemplate { material: Material shape: Shape @@ -39,6 +41,7 @@ export interface GateTemplate { name: string } code: { + initialisation: string activation: string onClick: string } @@ -46,5 +49,11 @@ export interface GateTemplate { throttle: TimePipe debounce: TimePipe } + integration: { + allowed: boolean + input: boolean + output: boolean + } info: string[] + tags: GateTag[] } diff --git a/src/modules/simulationRenderer/classes/SimulationRenderer.ts b/src/modules/simulationRenderer/classes/SimulationRenderer.ts index b4d99f4..b924baf 100644 --- a/src/modules/simulationRenderer/classes/SimulationRenderer.ts +++ b/src/modules/simulationRenderer/classes/SimulationRenderer.ts @@ -115,17 +115,22 @@ export class SimulationRenderer { pin.value === this.selectedPins.start.wrapper.value ) { this.selectedPins.start = null + this.selectedPins.end = null } else if ( this.selectedPins.end && pin.value === this.selectedPins.end.wrapper.value ) { + this.selectedPins.start = null this.selectedPins.end = null } else if ((pin.value.type & 0b10) >> 1) { this.selectedPins.start = { wrapper: pin, transform } - } else if (pin.value.type & 1) { + } else if ( + pin.value.type & 1 && + pin.value.pairs.size === 0 + ) { this.selectedPins.end = { wrapper: pin, transform diff --git a/src/modules/simulationRenderer/constants.ts b/src/modules/simulationRenderer/constants.ts index 6133bdf..cefb7b6 100644 --- a/src/modules/simulationRenderer/constants.ts +++ b/src/modules/simulationRenderer/constants.ts @@ -1,4 +1,5 @@ import { SimulationRendererOptions } from './types/SimulationRendererOptions' +import { vector2 } from '../../common/math/classes/Transform' export const defaultSimulationRendererOptions: SimulationRendererOptions = { dnd: { @@ -24,3 +25,5 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = { curvePointOffset: 100 } } + +export const imageQuality: vector2 = [100, 100] diff --git a/src/modules/simulationRenderer/helpers/renderGate.ts b/src/modules/simulationRenderer/helpers/renderGate.ts index 7749d95..0369246 100644 --- a/src/modules/simulationRenderer/helpers/renderGate.ts +++ b/src/modules/simulationRenderer/helpers/renderGate.ts @@ -1,7 +1,10 @@ import { Gate } from '../../simulation/classes/Gate' -import { drawRotatedSquare } from '../../../common/canvas/helpers/drawRotatedSquare' import { renderPins } from './renderPins' import { SimulationRenderer } from '../classes/SimulationRenderer' +import { useTransform } from '../../../common/canvas/helpers/useTransform' +import { roundRect } from '../../../common/canvas/helpers/drawRoundedSquare' +import { roundImage } from '../../../common/canvas/helpers/drawRoundedImage' +import { ImageStore } from '../stores/imageStore' export const renderGate = ( ctx: CanvasRenderingContext2D, @@ -18,9 +21,33 @@ export const renderGate = ( ctx.lineWidth = renderer.options.gates.gateStroke.width + ctx.save() + const r = useTransform(ctx, gate.transform) + const renderingParameters = [ + r.x, + r.y, + r.width, + r.height, + gate.template.shape.rounded ? gate.template.shape.radius : 0 + ] + + if (gate.template.material.type === 'image') { + roundImage( + ctx, + ImageStore.get(gate.template.material.value), + ...renderingParameters + ) + } + + roundRect(ctx, ...renderingParameters) + + ctx.stroke() + if (gate.template.material.type === 'color') { ctx.fillStyle = gate.template.material.value - drawRotatedSquare(ctx, gate.transform, gate.template.shape) - ctx.stroke() + + ctx.fill() } + + ctx.restore() } diff --git a/src/modules/simulationRenderer/stores/imageStore.ts b/src/modules/simulationRenderer/stores/imageStore.ts new file mode 100644 index 0000000..07c653d --- /dev/null +++ b/src/modules/simulationRenderer/stores/imageStore.ts @@ -0,0 +1,54 @@ +import { imageQuality } from '../constants' +import { SimulationError } from '../../errors/classes/SimulationError' + +/** + * Creates an image from a given url + * + * @param url The url of the image + */ +export const toImage = (url: string) => { + const image = new Image(...imageQuality) + image.src = url + + return image +} + +/** + * Store to be sure no more than 1 image oer url is created + */ +export const ImageStore = { + /** + * Map holding the url - image pairs + */ + memory: new Map(), + + /** + * If the image doesnt exist it'll create it from the url. + * If it does the method does nothing. + * + * @param url the url of the image + */ + set(url: string) { + if (!ImageStore.memory.has(url)) { + ImageStore.memory.set(url, toImage(url)) + } + }, + + /** + * Returns the image object from the given url + * + * + * @throws SimulationError if the image doesnt exist + * + * @param url The url of the image + */ + get(url: string) { + const image = ImageStore.memory.get(url) + + if (!image) { + throw new SimulationError(`Cannot get image ${url}`) + } + + return image + } +} diff --git a/webpack.config.js b/webpack.config.js index 33d3c29..15db204 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -17,6 +17,11 @@ const babelRule = { use: 'babel-loader' } +const fileRule = { + test: /\.(png|svg|jpg|gif)$/, + use: ['file-loader'] +} + const cssAndSass = [ isProduction ? MiniCssExtractPlugin.loader @@ -56,10 +61,19 @@ const baseConfig = { publicPath: '/' }, module: { - rules: [babelRule, sassRule, cssRule] + rules: [babelRule, sassRule, cssRule, fileRule] }, resolve: { - extensions: ['.js', '.ts', '.tsx', '.scss'] + extensions: [ + '.js', + '.ts', + '.tsx', + '.scss', + '.png', + '.svg', + '.jpg', + '.gif' + ] }, plugins: [] }