integrated circuits
This commit is contained in:
parent
e88244bc9d
commit
639f4b5aa0
22
package-lock.json
generated
22
package-lock.json
generated
|
@ -4268,6 +4268,28 @@
|
||||||
"integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
|
"integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
|
||||||
"dev": true
|
"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": {
|
"fill-range": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"dev": "webpack-dev-server --open --mode development",
|
"dev": "webpack-dev-server --open --mode development",
|
||||||
"build": "cross-env NODE_ENV=production webpack",
|
"build": "cross-env NODE_ENV=production webpack",
|
||||||
"deploy": "ts-node deploy",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.5",
|
"@babel/core": "^7.5.5",
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"babel-regenerator-runtime": "^6.5.0",
|
"babel-regenerator-runtime": "^6.5.0",
|
||||||
"css-loader": "^3.0.0",
|
"css-loader": "^3.0.0",
|
||||||
|
"file-loader": "^4.1.0",
|
||||||
"html-webpack-inline-source-plugin": "0.0.10",
|
"html-webpack-inline-source-plugin": "0.0.10",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"mini-css-extract-plugin": "^0.8.0",
|
"mini-css-extract-plugin": "^0.8.0",
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import { SimulationRenderer } from '../../../modules/simulationRenderer/classes/SimulationRenderer'
|
|
||||||
import { Screen } from '../../../modules/core/classes/Screen'
|
import { Screen } from '../../../modules/core/classes/Screen'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A screen instance used for the canvas clearing
|
||||||
|
*/
|
||||||
const screen = new Screen()
|
const screen = new Screen()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the used portion of the canvas
|
||||||
|
*
|
||||||
|
* @param ctx the context to clear
|
||||||
|
*/
|
||||||
export const clearCanvas = (ctx: CanvasRenderingContext2D) => {
|
export const clearCanvas = (ctx: CanvasRenderingContext2D) => {
|
||||||
ctx.clearRect(0, 0, screen.x, screen.y)
|
ctx.clearRect(0, 0, screen.x, screen.y)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { vector2 } from '../../math/types/vector2'
|
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 = (
|
export const drawPolygon = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
points: vector2[],
|
points: vector2[],
|
||||||
|
|
|
@ -1,33 +1,34 @@
|
||||||
import { Transform } from '../../math/classes/Transform'
|
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 { 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 = (
|
export const drawRotatedSquare = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
{ position, scale, rotation }: Transform,
|
transform: Transform,
|
||||||
shape: Shape
|
shape: Shape
|
||||||
) => {
|
) => {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
|
|
||||||
ctx.translate(...position)
|
const relative = useTransform(ctx, transform)
|
||||||
ctx.translate(scale[0] / 2, scale[1] / 2)
|
|
||||||
|
|
||||||
ctx.rotate(rotation)
|
roundRect(
|
||||||
|
ctx,
|
||||||
|
relative.x,
|
||||||
|
relative.y,
|
||||||
|
relative.width,
|
||||||
|
relative.height,
|
||||||
|
shape.radius ? shape.radius : 0
|
||||||
|
)
|
||||||
|
|
||||||
if (shape.rounded) {
|
ctx.fill()
|
||||||
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.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
35
src/common/canvas/helpers/drawRoundedImage.ts
Normal file
35
src/common/canvas/helpers/drawRoundedImage.ts
Normal file
|
@ -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()
|
||||||
|
}
|
|
@ -3,10 +3,10 @@
|
||||||
*/
|
*/
|
||||||
export function roundRect(
|
export function roundRect(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
x: number,
|
x: number = 0,
|
||||||
y: number,
|
y: number = 0,
|
||||||
width: number,
|
width: number = 100,
|
||||||
height: number,
|
height: number = 100,
|
||||||
radius: number = 5
|
radius: number = 5
|
||||||
) {
|
) {
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { Transform } from '../../math/classes/Transform'
|
import { Transform } from '../../math/classes/Transform'
|
||||||
import { multiply } from '../../../modules/vector2/helpers/basic'
|
import { multiply } from '../../../modules/vector2/helpers/basic'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param ctx The context to use
|
||||||
|
* @param transform The transform to move relative to
|
||||||
|
*/
|
||||||
export const useTransform = (
|
export const useTransform = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
{ position, rotation, scale }: Transform
|
{ position, rotation, scale }: Transform
|
||||||
|
|
|
@ -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 {
|
export interface Context {
|
||||||
memory: Record<string, unknown>
|
memory: Record<string, unknown>
|
||||||
get: (index: number) => boolean
|
get: (index: number) => boolean
|
||||||
set: (index: number, state: boolean) => void
|
set: (index: number, state: boolean) => void
|
||||||
color: (color: string) => void
|
color: (color: string) => void
|
||||||
|
enviroment: SimulationEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InitialisationContext {
|
||||||
|
memory: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
40
src/modules/integrated-circuits/helpers/compileIc.ts
Normal file
40
src/modules/integrated-circuits/helpers/compileIc.ts
Normal file
|
@ -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<GateTemplate> = {
|
||||||
|
metadata: {
|
||||||
|
name
|
||||||
|
},
|
||||||
|
tags: ['integrated'],
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: inputCount
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
count: outputCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templateStore.set(name, result)
|
||||||
|
}
|
34
src/modules/integrated-circuits/helpers/simulationIoCount.ts
Normal file
34
src/modules/integrated-circuits/helpers/simulationIoCount.ts
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import { rendererSubject } from '../../core/subjects/rendererSubject'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import { templateStore } from '../../saving/stores/templateStore'
|
import { templateStore } from '../../saving/stores/templateStore'
|
||||||
import { randomItem } from '../../internalisation/helpers/randomItem'
|
import { randomItem } from '../../internalisation/helpers/randomItem'
|
||||||
|
import { completeTemplate } from '../helpers/completeTemplate'
|
||||||
|
import { gateIcons } from '../constants'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subject containing the open state of the modal
|
* Subject containing the open state of the modal
|
||||||
|
@ -43,12 +45,11 @@ const LogicGateModal = () => {
|
||||||
throw new SimulationError(`Renderer not found`)
|
throw new SimulationError(`Renderer not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const template =
|
const template = completeTemplate(templateStore.get(gate) || {})
|
||||||
gate.source === 'base' ? templateStore.get(gate.name) : ''
|
|
||||||
|
|
||||||
if (gate.source === 'base' && !template) {
|
if (!template) {
|
||||||
throw new SimulationError(
|
throw new SimulationError(
|
||||||
`Template ${gate.name} cannot be found`
|
`Template ${gate} cannot be found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,29 +58,28 @@ const LogicGateModal = () => {
|
||||||
key={index}
|
key={index}
|
||||||
className="logic-gate-item"
|
className="logic-gate-item"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
addGate(renderer.simulation, gate.name)
|
addGate(renderer.simulation, gate)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon className="lgi-icon logic-gate-item-type">
|
<Icon className="lgi-icon logic-gate-item-type">
|
||||||
{gate.source === 'base' ? 'sd_storage' : 'memory'}
|
{gateIcons[template.tags[0]]}
|
||||||
</Icon>
|
</Icon>
|
||||||
<Typography className="logic-gate-item-name">
|
<Typography className="logic-gate-item-name">
|
||||||
{gate.name}
|
{gate}
|
||||||
</Typography>
|
</Typography>
|
||||||
{template && template.info && template.info.length && (
|
{template.info.length && (
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="logic-gate-item-info"
|
className="logic-gate-item-info"
|
||||||
href={randomItem(template.info)}
|
href={randomItem(template.info)}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Icon className="lgi-icon">info</Icon>
|
<Icon className="lgi-icon">info</Icon>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{gate.source === 'ic' && (
|
|
||||||
<Icon className="lgi-icon logic-gate-item-delete">
|
|
||||||
delete
|
|
||||||
</Icon>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
7
src/modules/logic-gates/constants.ts
Normal file
7
src/modules/logic-gates/constants.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { GateTag } from '../simulation/types/GateTemplate'
|
||||||
|
|
||||||
|
export const gateIcons: Record<GateTag, string> = {
|
||||||
|
base: 'house',
|
||||||
|
imported: 'share',
|
||||||
|
integrated: 'memory'
|
||||||
|
}
|
9
src/modules/logic-gates/helpers/completeTemplate.ts
Normal file
9
src/modules/logic-gates/helpers/completeTemplate.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import merge from 'deepmerge'
|
||||||
|
import { GateTemplate } from '../../simulation/types/GateTemplate'
|
||||||
|
import { DefaultGateTemplate } from '../../simulation/constants'
|
||||||
|
|
||||||
|
export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
|
||||||
|
return merge(DefaultGateTemplate, template, {
|
||||||
|
arrayMerge: (a: unknown[], b: unknown[]) => b
|
||||||
|
}) as GateTemplate
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,37 +1,14 @@
|
||||||
import { BehaviorSubject } from 'rxjs'
|
import { BehaviorSubject } from 'rxjs'
|
||||||
import { templateStore } from '../../saving/stores/templateStore'
|
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
|
* Subject containing a list with the names of all logic gate templates
|
||||||
*/
|
*/
|
||||||
export const LogicGateList = new BehaviorSubject<LogicGateNameWrapper[]>([])
|
export const LogicGateList = new BehaviorSubject<string[]>([])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to update the list of logic gate templates.
|
* Helper method to update the list of logic gate templates.
|
||||||
*/
|
*/
|
||||||
export const updateLogicGateList = () => {
|
export const updateLogicGateList = () => {
|
||||||
const ics = getAllics().map(
|
LogicGateList.next(templateStore.ls())
|
||||||
(name): LogicGateNameWrapper => ({
|
|
||||||
source: 'ic',
|
|
||||||
name
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const templates = templateStore.ls().map(
|
|
||||||
(name): LogicGateNameWrapper => ({
|
|
||||||
source: 'base',
|
|
||||||
name
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
LogicGateList.next([...ics, ...templates])
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
name: 'and'
|
name: 'and'
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
value: 'green'
|
type: 'image',
|
||||||
|
value: require('../../assets/and_gate')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `context.set(0, context.get(0) && context.get(1))`
|
activation: `context.set(0, context.get(0) && context.get(1))`
|
||||||
|
@ -42,7 +43,8 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
name: 'xor'
|
name: 'xor'
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
value: 'white'
|
type: 'image',
|
||||||
|
value: require('../../assets/xor_gate')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
@ -94,6 +96,9 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
count: 0
|
count: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
integration: {
|
||||||
|
input: true
|
||||||
|
},
|
||||||
info: ['https://en.wikipedia.org/wiki/Push-button']
|
info: ['https://en.wikipedia.org/wiki/Push-button']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -111,6 +116,9 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
context.color(context.get(0) ? 'yellow' : 'white')
|
context.color(context.get(0) ? 'yellow' : 'white')
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
integration: {
|
||||||
|
output: true
|
||||||
|
},
|
||||||
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
||||||
pins: {
|
pins: {
|
||||||
outputs: {
|
outputs: {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
} from '../types/SimulationSave'
|
} from '../types/SimulationSave'
|
||||||
import { Transform } from '../../../common/math/classes/Transform'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
import { Camera } from '../../simulationRenderer/classes/Camera'
|
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 { Wire } from '../../simulation/classes/Wire'
|
||||||
import { templateStore } from '../stores/templateStore'
|
import { templateStore } from '../stores/templateStore'
|
||||||
|
|
||||||
|
@ -26,8 +26,11 @@ export const fromCameraState = (state: CameraState): Camera => {
|
||||||
return camera
|
return camera
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fromSimulationState = (state: SimulationState): Simulation => {
|
export const fromSimulationState = (
|
||||||
const simulation = new Simulation(state.mode, state.name)
|
state: SimulationState,
|
||||||
|
env: SimulationEnv = 'global'
|
||||||
|
): Simulation => {
|
||||||
|
const simulation = new Simulation(state.mode, state.name, env)
|
||||||
|
|
||||||
for (const gateState of state.gates) {
|
for (const gateState of state.gates) {
|
||||||
const gate = new Gate(
|
const gate = new Gate(
|
||||||
|
@ -60,7 +63,7 @@ export const fromSimulationState = (state: SimulationState): Simulation => {
|
||||||
value: endGateNode.data._pins.inputs[wireState.to.index]
|
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)
|
simulation.wires.push(wire)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { saveStore } from '../stores/saveStore'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||||
|
import { compileIc } from '../../integrated-circuits/helpers/compileIc'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the state from a renderer in localStorage,
|
* Saves the state from a renderer in localStorage,
|
||||||
|
@ -25,6 +26,10 @@ export const save = (renderer: SimulationRenderer) => {
|
||||||
|
|
||||||
saveStore.set(current, state)
|
saveStore.set(current, state)
|
||||||
|
|
||||||
|
if (state.simulation.mode === 'ic') {
|
||||||
|
compileIc(state.simulation)
|
||||||
|
}
|
||||||
|
|
||||||
toast(
|
toast(
|
||||||
...createToastArguments(
|
...createToastArguments(
|
||||||
translation.messages.savedSimulation(current),
|
translation.messages.savedSimulation(current),
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { Transform } from '../../../common/math/classes/Transform'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
import { Pin } from './Pin'
|
import { Pin } from './Pin'
|
||||||
import merge from 'deepmerge'
|
|
||||||
import { GateTemplate, PinCount } from '../types/GateTemplate'
|
import { GateTemplate, PinCount } from '../types/GateTemplate'
|
||||||
import { DefaultGateTemplate } from '../constants'
|
|
||||||
import { idStore } from '../stores/idStore'
|
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 { toFunction } from '../../activation/helpers/toFunction'
|
||||||
import { Subscription, combineLatest } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import { throttleTime, debounce, debounceTime } from 'rxjs/operators'
|
|
||||||
import { getGateTimePipes } from '../helpers/getGateTimePipes'
|
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 {
|
export interface GatePins {
|
||||||
inputs: Pin[]
|
inputs: Pin[]
|
||||||
|
@ -47,8 +50,14 @@ export class Gate {
|
||||||
private subscriptions: Subscription[] = []
|
private subscriptions: Subscription[] = []
|
||||||
private memory: Record<string, unknown> = {}
|
private memory: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
// Related to integration
|
||||||
|
private ghostSimulation: Simulation
|
||||||
|
private ghostWires: Wire[] = []
|
||||||
|
private isIntegrated = false
|
||||||
|
public env: SimulationEnv = 'global'
|
||||||
|
|
||||||
public constructor(template: DeepPartial<GateTemplate> = {}, id?: number) {
|
public constructor(template: DeepPartial<GateTemplate> = {}, id?: number) {
|
||||||
this.template = merge(DefaultGateTemplate, template) as GateTemplate
|
this.template = completeTemplate(template)
|
||||||
|
|
||||||
this.transform.scale = this.template.shape.scale
|
this.transform.scale = this.template.shape.scale
|
||||||
|
|
||||||
|
@ -73,6 +82,10 @@ export class Gate {
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (this.template.material.type === 'image') {
|
||||||
|
ImageStore.set(this.template.material.value)
|
||||||
|
}
|
||||||
|
|
||||||
this.id = id !== undefined ? id : idStore.generate()
|
this.id = id !== undefined ? id : idStore.generate()
|
||||||
|
|
||||||
for (const pin of this._pins.inputs) {
|
for (const pin of this._pins.inputs) {
|
||||||
|
@ -84,6 +97,81 @@ export class Gate {
|
||||||
|
|
||||||
this.subscriptions.push(subscription)
|
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() {
|
public onClick() {
|
||||||
|
@ -100,15 +188,22 @@ export class Gate {
|
||||||
for (const subscription of this.subscriptions) {
|
for (const subscription of this.subscriptions) {
|
||||||
subscription.unsubscribe()
|
subscription.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isIntegrated) {
|
||||||
|
this.ghostSimulation.dispose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public update() {
|
public update() {
|
||||||
const context = this.getContext()
|
if (this.template.tags.includes('integrated')) {
|
||||||
|
} else {
|
||||||
|
const context = this.getContext()
|
||||||
|
|
||||||
if (!this.functions.activation)
|
if (!this.functions.activation)
|
||||||
throw new SimulationError('Activation function is missing')
|
throw new SimulationError('Activation function is missing')
|
||||||
|
|
||||||
this.functions.activation(context)
|
this.functions.activation(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContext(): Context {
|
public getContext(): Context {
|
||||||
|
@ -124,14 +219,11 @@ export class Gate {
|
||||||
if (this.template.material.type === 'color') {
|
if (this.template.material.type === 'color') {
|
||||||
this.template.material.value = color
|
this.template.material.value = color
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
enviroment: this.env
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getInputsStates() {
|
|
||||||
return this._pins.inputs.map(pin => pin.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
private wrapPins(pins: Pin[]) {
|
private wrapPins(pins: Pin[]) {
|
||||||
const result: PinWrapper[] = []
|
const result: PinWrapper[] = []
|
||||||
const length = pins.length
|
const length = pins.length
|
||||||
|
|
|
@ -16,8 +16,10 @@ export class Pin {
|
||||||
|
|
||||||
public constructor(public type = 0b01, public gate: Gate) {}
|
public constructor(public type = 0b01, public gate: Gate) {}
|
||||||
|
|
||||||
public addPair(pin: Pin, subscribe = false) {
|
public addPair(pin: Pin, subscribe = false, remember = true) {
|
||||||
this.pairs.add(pin)
|
if (remember) {
|
||||||
|
this.pairs.add(pin)
|
||||||
|
}
|
||||||
|
|
||||||
if (subscribe) {
|
if (subscribe) {
|
||||||
const rawSubscription = pin.state.subscribe(state => {
|
const rawSubscription = pin.state.subscribe(state => {
|
||||||
|
|
|
@ -4,17 +4,25 @@ import { LruCacheNode } from '@eix-js/utils'
|
||||||
import { Wire } from './Wire'
|
import { Wire } from './Wire'
|
||||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The env a simulation can run in
|
||||||
|
*/
|
||||||
|
export type SimulationEnv = 'gate' | 'global'
|
||||||
|
|
||||||
export class Simulation {
|
export class Simulation {
|
||||||
public gates = new GateStorage()
|
public gates = new GateStorage()
|
||||||
public wires: Wire[] = []
|
public wires: Wire[] = []
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public mode: simulationMode = 'project',
|
public mode: simulationMode = 'project',
|
||||||
public name: string
|
public name: string,
|
||||||
|
public env: SimulationEnv = 'global'
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public push(...gates: Gate[]) {
|
public push(...gates: Gate[]) {
|
||||||
for (const gate of gates) {
|
for (const gate of gates) {
|
||||||
|
gate.env = this.env
|
||||||
|
|
||||||
const node = new LruCacheNode<Gate>(gate.id, gate)
|
const node = new LruCacheNode<Gate>(gate.id, gate)
|
||||||
|
|
||||||
this.gates.set(gate.id, node)
|
this.gates.set(gate.id, node)
|
||||||
|
|
|
@ -9,14 +9,20 @@ export class Wire {
|
||||||
public constructor(
|
public constructor(
|
||||||
public start: PinWrapper,
|
public start: PinWrapper,
|
||||||
public end: PinWrapper,
|
public end: PinWrapper,
|
||||||
|
ic: boolean = false,
|
||||||
id?: number
|
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')
|
throw new SimulationError('An input pin can only have 1 pair')
|
||||||
}
|
}
|
||||||
|
|
||||||
end.value.addPair(start.value, true)
|
end.value.addPair(start.value, true, !ic)
|
||||||
start.value.addPair(end.value)
|
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()
|
this.id = id !== undefined ? id : idStore.generate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,9 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
scale: [100, 100]
|
scale: [100, 100]
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: 'context.set(0,true)',
|
activation: '',
|
||||||
onClick: ''
|
onClick: '',
|
||||||
|
initialisation: ''
|
||||||
},
|
},
|
||||||
simulation: {
|
simulation: {
|
||||||
debounce: {
|
debounce: {
|
||||||
|
@ -36,5 +37,11 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
info: []
|
integration: {
|
||||||
|
allowed: true,
|
||||||
|
input: false,
|
||||||
|
output: false
|
||||||
|
},
|
||||||
|
info: [],
|
||||||
|
tags: ['base']
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ export interface PinCount {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Material {
|
export interface Material {
|
||||||
type: 'color'
|
type: 'color' | 'image'
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ export type TimePipe = Enabled<{
|
||||||
time: number
|
time: number
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
export type GateTag = 'base' | 'imported' | 'integrated'
|
||||||
|
|
||||||
export interface GateTemplate {
|
export interface GateTemplate {
|
||||||
material: Material
|
material: Material
|
||||||
shape: Shape
|
shape: Shape
|
||||||
|
@ -39,6 +41,7 @@ export interface GateTemplate {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
code: {
|
code: {
|
||||||
|
initialisation: string
|
||||||
activation: string
|
activation: string
|
||||||
onClick: string
|
onClick: string
|
||||||
}
|
}
|
||||||
|
@ -46,5 +49,11 @@ export interface GateTemplate {
|
||||||
throttle: TimePipe
|
throttle: TimePipe
|
||||||
debounce: TimePipe
|
debounce: TimePipe
|
||||||
}
|
}
|
||||||
|
integration: {
|
||||||
|
allowed: boolean
|
||||||
|
input: boolean
|
||||||
|
output: boolean
|
||||||
|
}
|
||||||
info: string[]
|
info: string[]
|
||||||
|
tags: GateTag[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,17 +115,22 @@ export class SimulationRenderer {
|
||||||
pin.value === this.selectedPins.start.wrapper.value
|
pin.value === this.selectedPins.start.wrapper.value
|
||||||
) {
|
) {
|
||||||
this.selectedPins.start = null
|
this.selectedPins.start = null
|
||||||
|
this.selectedPins.end = null
|
||||||
} else if (
|
} else if (
|
||||||
this.selectedPins.end &&
|
this.selectedPins.end &&
|
||||||
pin.value === this.selectedPins.end.wrapper.value
|
pin.value === this.selectedPins.end.wrapper.value
|
||||||
) {
|
) {
|
||||||
|
this.selectedPins.start = null
|
||||||
this.selectedPins.end = null
|
this.selectedPins.end = null
|
||||||
} else if ((pin.value.type & 0b10) >> 1) {
|
} else if ((pin.value.type & 0b10) >> 1) {
|
||||||
this.selectedPins.start = {
|
this.selectedPins.start = {
|
||||||
wrapper: pin,
|
wrapper: pin,
|
||||||
transform
|
transform
|
||||||
}
|
}
|
||||||
} else if (pin.value.type & 1) {
|
} else if (
|
||||||
|
pin.value.type & 1 &&
|
||||||
|
pin.value.pairs.size === 0
|
||||||
|
) {
|
||||||
this.selectedPins.end = {
|
this.selectedPins.end = {
|
||||||
wrapper: pin,
|
wrapper: pin,
|
||||||
transform
|
transform
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
||||||
|
import { vector2 } from '../../common/math/classes/Transform'
|
||||||
|
|
||||||
export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
||||||
dnd: {
|
dnd: {
|
||||||
|
@ -24,3 +25,5 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
||||||
curvePointOffset: 100
|
curvePointOffset: 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const imageQuality: vector2 = [100, 100]
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { Gate } from '../../simulation/classes/Gate'
|
import { Gate } from '../../simulation/classes/Gate'
|
||||||
import { drawRotatedSquare } from '../../../common/canvas/helpers/drawRotatedSquare'
|
|
||||||
import { renderPins } from './renderPins'
|
import { renderPins } from './renderPins'
|
||||||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
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 = (
|
export const renderGate = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
@ -18,9 +21,33 @@ export const renderGate = (
|
||||||
|
|
||||||
ctx.lineWidth = renderer.options.gates.gateStroke.width
|
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') {
|
if (gate.template.material.type === 'color') {
|
||||||
ctx.fillStyle = gate.template.material.value
|
ctx.fillStyle = gate.template.material.value
|
||||||
drawRotatedSquare(ctx, gate.transform, gate.template.shape)
|
|
||||||
ctx.stroke()
|
ctx.fill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
54
src/modules/simulationRenderer/stores/imageStore.ts
Normal file
54
src/modules/simulationRenderer/stores/imageStore.ts
Normal file
|
@ -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<string, HTMLImageElement>(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,11 @@ const babelRule = {
|
||||||
use: 'babel-loader'
|
use: 'babel-loader'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileRule = {
|
||||||
|
test: /\.(png|svg|jpg|gif)$/,
|
||||||
|
use: ['file-loader']
|
||||||
|
}
|
||||||
|
|
||||||
const cssAndSass = [
|
const cssAndSass = [
|
||||||
isProduction
|
isProduction
|
||||||
? MiniCssExtractPlugin.loader
|
? MiniCssExtractPlugin.loader
|
||||||
|
@ -56,10 +61,19 @@ const baseConfig = {
|
||||||
publicPath: '/'
|
publicPath: '/'
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [babelRule, sassRule, cssRule]
|
rules: [babelRule, sassRule, cssRule, fileRule]
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.ts', '.tsx', '.scss']
|
extensions: [
|
||||||
|
'.js',
|
||||||
|
'.ts',
|
||||||
|
'.tsx',
|
||||||
|
'.scss',
|
||||||
|
'.png',
|
||||||
|
'.svg',
|
||||||
|
'.jpg',
|
||||||
|
'.gif'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
plugins: []
|
plugins: []
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue