Add support for constants
This commit is contained in:
parent
7d9d2a2d78
commit
96e2184a24
6
build.js
6
build.js
|
@ -43,7 +43,11 @@ const ctx = await esbuild.context({
|
|||
})
|
||||
|
||||
if (serve) {
|
||||
const { port, host } = await ctx.serve({ servedir: 'dist' })
|
||||
await ctx.watch()
|
||||
const { port, host } = await ctx.serve({
|
||||
servedir: 'dist',
|
||||
fallback: 'dist/index.html'
|
||||
})
|
||||
console.log(`Serving on ${host}:${port}`)
|
||||
} else {
|
||||
await ctx.rebuild()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "ESBUILD_SERVE=1 node ./build.js",
|
||||
"build": "node ./build.js",
|
||||
"check": "tsc"
|
||||
},
|
||||
|
|
|
@ -33,8 +33,10 @@ async function main() {
|
|||
}
|
||||
}
|
||||
|
||||
new EventSource('/esbuild').addEventListener('change', () => location.reload())
|
||||
|
||||
// Call entry
|
||||
main().catch(error => {
|
||||
main().catch((error) => {
|
||||
// if the error handling error has an error, log that error
|
||||
console.error('Error loading app', error)
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Simulation, SimulationEnv } from '../../simulation/classes/Simulation'
|
||||
import { SimulationEnv } from '../../simulation/classes/Simulation'
|
||||
import { PinState } from '../../simulation/classes/Pin'
|
||||
|
||||
export interface Context {
|
||||
|
@ -6,8 +6,12 @@ export interface Context {
|
|||
setProperty: (name: string, value: unknown) => void
|
||||
get: (index: number) => PinState
|
||||
set: (index: number, state: PinState) => void
|
||||
getOutput: (index: number) => PinState
|
||||
getBinary: (index: number) => number
|
||||
setBinary: (index: number, value: number, bits: number) => void
|
||||
printBinary: (value: number, bits?: number) => string
|
||||
printHex: (value: number, length?: number) => string
|
||||
setBinary: (index: number, value: number, bits?: number) => void
|
||||
getOutputBinary: (index: number) => number
|
||||
invertBinary: (value: number) => number
|
||||
color: (color: string) => void
|
||||
innerText: (value: string) => void
|
||||
|
|
|
@ -46,6 +46,10 @@ div .gate-prop-container {
|
|||
&.visible {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
&>* {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
div .gate-prop-group-container {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import './GateProperties.scss'
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import { ChangeEvent } from 'react'
|
||||
import { getRendererSafely } from '../helpers/getRendererSafely'
|
||||
import { Property, RawProp, isGroup } from '../../simulation/types/GateTemplate'
|
||||
import { useObservable } from 'rxjs-hooks'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import CheckBox from '@material-ui/core/Checkbox'
|
||||
import { open, id } from '../subjects/LogicGatePropsSubjects'
|
||||
|
@ -70,15 +69,13 @@ const GateRawProperty = ({
|
|||
// rerender when the external checkbox changes
|
||||
const external = useObservable(
|
||||
() =>
|
||||
gate.props.external.pipe(
|
||||
map((value) => value && name !== 'external')
|
||||
),
|
||||
gate.props.external.pipe(map((value) => value && name !== 'external')),
|
||||
false
|
||||
)
|
||||
|
||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}${
|
||||
external && name !== 'label' ? ' (default value)' : ''
|
||||
}:`
|
||||
}${raw.description == undefined ? '' : ` ${raw.description}`}:`
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
|
@ -112,11 +109,7 @@ const GateRawProperty = ({
|
|||
return emptyInput
|
||||
}
|
||||
|
||||
if (
|
||||
raw.type === 'number' ||
|
||||
raw.type === 'text' ||
|
||||
raw.type === 'string'
|
||||
) {
|
||||
if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
|
||||
return (
|
||||
<TextField
|
||||
onChange={handleChange}
|
||||
|
@ -124,7 +117,6 @@ const GateRawProperty = ({
|
|||
value={outputSnapshot}
|
||||
type={raw.type}
|
||||
multiline={raw.type === 'string'}
|
||||
rowsMax={7}
|
||||
/>
|
||||
)
|
||||
} else if (raw.type === 'boolean') {
|
||||
|
@ -145,9 +137,7 @@ const GateRawProperty = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`gate-prop-container ${
|
||||
input !== emptyInput ? 'visible' : ''
|
||||
}`}
|
||||
className={`gate-prop-container ${input !== emptyInput ? 'visible' : ''}`}
|
||||
>
|
||||
{input}
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@ import comparatorTemplate from './templates/comparator'
|
|||
import bitMergerTemplate from './templates/bitMerger'
|
||||
import bitSplitterTemplate from './templates/bitSplitter'
|
||||
import incrementorTemplate from './templates/incrementor'
|
||||
import constantTemplate from './templates/constant'
|
||||
|
||||
export const defaultSimulationName = 'default'
|
||||
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||
|
@ -42,7 +43,8 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
|||
comparatorTemplate,
|
||||
bitMergerTemplate,
|
||||
bitSplitterTemplate,
|
||||
incrementorTemplate
|
||||
incrementorTemplate,
|
||||
constantTemplate
|
||||
]
|
||||
|
||||
export const baseSave: RendererState = {
|
||||
|
|
60
src/modules/saving/templates/constant.ts
Normal file
60
src/modules/saving/templates/constant.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { PartialTemplate } from '../types/PartialTemplate'
|
||||
import { categories } from '../data/categories'
|
||||
|
||||
/**
|
||||
* The template of the button gate
|
||||
*/
|
||||
const constTemplate: PartialTemplate = {
|
||||
metadata: {
|
||||
name: 'constant'
|
||||
},
|
||||
material: {
|
||||
fill: '#673AB7',
|
||||
stroke: {
|
||||
normal: '#EDC6FB'
|
||||
}
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
const state = context.getProperty('value')
|
||||
const bits = context.getProperty('output bits')
|
||||
const length = state.toString(2).length
|
||||
const text = length > 10
|
||||
? "0x" + context.printHex(state, Math.ceil(length/4))
|
||||
: context.printBinary(state, length)
|
||||
|
||||
context.setBinary(0, state, bits === 0 ? length : bits)
|
||||
context.innerText(text)
|
||||
`
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
integration: {
|
||||
input: true
|
||||
},
|
||||
info: [],
|
||||
properties: {
|
||||
enabled: true,
|
||||
data: [
|
||||
{
|
||||
base: 0,
|
||||
name: 'output bits',
|
||||
description: '(0 for auto)',
|
||||
type: 'number',
|
||||
needsUpdate: true
|
||||
},
|
||||
{
|
||||
base: 0,
|
||||
name: 'value',
|
||||
type: 'number',
|
||||
needsUpdate: true
|
||||
}
|
||||
]
|
||||
},
|
||||
category: categories.io
|
||||
}
|
||||
|
||||
export default constTemplate
|
|
@ -9,12 +9,7 @@ import {
|
|||
import { idStore } from '../stores/idStore'
|
||||
import { Context, InitialisationContext } from '../../activation/types/Context'
|
||||
import { toFunction } from '../../activation/helpers/toFunction'
|
||||
import {
|
||||
Subscription,
|
||||
BehaviorSubject,
|
||||
asapScheduler,
|
||||
animationFrameScheduler
|
||||
} from 'rxjs'
|
||||
import { Subscription, BehaviorSubject } from 'rxjs'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { getGateTimePipes } from '../helpers/getGateTimePipes'
|
||||
import { ImageStore } from '../../simulationRenderer/stores/imageStore'
|
||||
|
@ -25,7 +20,6 @@ import { saveStore } from '../../saving/stores/saveStore'
|
|||
import { Wire } from './Wire'
|
||||
import { cleanSimulation } from '../../simulation-actions/helpers/clean'
|
||||
import { ExecutionQueue } from '../../activation/classes/ExecutionQueue'
|
||||
import { tap, observeOn } from 'rxjs/operators'
|
||||
import { PropsSave } from '../../saving/types/SimulationSave'
|
||||
import { ValueOf } from '../../../common/lang/record/types/ValueOf'
|
||||
|
||||
|
@ -139,7 +133,7 @@ export class Gate {
|
|||
* Holds all the gate-related text
|
||||
*/
|
||||
public text = {
|
||||
inner: new BehaviorSubject('text goes here')
|
||||
inner: new BehaviorSubject<string | null>(null)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,21 +164,10 @@ export class Gate {
|
|||
'context'
|
||||
)
|
||||
|
||||
this.functions.onClick = toFunction(
|
||||
this.template.code.onClick,
|
||||
'context'
|
||||
)
|
||||
this.functions.onClick = toFunction(this.template.code.onClick, 'context')
|
||||
|
||||
this._pins.inputs = Gate.generatePins(
|
||||
this.template.pins.inputs,
|
||||
1,
|
||||
this
|
||||
)
|
||||
this._pins.outputs = Gate.generatePins(
|
||||
this.template.pins.outputs,
|
||||
2,
|
||||
this
|
||||
)
|
||||
this._pins.inputs = Gate.generatePins(this.template.pins.inputs, 1, this)
|
||||
this._pins.outputs = Gate.generatePins(this.template.pins.outputs, 2, this)
|
||||
|
||||
if (this.template.material.type === 'image') {
|
||||
ImageStore.set(this.template.material.fill)
|
||||
|
@ -198,7 +181,7 @@ export class Gate {
|
|||
const subscription = pin.state.pipe(...pipes).subscribe(() => {
|
||||
if (this.template.code.async) {
|
||||
this.executionQueue.push(async () => {
|
||||
return await this.update()
|
||||
return this.update()
|
||||
})
|
||||
} else {
|
||||
this.update()
|
||||
|
@ -305,7 +288,7 @@ export class Gate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Assign the props passed to the gate and mere them with the base ones
|
||||
* Assign the props passed to the gate and merge them with the base ones
|
||||
*/
|
||||
private assignProps(
|
||||
source: PropsSave,
|
||||
|
@ -313,14 +296,15 @@ export class Gate {
|
|||
target: GateProps = this.props,
|
||||
path: string[] = []
|
||||
) {
|
||||
let shouldUpdate = false
|
||||
// We don't want to update until every prop has been created
|
||||
let lockUpdates = true
|
||||
|
||||
if (this.template.properties.enabled) {
|
||||
for (const prop of props) {
|
||||
if (isGroup(prop)) {
|
||||
const { groupName } = prop
|
||||
target[groupName] = {} as GateProps
|
||||
const needsUpdate = this.assignProps(
|
||||
this.assignProps(
|
||||
typeof source[groupName] === 'object'
|
||||
? (source[groupName] as PropsSave)
|
||||
: {},
|
||||
|
@ -329,10 +313,6 @@ export class Gate {
|
|||
[...path, groupName]
|
||||
)
|
||||
|
||||
if (needsUpdate) {
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -346,7 +326,7 @@ export class Gate {
|
|||
|
||||
this.subscriptions.push(
|
||||
subject.subscribe((value) => {
|
||||
if (needsUpdate && path.length === 0) {
|
||||
if (!lockUpdates && needsUpdate && path.length === 0) {
|
||||
return this.update()
|
||||
}
|
||||
|
||||
|
@ -357,14 +337,11 @@ export class Gate {
|
|||
this.updateNestedProp([...path, name], value)
|
||||
})
|
||||
)
|
||||
|
||||
if (needsUpdate) {
|
||||
shouldUpdate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shouldUpdate
|
||||
lockUpdates = false
|
||||
this.update()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -446,7 +423,8 @@ export class Gate {
|
|||
|
||||
const toLength = (
|
||||
original: string | number,
|
||||
length: number = maxLength
|
||||
length: number = maxLength,
|
||||
paddingChar = '0'
|
||||
) => {
|
||||
const value = original.toString(2)
|
||||
|
||||
|
@ -455,31 +433,37 @@ export class Gate {
|
|||
} else if (value.length > length) {
|
||||
const difference = value.length - length
|
||||
|
||||
return value.substr(difference)
|
||||
return value.slice(difference)
|
||||
} else {
|
||||
return `${'0'.repeat(length - value.length)}${value}`
|
||||
return `${paddingChar.repeat(length - value.length)}${value}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
printBinary: (value: number, bits: number = maxLength) =>
|
||||
toLength(value.toString(2), bits),
|
||||
printHex: (value: number, bits: number = maxLength) =>
|
||||
toLength(value.toString(16), bits),
|
||||
get: (index: number) => {
|
||||
return this._pins.inputs[index].state.value
|
||||
},
|
||||
set: (index: number, state) => {
|
||||
return this._pins.outputs[index].state.next(state)
|
||||
},
|
||||
getOutput: (index: number) => {
|
||||
return this._pins.outputs[index].state.value
|
||||
},
|
||||
getBinary: (index: number) => {
|
||||
return parseInt(this._pins.inputs[index].state.value, 2)
|
||||
},
|
||||
setBinary: (
|
||||
index: number,
|
||||
value: number,
|
||||
bits: number = maxLength
|
||||
) => {
|
||||
setBinary: (index: number, value: number, bits: number = maxLength) => {
|
||||
return this._pins.outputs[index].state.next(
|
||||
toLength(value.toString(2), bits)
|
||||
)
|
||||
},
|
||||
getOutputBinary: (index: number) => {
|
||||
return parseInt(this._pins.outputs[index].state.value, 2)
|
||||
},
|
||||
invertBinary: (value: number) => {
|
||||
return value ^ ((1 << maxLength) - 1)
|
||||
},
|
||||
|
@ -489,7 +473,16 @@ export class Gate {
|
|||
}
|
||||
},
|
||||
getProperty: (name: string) => {
|
||||
if (this.props[name] === undefined) {
|
||||
throw new Error(
|
||||
[
|
||||
`Cannot find property ${name} on gate ${this.template.metadata.name}.`,
|
||||
`Current values: ${Object.keys(this.props)}`
|
||||
].join('\n')
|
||||
)
|
||||
} else {
|
||||
return this.props[name].value
|
||||
}
|
||||
},
|
||||
setProperty: (name: string, value: string | number | boolean) => {
|
||||
const subject = this.props[name]
|
||||
|
@ -551,6 +544,6 @@ export class Gate {
|
|||
private static generatePins(options: PinCount, type: number, gate: Gate) {
|
||||
return [...Array(options.count)]
|
||||
.fill(true)
|
||||
.map((v, index) => new Pin(type, gate))
|
||||
.map((_v, _index) => new Pin(type, gate))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export type RawProp<
|
|||
type: 'number' | 'string' | 'text' | 'boolean'
|
||||
base: T
|
||||
name: string
|
||||
description?: string
|
||||
needsUpdate?: boolean
|
||||
}
|
||||
export type Property<
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const textSettings = {
|
||||
font: '30px Roboto',
|
||||
font: '30px monospace',
|
||||
offset: 35,
|
||||
fill: `rgba(256,256,256,0.75)`
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const pinFill = (renderer: SimulationRenderer, pin: Pin) => {
|
|||
.map((key) =>
|
||||
chunked
|
||||
.flat()
|
||||
.filter((v, index) => index % 3 === key)
|
||||
.filter((_v, index) => index % 3 === key)
|
||||
.reduce((acc, curr) => acc + curr, 0)
|
||||
)
|
||||
.map((value) => Math.floor(value / digits.length))
|
||||
|
|
|
@ -61,6 +61,7 @@ export const renderGate = (
|
|||
ctx.stroke()
|
||||
|
||||
if (gate.template.tags.includes('integrated')) {
|
||||
ctx.textBaseline = 'top'
|
||||
ctx.fillStyle = textSettings.fill
|
||||
ctx.fillText(
|
||||
gate.template.metadata.name,
|
||||
|
@ -69,5 +70,25 @@ export const renderGate = (
|
|||
)
|
||||
}
|
||||
|
||||
const text = gate.text.inner.value
|
||||
if (text !== null) {
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.fillStyle = textSettings.fill
|
||||
|
||||
let size = 30
|
||||
if (text.length >= 8) {
|
||||
size = 15
|
||||
} else if (text.length >= 6) {
|
||||
size = 20
|
||||
}
|
||||
|
||||
ctx.font = `${size}px monospace`
|
||||
ctx.fillText(
|
||||
text,
|
||||
relativeTransform.center[0],
|
||||
relativeTransform.center[1] + 2
|
||||
)
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue