Add support for constants

This commit is contained in:
prescientmoon 2024-11-27 10:41:36 +01:00
parent 7d9d2a2d78
commit 96e2184a24
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
19 changed files with 1086 additions and 1004 deletions

View file

@ -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()

View file

@ -3,6 +3,7 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "ESBUILD_SERVE=1 node ./build.js",
"build": "node ./build.js",
"check": "tsc"
},

View file

@ -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)
})

View file

@ -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

View file

@ -46,6 +46,10 @@ div .gate-prop-container {
&.visible {
margin: 1rem;
}
&>* {
width: 100%;
}
}
div .gate-prop-group-container {

View file

@ -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>

View file

@ -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 = {

View 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

View file

@ -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))
}
}

View file

@ -21,6 +21,7 @@ export type RawProp<
type: 'number' | 'string' | 'text' | 'boolean'
base: T
name: string
description?: string
needsUpdate?: boolean
}
export type Property<

View file

@ -1,5 +1,5 @@
export const textSettings = {
font: '30px Roboto',
font: '30px monospace',
offset: 35,
fill: `rgba(256,256,256,0.75)`
}

View file

@ -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))

View file

@ -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()
}