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==",
|
||||
"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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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[],
|
||||
|
|
|
@ -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)
|
||||
|
||||
if (shape.rounded) {
|
||||
roundRect(
|
||||
ctx,
|
||||
-scale[0] / 2,
|
||||
-scale[1] / 2,
|
||||
scale[0],
|
||||
scale[1],
|
||||
shape.radius
|
||||
relative.x,
|
||||
relative.y,
|
||||
relative.width,
|
||||
relative.height,
|
||||
shape.radius ? shape.radius : 0
|
||||
)
|
||||
|
||||
ctx.fill()
|
||||
} else {
|
||||
ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale)
|
||||
}
|
||||
|
||||
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(
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<string, unknown>
|
||||
get: (index: number) => boolean
|
||||
set: (index: number, state: boolean) => 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 { 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)
|
||||
}}
|
||||
>
|
||||
<Icon className="lgi-icon logic-gate-item-type">
|
||||
{gate.source === 'base' ? 'sd_storage' : 'memory'}
|
||||
{gateIcons[template.tags[0]]}
|
||||
</Icon>
|
||||
<Typography className="logic-gate-item-name">
|
||||
{gate.name}
|
||||
{gate}
|
||||
</Typography>
|
||||
{template && template.info && template.info.length && (
|
||||
{template.info.length && (
|
||||
<a
|
||||
target="_blank"
|
||||
className="logic-gate-item-info"
|
||||
href={randomItem(template.info)}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<Icon className="lgi-icon">info</Icon>
|
||||
</a>
|
||||
)}
|
||||
{gate.source === 'ic' && (
|
||||
<Icon className="lgi-icon logic-gate-item-delete">
|
||||
delete
|
||||
</Icon>
|
||||
)}
|
||||
</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 { 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<LogicGateNameWrapper[]>([])
|
||||
export const LogicGateList = new BehaviorSubject<string[]>([])
|
||||
|
||||
/**
|
||||
* 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())
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
|||
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<GateTemplate>[] = [
|
|||
name: 'xor'
|
||||
},
|
||||
material: {
|
||||
value: 'white'
|
||||
type: 'image',
|
||||
value: require('../../assets/xor_gate')
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
|
@ -94,6 +96,9 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
|||
count: 0
|
||||
}
|
||||
},
|
||||
integration: {
|
||||
input: true
|
||||
},
|
||||
info: ['https://en.wikipedia.org/wiki/Push-button']
|
||||
},
|
||||
{
|
||||
|
@ -111,6 +116,9 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
|||
context.color(context.get(0) ? 'yellow' : 'white')
|
||||
`
|
||||
},
|
||||
integration: {
|
||||
output: true
|
||||
},
|
||||
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
||||
pins: {
|
||||
outputs: {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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<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) {
|
||||
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,9 +188,15 @@ export class Gate {
|
|||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
|
||||
if (this.isIntegrated) {
|
||||
this.ghostSimulation.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public update() {
|
||||
if (this.template.tags.includes('integrated')) {
|
||||
} else {
|
||||
const context = this.getContext()
|
||||
|
||||
if (!this.functions.activation)
|
||||
|
@ -110,6 +204,7 @@ export class Gate {
|
|||
|
||||
this.functions.activation(context)
|
||||
}
|
||||
}
|
||||
|
||||
public getContext(): Context {
|
||||
return {
|
||||
|
@ -124,13 +219,10 @@ 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[] = []
|
||||
|
|
|
@ -16,8 +16,10 @@ export class Pin {
|
|||
|
||||
public constructor(public type = 0b01, public gate: Gate) {}
|
||||
|
||||
public addPair(pin: Pin, subscribe = false) {
|
||||
public addPair(pin: Pin, subscribe = false, remember = true) {
|
||||
if (remember) {
|
||||
this.pairs.add(pin)
|
||||
}
|
||||
|
||||
if (subscribe) {
|
||||
const rawSubscription = pin.state.subscribe(state => {
|
||||
|
|
|
@ -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>(gate.id, gate)
|
||||
|
||||
this.gates.set(gate.id, node)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
info: string[]
|
||||
integration: {
|
||||
allowed: boolean
|
||||
input: boolean
|
||||
output: boolean
|
||||
}
|
||||
info: string[]
|
||||
tags: GateTag[]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
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'
|
||||
}
|
||||
|
||||
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: []
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue