From 96e2184a2431102636928d6f1455b06cb57f6147 Mon Sep 17 00:00:00 2001
From: prescientmoon <git@moonythm.dev>
Date: Wed, 27 Nov 2024 10:41:36 +0100
Subject: [PATCH] Add support for constants

---
 build.js                                      |   6 +-
 package.json                                  |   1 +
 src/index.ts                                  |  52 +-
 src/modules/activation/types/Context.ts       |  38 +-
 .../components/GateProperties.scss            |  58 +-
 .../components/GatePropertiesModal.tsx        | 292 +++---
 .../logic-gates/helpers/completeTemplate.ts   |   6 +-
 src/modules/saving/constants.ts               |  66 +-
 src/modules/saving/data/categories.ts         |  14 +-
 .../saving/helpers/initBaseTemplates.ts       |  16 +-
 src/modules/saving/templates/comment.ts       |  72 +-
 src/modules/saving/templates/constant.ts      |  60 ++
 src/modules/saving/templates/light.ts         |  54 +-
 src/modules/simulation/classes/Gate.ts        | 957 +++++++++---------
 src/modules/simulation/constants.ts           | 134 +--
 src/modules/simulation/types/GateTemplate.ts  | 129 +--
 .../simulationRenderer/data/textSettings.ts   |   6 +-
 .../simulationRenderer/helpers/pinFill.ts     |   2 +-
 .../simulationRenderer/helpers/renderGate.ts  | 127 ++-
 19 files changed, 1086 insertions(+), 1004 deletions(-)
 create mode 100644 src/modules/saving/templates/constant.ts

diff --git a/build.js b/build.js
index 99c7b2a..5809460 100644
--- a/build.js
+++ b/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()
diff --git a/package.json b/package.json
index 8479a85..ba2264d 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
     "version": "1.0.0",
     "type": "module",
     "scripts": {
+        "dev": "ESBUILD_SERVE=1 node ./build.js",
         "build": "node ./build.js",
         "check": "tsc"
     },
diff --git a/src/index.ts b/src/index.ts
index e46a089..df2c8fd 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,37 +4,39 @@ import { Splash } from './modules/splash/classes/Splash'
  * The function wich is run when the app is loaded
  */
 async function main() {
-    // Create splash screen variable
-    let splash: Splash | undefined = undefined
+  // Create splash screen variable
+  let splash: Splash | undefined = undefined
 
-    try {
-        // instantiate splash screen
-        splash = new Splash()
-    } catch {}
+  try {
+    // instantiate splash screen
+    splash = new Splash()
+  } catch {}
 
-    try {
-        // import main app
-        const app = await import('./main')
+  try {
+    // import main app
+    const app = await import('./main')
 
-        // wait for app to start
-        await app.start()
-    } catch (error) {
-        // show the error to the client
-        if (splash) splash.setError(error)
+    // wait for app to start
+    await app.start()
+  } catch (error) {
+    // show the error to the client
+    if (splash) splash.setError(error)
 
-        // log the error to the console
-        console.error(error.stack || error)
-        return
-    }
+    // log the error to the console
+    console.error(error.stack || error)
+    return
+  }
 
-    // hide splash screen if it exists
-    if (splash) {
-        splash.fade()
-    }
+  // hide splash screen if it exists
+  if (splash) {
+    splash.fade()
+  }
 }
 
+new EventSource('/esbuild').addEventListener('change', () => location.reload())
+
 // Call entry
-main().catch(error => {
-    // if the error handling error has an error, log that error
-    console.error('Error loading app', error)
+main().catch((error) => {
+  // if the error handling error has an error, log that error
+  console.error('Error loading app', error)
 })
diff --git a/src/modules/activation/types/Context.ts b/src/modules/activation/types/Context.ts
index 11df2c8..da6dcb1 100644
--- a/src/modules/activation/types/Context.ts
+++ b/src/modules/activation/types/Context.ts
@@ -1,24 +1,28 @@
-import { Simulation, SimulationEnv } from '../../simulation/classes/Simulation'
+import { SimulationEnv } from '../../simulation/classes/Simulation'
 import { PinState } from '../../simulation/classes/Pin'
 
 export interface Context {
-    getProperty: (name: string) => unknown
-    setProperty: (name: string, value: unknown) => void
-    get: (index: number) => PinState
-    set: (index: number, state: PinState) => void
-    getBinary: (index: number) => number
-    setBinary: (index: number, value: number, bits: number) => void
-    invertBinary: (value: number) => number
-    color: (color: string) => void
-    innerText: (value: string) => void
-    update: () => void
-    toLength: (value: number | PinState, length: number) => string
-    maxLength: number
-    enviroment: SimulationEnv
-    colors: Record<string, string>
-    memory: Record<string, unknown>
+  getProperty: (name: string) => unknown
+  setProperty: (name: string, value: unknown) => void
+  get: (index: number) => PinState
+  set: (index: number, state: PinState) => void
+  getOutput: (index: number) => PinState
+  getBinary: (index: number) => number
+  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
+  update: () => void
+  toLength: (value: number | PinState, length: number) => string
+  maxLength: number
+  enviroment: SimulationEnv
+  colors: Record<string, string>
+  memory: Record<string, unknown>
 }
 
 export interface InitialisationContext {
-    memory: Record<string, unknown>
+  memory: Record<string, unknown>
 }
diff --git a/src/modules/logic-gates/components/GateProperties.scss b/src/modules/logic-gates/components/GateProperties.scss
index 22cb619..2d4386e 100644
--- a/src/modules/logic-gates/components/GateProperties.scss
+++ b/src/modules/logic-gates/components/GateProperties.scss
@@ -7,60 +7,64 @@
 $gate-props-margin: 1rem;
 
 #gate-properties-modal {
-    @include modal-container();
+  @include modal-container();
 
-    justify-content: center;
+  justify-content: center;
 }
 
 .visible#gate-properties-modal {
-    @include visible();
+  @include visible();
 }
 
 div #gate-properties-container {
-    display: flex;
-    flex-direction: column;
+  display: flex;
+  flex-direction: column;
 
-    background-color: $grey;
-    border-radius: 1em;
+  background-color: $grey;
+  border-radius: 1em;
 
-    padding: $gate-props-margin * 4;
-    box-sizing: border-box;
+  padding: $gate-props-margin * 4;
+  box-sizing: border-box;
 
-    max-height: 80vh;
-    overflow: auto;
+  max-height: 80vh;
+  overflow: auto;
 }
 
 div #gate-props-title {
-    color: white;
-    font-size: 3em;
+  color: white;
+  font-size: 3em;
 
-    margin-bottom: 2 * $gate-props-margin;
+  margin-bottom: 2 * $gate-props-margin;
 }
 
 div .gate-prop-container {
-    @include flex();
+  @include flex();
 
-    flex-direction: row;
-    justify-content: start;
+  flex-direction: row;
+  justify-content: start;
 
-    &.visible {
-        margin: 1rem;
-    }
+  &.visible {
+    margin: 1rem;
+  }
+
+  &>* {
+    width: 100%;
+  }
 }
 
 div .gate-prop-group-container {
-    @include flex;
+  @include flex;
 
-    margin-left: 1rem;
+  margin-left: 1rem;
 }
 
 div #save-props {
-    width: 50%;
-    margin: $gate-props-margin * 2;
-    margin-bottom: 0;
+  width: 50%;
+  margin: $gate-props-margin * 2;
+  margin-bottom: 0;
 }
 
 .checkbox-label {
-    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
-        Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
+    Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
 }
diff --git a/src/modules/logic-gates/components/GatePropertiesModal.tsx b/src/modules/logic-gates/components/GatePropertiesModal.tsx
index 8e9991e..c758be0 100644
--- a/src/modules/logic-gates/components/GatePropertiesModal.tsx
+++ b/src/modules/logic-gates/components/GatePropertiesModal.tsx
@@ -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'
@@ -16,41 +15,41 @@ import Typography from '@material-ui/core/Typography'
 import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'
 import Icon from '@material-ui/core/Icon'
 interface GatePropertyProps<T extends Property = Property> {
-    raw: T
-    gate: Gate
-    props: GateProps
+  raw: T
+  gate: Gate
+  props: GateProps
 }
 
 const emptyInput = <></>
 
 const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
-    if (isGroup(raw)) {
-        return (
-            <ExpansionPanel>
-                <ExpansionPanelSummary
-                    expandIcon={<Icon> expand_more</Icon>}
-                    aria-controls={raw.groupName}
-                    id={raw.groupName}
-                >
-                    <Typography>{raw.groupName}</Typography>
-                </ExpansionPanelSummary>
-                <ExpansionPanelDetails>
-                    <div className="gate-prop-group-container">
-                        {raw.props.map((propTemplate, index) => (
-                            <GateProperty
-                                key={index}
-                                raw={propTemplate}
-                                gate={gate}
-                                props={props[raw.groupName] as GateProps}
-                            />
-                        ))}
-                    </div>
-                </ExpansionPanelDetails>
-            </ExpansionPanel>
-        )
-    }
+  if (isGroup(raw)) {
+    return (
+      <ExpansionPanel>
+        <ExpansionPanelSummary
+          expandIcon={<Icon> expand_more</Icon>}
+          aria-controls={raw.groupName}
+          id={raw.groupName}
+        >
+          <Typography>{raw.groupName}</Typography>
+        </ExpansionPanelSummary>
+        <ExpansionPanelDetails>
+          <div className="gate-prop-group-container">
+            {raw.props.map((propTemplate, index) => (
+              <GateProperty
+                key={index}
+                raw={propTemplate}
+                gate={gate}
+                props={props[raw.groupName] as GateProps}
+              />
+            ))}
+          </div>
+        </ExpansionPanelDetails>
+      </ExpansionPanel>
+    )
+  }
 
-    return <GateRawProperty raw={raw} gate={gate} props={props} />
+  return <GateRawProperty raw={raw} gate={gate} props={props} />
 }
 
 /**
@@ -59,99 +58,90 @@ const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
  * @param param0 The props passed to the component
  */
 const GateRawProperty = ({
-    props,
-    raw,
-    gate
+  props,
+  raw,
+  gate
 }: GatePropertyProps & { raw: RawProp }) => {
-    const { name } = raw
-    const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
-    const outputSnapshot = useObservable(() => prop, '')
+  const { name } = raw
+  const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
+  const outputSnapshot = useObservable(() => prop, '')
 
-    // rerender when the external checkbox changes
-    const external = useObservable(
-        () =>
-            gate.props.external.pipe(
-                map((value) => value && name !== 'external')
-            ),
-        false
-    )
+  // rerender when the external checkbox changes
+  const external = useObservable(
+    () =>
+      gate.props.external.pipe(map((value) => value && name !== 'external')),
+    false
+  )
 
-    const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${
-        external && name !== 'label' ? '(default value)' : ''
-    }:`
+  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
-        let value: boolean | string | number = target.value
+  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
+    const target = e.target as HTMLInputElement
+    let value: boolean | string | number = target.value
 
-        if (raw.type === 'boolean') {
-            value = target.checked
-        } else if (raw.type === 'number') {
-            value = Number(target.value)
-        }
-
-        if (raw.type !== 'boolean') {
-            prop.next(value)
-        }
+    if (raw.type === 'boolean') {
+      value = target.checked
+    } else if (raw.type === 'number') {
+      value = Number(target.value)
     }
 
-    let input = (() => {
-        const root = gate.props[name] === prop
-        const renderer = getRendererSafely()
-        const displayExternal = () =>
-            renderer.simulation.mode === 'ic' &&
-            root &&
-            !gate.template.properties.data.some(
-                (prop) => (prop as RawProp).needsUpdate
-            )
+    if (raw.type !== 'boolean') {
+      prop.next(value)
+    }
+  }
 
-        if (
-            (raw.name === 'external' && !displayExternal()) ||
-            (raw.name === 'label' && (!external || !root))
-        ) {
-            return emptyInput
-        }
+  let input = (() => {
+    const root = gate.props[name] === prop
+    const renderer = getRendererSafely()
+    const displayExternal = () =>
+      renderer.simulation.mode === 'ic' &&
+      root &&
+      !gate.template.properties.data.some(
+        (prop) => (prop as RawProp).needsUpdate
+      )
 
-        if (
-            raw.type === 'number' ||
-            raw.type === 'text' ||
-            raw.type === 'string'
-        ) {
-            return (
-                <TextField
-                    onChange={handleChange}
-                    label={displayableName}
-                    value={outputSnapshot}
-                    type={raw.type}
-                    multiline={raw.type === 'string'}
-                    rowsMax={7}
-                />
-            )
-        } else if (raw.type === 'boolean') {
-            return (
-                <>
-                    <span className="checkbox-label">{displayableName}</span>
-                    <CheckBox
-                        onClick={() => {
-                            prop.next(!outputSnapshot)
-                        }}
-                        onChange={handleChange}
-                        checked={!!outputSnapshot}
-                    />{' '}
-                </>
-            )
-        }
-    })()
+    if (
+      (raw.name === 'external' && !displayExternal()) ||
+      (raw.name === 'label' && (!external || !root))
+    ) {
+      return emptyInput
+    }
 
-    return (
-        <div
-            className={`gate-prop-container ${
-                input !== emptyInput ? 'visible' : ''
-            }`}
-        >
-            {input}
-        </div>
-    )
+    if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
+      return (
+        <TextField
+          onChange={handleChange}
+          label={displayableName}
+          value={outputSnapshot}
+          type={raw.type}
+          multiline={raw.type === 'string'}
+        />
+      )
+    } else if (raw.type === 'boolean') {
+      return (
+        <>
+          <span className="checkbox-label">{displayableName}</span>
+          <CheckBox
+            onClick={() => {
+              prop.next(!outputSnapshot)
+            }}
+            onChange={handleChange}
+            checked={!!outputSnapshot}
+          />{' '}
+        </>
+      )
+    }
+  })()
+
+  return (
+    <div
+      className={`gate-prop-container ${input !== emptyInput ? 'visible' : ''}`}
+    >
+      {input}
+    </div>
+  )
 }
 
 /**
@@ -160,47 +150,47 @@ const GateRawProperty = ({
  * @param props The react props of the component
  */
 const GateProperties = () => {
-    const openSnapshot = useObservable(() => open, false)
-    const renderer = getRendererSafely()
+  const openSnapshot = useObservable(() => open, false)
+  const renderer = getRendererSafely()
 
-    const node = renderer.simulation.gates.get(id.value)
+  const node = renderer.simulation.gates.get(id.value)
 
-    if (!(node && node.data && node.data.template.properties.enabled)) {
+  if (!(node && node.data && node.data.template.properties.enabled)) {
+    open.next(false)
+    return <></>
+  }
+
+  const gate = node.data
+
+  return (
+    <div
+      id="gate-properties-modal"
+      className={openSnapshot ? 'visible' : ''}
+      onClick={() => {
         open.next(false)
-        return <></>
-    }
+      }}
+    >
+      <div
+        id="gate-properties-container"
+        onClick={(e) => {
+          e.stopPropagation()
+        }}
+      >
+        <div id="gate-props-title">Gate properties</div>
 
-    const gate = node.data
-
-    return (
-        <div
-            id="gate-properties-modal"
-            className={openSnapshot ? 'visible' : ''}
-            onClick={() => {
-                open.next(false)
-            }}
-        >
-            <div
-                id="gate-properties-container"
-                onClick={(e) => {
-                    e.stopPropagation()
-                }}
-            >
-                <div id="gate-props-title">Gate properties</div>
-
-                {gate.template.properties.data.map((prop, index) => {
-                    return (
-                        <GateProperty
-                            props={gate.props}
-                            raw={prop}
-                            gate={gate}
-                            key={`${index}-${id.value}`}
-                        />
-                    )
-                })}
-            </div>
-        </div>
-    )
+        {gate.template.properties.data.map((prop, index) => {
+          return (
+            <GateProperty
+              props={gate.props}
+              raw={prop}
+              gate={gate}
+              key={`${index}-${id.value}`}
+            />
+          )
+        })}
+      </div>
+    </div>
+  )
 }
 
 export default GateProperties
diff --git a/src/modules/logic-gates/helpers/completeTemplate.ts b/src/modules/logic-gates/helpers/completeTemplate.ts
index b2f0e3a..e9da404 100644
--- a/src/modules/logic-gates/helpers/completeTemplate.ts
+++ b/src/modules/logic-gates/helpers/completeTemplate.ts
@@ -3,7 +3,7 @@ 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[]) => a.concat(b)
-    }) as GateTemplate
+  return merge(DefaultGateTemplate, template, {
+    arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
+  }) as GateTemplate
 }
diff --git a/src/modules/saving/constants.ts b/src/modules/saving/constants.ts
index d8ac7d9..8aaed7c 100644
--- a/src/modules/saving/constants.ts
+++ b/src/modules/saving/constants.ts
@@ -20,43 +20,45 @@ 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>[] = [
-    andTemplate,
-    buttonTemplate,
-    lightTemplate,
-    nandTemplate,
-    norTemplate,
-    notTemplate,
-    orTemplate,
-    parallelDelayerTemplate,
-    rgbLightTemplate,
-    sequentialDelayerTemplate,
-    xnorTemplate,
-    xorTemplate,
-    halfAdderTemplate,
-    fullAdderTemplate,
-    _4bitEncoderTemplate,
-    _4bitDecoderTemplate,
-    comparatorTemplate,
-    bitMergerTemplate,
-    bitSplitterTemplate,
-    incrementorTemplate
+  andTemplate,
+  buttonTemplate,
+  lightTemplate,
+  nandTemplate,
+  norTemplate,
+  notTemplate,
+  orTemplate,
+  parallelDelayerTemplate,
+  rgbLightTemplate,
+  sequentialDelayerTemplate,
+  xnorTemplate,
+  xorTemplate,
+  halfAdderTemplate,
+  fullAdderTemplate,
+  _4bitEncoderTemplate,
+  _4bitDecoderTemplate,
+  comparatorTemplate,
+  bitMergerTemplate,
+  bitSplitterTemplate,
+  incrementorTemplate,
+  constantTemplate
 ]
 
 export const baseSave: RendererState = {
-    camera: {
-        transform: {
-            position: [0, 0],
-            scale: [1, 1],
-            rotation: 0
-        }
-    },
-    simulation: {
-        gates: [],
-        mode: 'project',
-        wires: [],
-        name: 'default'
+  camera: {
+    transform: {
+      position: [0, 0],
+      scale: [1, 1],
+      rotation: 0
     }
+  },
+  simulation: {
+    gates: [],
+    mode: 'project',
+    wires: [],
+    name: 'default'
+  }
 }
diff --git a/src/modules/saving/data/categories.ts b/src/modules/saving/data/categories.ts
index 426c495..d8f0bc8 100644
--- a/src/modules/saving/data/categories.ts
+++ b/src/modules/saving/data/categories.ts
@@ -1,9 +1,9 @@
 export const categories = {
-    basic: 0,
-    math: 1,
-    time: 2,
-    compressing: 3,
-    io: 4,
-    advancedIo: 5,
-    ic: 6
+  basic: 0,
+  math: 1,
+  time: 2,
+  compressing: 3,
+  io: 4,
+  advancedIo: 5,
+  ic: 6
 }
diff --git a/src/modules/saving/helpers/initBaseTemplates.ts b/src/modules/saving/helpers/initBaseTemplates.ts
index 2a2badc..817d861 100644
--- a/src/modules/saving/helpers/initBaseTemplates.ts
+++ b/src/modules/saving/helpers/initBaseTemplates.ts
@@ -8,13 +8,13 @@ import { SimulationError } from '../../errors/classes/SimulationError'
  * @throws SimulationError if something is wrong with the template
  */
 export const initBaseTemplates = () => {
-    for (const template of baseTemplates) {
-        if (template.metadata && template.metadata.name) {
-            templateStore.set(template.metadata.name, template)
-        } else {
-            throw new SimulationError(
-                `Template ${JSON.stringify(template)} cannot be stored.`
-            )
-        }
+  for (const template of baseTemplates) {
+    if (template.metadata && template.metadata.name) {
+      templateStore.set(template.metadata.name, template)
+    } else {
+      throw new SimulationError(
+        `Template ${JSON.stringify(template)} cannot be stored.`
+      )
     }
+  }
 }
diff --git a/src/modules/saving/templates/comment.ts b/src/modules/saving/templates/comment.ts
index 48decbd..4113bb5 100644
--- a/src/modules/saving/templates/comment.ts
+++ b/src/modules/saving/templates/comment.ts
@@ -4,44 +4,44 @@ import { PartialTemplate } from '../types/PartialTemplate'
  * The template of the comment gate
  */
 const commentTemplate: PartialTemplate = {
-    metadata: {
-        name: 'comment'
+  metadata: {
+    name: 'comment'
+  },
+  pins: {
+    inputs: {
+      count: 0
     },
-    pins: {
-        inputs: {
-            count: 0
-        },
-        outputs: {
-            count: 0
-        }
-    },
-    material: {
-        fill: '#007A72'
-    },
-    shape: {
-        scale: [300, 100]
-    },
-    code: {
-        activation: `
-            context.innerText(context.getProperty('content'))
-        `
-    },
-    info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
-    properties: {
-        enabled: true,
-        data: [
-            {
-                needsUpdate: true,
-                base: 'Your comment here',
-                name: 'content',
-                type: 'string'
-            }
-        ]
-    },
-    innerText: {
-        enabled: true,
-        color: '#ADFFFA'
+    outputs: {
+      count: 0
     }
+  },
+  material: {
+    fill: '#007A72'
+  },
+  shape: {
+    scale: [300, 100]
+  },
+  code: {
+    activation: `
+      context.innerText(context.getProperty('content'))
+    `
+  },
+  info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
+  properties: {
+    enabled: true,
+    data: [
+      {
+        needsUpdate: true,
+        base: 'Your comment here',
+        name: 'content',
+        type: 'string'
+      }
+    ]
+  },
+  innerText: {
+    enabled: true,
+    color: '#ADFFFA'
+  }
 }
 
 export default commentTemplate
diff --git a/src/modules/saving/templates/constant.ts b/src/modules/saving/templates/constant.ts
new file mode 100644
index 0000000..94e6f55
--- /dev/null
+++ b/src/modules/saving/templates/constant.ts
@@ -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
diff --git a/src/modules/saving/templates/light.ts b/src/modules/saving/templates/light.ts
index ab273f9..8b3a89a 100644
--- a/src/modules/saving/templates/light.ts
+++ b/src/modules/saving/templates/light.ts
@@ -5,40 +5,40 @@ import { categories } from '../data/categories'
  * The template of the light gate
  */
 const lightTemplate: PartialTemplate = {
-    metadata: {
-        name: 'light bulb'
+  metadata: {
+    name: 'light bulb'
+  },
+  shape: {
+    radius: 50
+  },
+  material: {
+    fill: '#1C1C1C',
+    stroke: {
+      normal: '#3C3C3C'
     },
-    shape: {
-        radius: 50
-    },
-    material: {
-        fill: '#1C1C1C',
-        stroke: {
-            normal: '#3C3C3C'
-        },
-        colors: {
-            active: '#C6FF00'
-        }
-    },
-    code: {
-        activation: `
+    colors: {
+      active: '#C6FF00'
+    }
+  },
+  code: {
+    activation: `
             const { main, active } = context.colors
 
             const bits = context.get(0)
 
             context.color(parseInt(context.get(0),2) ? active : main)
         `
-    },
-    integration: {
-        output: true
-    },
-    info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
-    pins: {
-        outputs: {
-            count: 0
-        }
-    },
-    category: categories.io
+  },
+  integration: {
+    output: true
+  },
+  info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
+  pins: {
+    outputs: {
+      count: 0
+    }
+  },
+  category: categories.io
 }
 
 export default lightTemplate
diff --git a/src/modules/simulation/classes/Gate.ts b/src/modules/simulation/classes/Gate.ts
index 50e2850..332bcac 100644
--- a/src/modules/simulation/classes/Gate.ts
+++ b/src/modules/simulation/classes/Gate.ts
@@ -1,20 +1,15 @@
 import { Transform } from '../../../common/math/classes/Transform'
 import { Pin } from './Pin'
 import {
-    GateTemplate,
-    PinCount,
-    isGroup,
-    Property
+  GateTemplate,
+  PinCount,
+  isGroup,
+  Property
 } from '../types/GateTemplate'
 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'
 
@@ -33,17 +27,17 @@ import { ValueOf } from '../../../common/lang/record/types/ValueOf'
  * The interface for the pins of a gate
  */
 export interface GatePins {
-    inputs: Pin[]
-    outputs: Pin[]
+  inputs: Pin[]
+  outputs: Pin[]
 }
 
 /**
  * Wrapper around a pin so it can be rendered at the right place
  */
 export interface PinWrapper {
-    total: number
-    index: number
-    value: Pin
+  total: number
+  index: number
+  value: Pin
 }
 
 /**
@@ -55,502 +49,501 @@ export type GateFunction = null | ((ctx: Context) => void)
  * All functions a gate must remember
  */
 export interface GateFunctions {
-    activation: GateFunction
-    onClick: GateFunction
+  activation: GateFunction
+  onClick: GateFunction
 }
 
 export type GateProps = {
-    [K in keyof PropsSave]: BehaviorSubject<PropsSave[K]> | GateProps
+  [K in keyof PropsSave]: BehaviorSubject<PropsSave[K]> | GateProps
 } & {
-    external: BehaviorSubject<boolean>
-    label: BehaviorSubject<string>
+  external: BehaviorSubject<boolean>
+  label: BehaviorSubject<string>
 }
 
 export class Gate {
-    /**
-     * The transform of the gate
-     */
-    public transform = new Transform()
+  /**
+   * The transform of the gate
+   */
+  public transform = new Transform()
 
-    /**
-     * The object holding all the pins the gate curently has
-     */
-    public _pins: GatePins = {
-        inputs: [],
-        outputs: []
+  /**
+   * The object holding all the pins the gate curently has
+   */
+  public _pins: GatePins = {
+    inputs: [],
+    outputs: []
+  }
+
+  /**
+   * The id of the gate
+   */
+  public id: number
+
+  /**
+   * The template the gate needs to follow
+   */
+  public template: GateTemplate
+
+  /**
+   * All the functions created from the template strings
+   */
+  private functions: GateFunctions = {
+    activation: null,
+    onClick: null
+  }
+
+  /**
+   * Used only if the gate is async
+   */
+  private executionQueue = new ExecutionQueue<void>()
+
+  /**
+   * All rxjs subscriptions the gate created
+   * (if they are not manually cleared it can lead to memory leaks)
+   */
+  private subscriptions: Subscription[] = []
+
+  /**
+   * The state the activation functions have aces to
+   */
+  private memory: Record<string, unknown> = {}
+
+  /**
+   * The inner simulaton used by integrated circuits
+   */
+  private ghostSimulation: Simulation
+
+  /**
+   * The wires connecting the outer simulation to the inner one
+   */
+  private ghostWires: Wire[] = []
+
+  /**
+   * Boolean keeping track if the component is an ic
+   */
+  private isIntegrated = false
+
+  /**
+   * Used to know if the component runs in the global scope (rendered)
+   * or insie an integrated circuit
+   */
+  public env: SimulationEnv = 'global'
+
+  /**
+   * Holds all the gate-related text
+   */
+  public text = {
+    inner: new BehaviorSubject<string | null>(null)
+  }
+
+  /**
+   * The props used by the activation function (the same as memory but presists)
+   */
+  public props: GateProps = {} as GateProps
+
+  /**
+   * The main logic gate class
+   *
+   * @param template The template the gate needs to follow
+   * @param id The id of the gate
+   */
+  public constructor(
+    template: DeepPartial<GateTemplate> = {},
+    id?: number,
+    props: PropsSave = {}
+  ) {
+    this.template = completeTemplate(template)
+    this.transform.scale = this.template.shape.scale
+
+    if (this.template.material.type === 'color') {
+      this.template.material.colors.main = this.template.material.fill
     }
 
-    /**
-     * The id of the gate
-     */
-    public id: number
+    this.functions.activation = toFunction(
+      this.template.code.activation,
+      'context'
+    )
 
-    /**
-     * The template the gate needs to follow
-     */
-    public template: GateTemplate
+    this.functions.onClick = toFunction(this.template.code.onClick, 'context')
 
-    /**
-     * All the functions created from the template strings
-     */
-    private functions: GateFunctions = {
-        activation: null,
-        onClick: null
+    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)
     }
 
-    /**
-     * Used only if the gate is async
-     */
-    private executionQueue = new ExecutionQueue<void>()
+    this.id = id !== undefined ? id : idStore.generate()
 
-    /**
-     * All rxjs subscriptions the gate created
-     * (if they are not manually cleared it can lead to memory leaks)
-     */
-    private subscriptions: Subscription[] = []
+    for (const pin of this._pins.inputs) {
+      const pipes = getGateTimePipes(this.template)
 
-    /**
-     * The state the activation functions have aces to
-     */
-    private memory: Record<string, unknown> = {}
+      const subscription = pin.state.pipe(...pipes).subscribe(() => {
+        if (this.template.code.async) {
+          this.executionQueue.push(async () => {
+            return this.update()
+          })
+        } else {
+          this.update()
+        }
+      })
 
-    /**
-     * The inner simulaton used by integrated circuits
-     */
-    private ghostSimulation: Simulation
-
-    /**
-     * The wires connecting the outer simulation to the inner one
-     */
-    private ghostWires: Wire[] = []
-
-    /**
-     * Boolean keeping track if the component is an ic
-     */
-    private isIntegrated = false
-
-    /**
-     * Used to know if the component runs in the global scope (rendered)
-     * or insie an integrated circuit
-     */
-    public env: SimulationEnv = 'global'
-
-    /**
-     * Holds all the gate-related text
-     */
-    public text = {
-        inner: new BehaviorSubject('text goes here')
+      this.subscriptions.push(subscription)
     }
 
-    /**
-     * The props used by the activation function (the same as memory but presists)
-     */
-    public props: GateProps = {} as GateProps
+    this.init()
 
-    /**
-     * The main logic gate class
-     *
-     * @param template The template the gate needs to follow
-     * @param id The id of the gate
-     */
-    public constructor(
-        template: DeepPartial<GateTemplate> = {},
-        id?: number,
-        props: PropsSave = {}
-    ) {
-        this.template = completeTemplate(template)
-        this.transform.scale = this.template.shape.scale
+    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')
+      cleanSimulation(this.ghostSimulation)
+
+      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: ${inputs.length} !== ${this._pins.inputs.length}`
+        )
+      }
+
+      if (outputs.length !== this._pins.outputs.length) {
+        throw new SimulationError(
+          `Output count needs to match with the container gate: ${outputs.length} !== ${this._pins.outputs.length}`
+        )
+      }
+
+      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)
+    }
+
+    this.assignProps(props)
+  }
+
+  private updateNestedProp(
+    path: string[],
+    value: ValueOf<PropsSave>,
+    gate: Gate = this
+  ) {
+    if (!path.length) {
+      return
+    }
+
+    if (path.length === 1) {
+      const subject = gate.props[path[0]]
+
+      if (subject instanceof BehaviorSubject) {
+        subject.next(value)
+      }
+
+      return
+    }
+
+    const nextGates = [...gate.ghostSimulation.gates].filter(
+      (gate) => gate.props?.label?.value === path[0]
+    )
+
+    for (const nextGate of nextGates) {
+      this.updateNestedProp(path.slice(1), value, nextGate)
+    }
+  }
+
+  /**
+   * Assign the props passed to the gate and merge them with the base ones
+   */
+  private assignProps(
+    source: PropsSave,
+    props: Property[] = this.template.properties.data,
+    target: GateProps = this.props,
+    path: string[] = []
+  ) {
+    // 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
+          this.assignProps(
+            typeof source[groupName] === 'object'
+              ? (source[groupName] as PropsSave)
+              : {},
+            prop.props,
+            target[groupName] as GateProps,
+            [...path, groupName]
+          )
+
+          continue
+        }
+
+        const { name, base, needsUpdate } = prop
+
+        const subject = new BehaviorSubject(
+          source.hasOwnProperty(name) ? source[name] : base
+        )
+
+        target[name] = subject
+
+        this.subscriptions.push(
+          subject.subscribe((value) => {
+            if (!lockUpdates && needsUpdate && path.length === 0) {
+              return this.update()
+            }
+
+            if (path.length === 0) {
+              return
+            }
+
+            this.updateNestedProp([...path, name], value)
+          })
+        )
+      }
+    }
+
+    lockUpdates = false
+    this.update()
+  }
+
+  /**
+   * Runs the init function from the template
+   */
+  private init() {
+    toFunction<[InitialisationContext]>(
+      this.template.code.initialisation,
+      'context'
+    )({
+      memory: this.memory
+    })
+  }
+
+  /**
+   * Runs the onClick function from the template
+   */
+  public onClick() {
+    if (this.functions.onClick) {
+      this.functions.onClick(this.getContext())
+    }
+  }
+
+  /**
+   * Used to get the props as an object
+   */
+  public getProps(target = this.props) {
+    const props: PropsSave = {}
+
+    for (const [key, value] of Object.entries(target)) {
+      if (value instanceof BehaviorSubject) {
+        props[key] = value.value
+      } else {
+        props[key] = this.getProps(value)
+      }
+    }
+
+    return props
+  }
+
+  /**
+   * Clears subscriptions to prevent memory leaks
+   */
+  public dispose() {
+    for (const pin of this.pins) {
+      pin.value.dispose()
+    }
+
+    for (const subscription of this.subscriptions) {
+      subscription.unsubscribe()
+    }
+
+    if (this.isIntegrated) {
+      this.ghostSimulation.dispose()
+    }
+  }
+
+  /**
+   * Runs the activation function from the template
+   */
+  public update() {
+    if (!this.template.tags.includes('integrated')) {
+      const context = this.getContext()
+
+      if (!this.functions.activation)
+        throw new SimulationError('Activation function is missing')
+
+      return this.functions.activation(context)
+    }
+  }
+
+  /**
+   * Generates the activation context
+   */
+  public getContext(): Context {
+    const maxLength = Math.max(
+      ...this._pins.inputs.map((pin) => pin.state.value.length)
+    )
+
+    const toLength = (
+      original: string | number,
+      length: number = maxLength,
+      paddingChar = '0'
+    ) => {
+      const value = original.toString(2)
+
+      if (value.length === length) {
+        return value
+      } else if (value.length > length) {
+        const difference = value.length - length
+
+        return value.slice(difference)
+      } else {
+        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) => {
+        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)
+      },
+      color: (color: string) => {
         if (this.template.material.type === 'color') {
-            this.template.material.colors.main = this.template.material.fill
+          this.template.material.fill = color
         }
-
-        this.functions.activation = toFunction(
-            this.template.code.activation,
-            '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
-        )
-
-        if (this.template.material.type === 'image') {
-            ImageStore.set(this.template.material.fill)
+      },
+      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
         }
-
-        this.id = id !== undefined ? id : idStore.generate()
-
-        for (const pin of this._pins.inputs) {
-            const pipes = getGateTimePipes(this.template)
-
-            const subscription = pin.state.pipe(...pipes).subscribe(() => {
-                if (this.template.code.async) {
-                    this.executionQueue.push(async () => {
-                        return await this.update()
-                    })
-                } else {
-                    this.update()
-                }
-            })
-
-            this.subscriptions.push(subscription)
+      },
+      setProperty: (name: string, value: string | number | boolean) => {
+        const subject = this.props[name]
+        if (subject instanceof BehaviorSubject) {
+          subject.next(value)
         }
+      },
+      innerText: (value: string) => {
+        this.text.inner.next(value)
+      },
+      update: () => {
+        this.update()
+      },
+      toLength,
+      maxLength,
+      enviroment: this.env,
+      memory: this.memory,
+      colors: {
+        ...this.template.material.colors
+      }
+    }
+  }
 
-        this.init()
+  /**
+   * Generates pin wrappers from an array of pins
+   *
+   * @param pins The pins to wwap
+   */
+  private wrapPins(pins: Pin[]) {
+    const result: PinWrapper[] = []
+    const length = pins.length
 
-        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')
-            cleanSimulation(this.ghostSimulation)
-
-            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: ${inputs.length} !== ${this._pins.inputs.length}`
-                )
-            }
-
-            if (outputs.length !== this._pins.outputs.length) {
-                throw new SimulationError(
-                    `Output count needs to match with the container gate: ${outputs.length} !== ${this._pins.outputs.length}`
-                )
-            }
-
-            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)
-        }
-
-        this.assignProps(props)
+    for (let index = 0; index < length; index++) {
+      result.push({
+        index,
+        total: length,
+        value: pins[index]
+      })
     }
 
-    private updateNestedProp(
-        path: string[],
-        value: ValueOf<PropsSave>,
-        gate: Gate = this
-    ) {
-        if (!path.length) {
-            return
-        }
+    return result
+  }
 
-        if (path.length === 1) {
-            const subject = gate.props[path[0]]
+  /**
+   * Returns all pins (input + output)
+   */
+  public get pins() {
+    const result = [
+      ...this.wrapPins(this._pins.inputs),
+      ...this.wrapPins(this._pins.outputs)
+    ]
 
-            if (subject instanceof BehaviorSubject) {
-                subject.next(value)
-            }
+    return result
+  }
 
-            return
-        }
-
-        const nextGates = [...gate.ghostSimulation.gates].filter(
-            (gate) => gate.props?.label?.value === path[0]
-        )
-
-        for (const nextGate of nextGates) {
-            this.updateNestedProp(path.slice(1), value, nextGate)
-        }
-    }
-
-    /**
-     * Assign the props passed to the gate and mere them with the base ones
-     */
-    private assignProps(
-        source: PropsSave,
-        props: Property[] = this.template.properties.data,
-        target: GateProps = this.props,
-        path: string[] = []
-    ) {
-        let shouldUpdate = false
-
-        if (this.template.properties.enabled) {
-            for (const prop of props) {
-                if (isGroup(prop)) {
-                    const { groupName } = prop
-                    target[groupName] = {} as GateProps
-                    const needsUpdate = this.assignProps(
-                        typeof source[groupName] === 'object'
-                            ? (source[groupName] as PropsSave)
-                            : {},
-                        prop.props,
-                        target[groupName] as GateProps,
-                        [...path, groupName]
-                    )
-
-                    if (needsUpdate) {
-                        shouldUpdate = true
-                    }
-
-                    continue
-                }
-
-                const { name, base, needsUpdate } = prop
-
-                const subject = new BehaviorSubject(
-                    source.hasOwnProperty(name) ? source[name] : base
-                )
-
-                target[name] = subject
-
-                this.subscriptions.push(
-                    subject.subscribe((value) => {
-                        if (needsUpdate && path.length === 0) {
-                            return this.update()
-                        }
-
-                        if (path.length === 0) {
-                            return
-                        }
-
-                        this.updateNestedProp([...path, name], value)
-                    })
-                )
-
-                if (needsUpdate) {
-                    shouldUpdate = true
-                }
-            }
-        }
-
-        return shouldUpdate
-    }
-
-    /**
-     * Runs the init function from the template
-     */
-    private init() {
-        toFunction<[InitialisationContext]>(
-            this.template.code.initialisation,
-            'context'
-        )({
-            memory: this.memory
-        })
-    }
-
-    /**
-     * Runs the onClick function from the template
-     */
-    public onClick() {
-        if (this.functions.onClick) {
-            this.functions.onClick(this.getContext())
-        }
-    }
-
-    /**
-     * Used to get the props as an object
-     */
-    public getProps(target = this.props) {
-        const props: PropsSave = {}
-
-        for (const [key, value] of Object.entries(target)) {
-            if (value instanceof BehaviorSubject) {
-                props[key] = value.value
-            } else {
-                props[key] = this.getProps(value)
-            }
-        }
-
-        return props
-    }
-
-    /**
-     * Clears subscriptions to prevent memory leaks
-     */
-    public dispose() {
-        for (const pin of this.pins) {
-            pin.value.dispose()
-        }
-
-        for (const subscription of this.subscriptions) {
-            subscription.unsubscribe()
-        }
-
-        if (this.isIntegrated) {
-            this.ghostSimulation.dispose()
-        }
-    }
-
-    /**
-     * Runs the activation function from the template
-     */
-    public update() {
-        if (!this.template.tags.includes('integrated')) {
-            const context = this.getContext()
-
-            if (!this.functions.activation)
-                throw new SimulationError('Activation function is missing')
-
-            return this.functions.activation(context)
-        }
-    }
-
-    /**
-     * Generates the activation context
-     */
-    public getContext(): Context {
-        const maxLength = Math.max(
-            ...this._pins.inputs.map((pin) => pin.state.value.length)
-        )
-
-        const toLength = (
-            original: string | number,
-            length: number = maxLength
-        ) => {
-            const value = original.toString(2)
-
-            if (value.length === length) {
-                return value
-            } else if (value.length > length) {
-                const difference = value.length - length
-
-                return value.substr(difference)
-            } else {
-                return `${'0'.repeat(length - value.length)}${value}`
-            }
-        }
-
-        return {
-            get: (index: number) => {
-                return this._pins.inputs[index].state.value
-            },
-            set: (index: number, state) => {
-                return this._pins.outputs[index].state.next(state)
-            },
-            getBinary: (index: number) => {
-                return parseInt(this._pins.inputs[index].state.value, 2)
-            },
-            setBinary: (
-                index: number,
-                value: number,
-                bits: number = maxLength
-            ) => {
-                return this._pins.outputs[index].state.next(
-                    toLength(value.toString(2), bits)
-                )
-            },
-            invertBinary: (value: number) => {
-                return value ^ ((1 << maxLength) - 1)
-            },
-            color: (color: string) => {
-                if (this.template.material.type === 'color') {
-                    this.template.material.fill = color
-                }
-            },
-            getProperty: (name: string) => {
-                return this.props[name].value
-            },
-            setProperty: (name: string, value: string | number | boolean) => {
-                const subject = this.props[name]
-                if (subject instanceof BehaviorSubject) {
-                    subject.next(value)
-                }
-            },
-            innerText: (value: string) => {
-                this.text.inner.next(value)
-            },
-            update: () => {
-                this.update()
-            },
-            toLength,
-            maxLength,
-            enviroment: this.env,
-            memory: this.memory,
-            colors: {
-                ...this.template.material.colors
-            }
-        }
-    }
-
-    /**
-     * Generates pin wrappers from an array of pins
-     *
-     * @param pins The pins to wwap
-     */
-    private wrapPins(pins: Pin[]) {
-        const result: PinWrapper[] = []
-        const length = pins.length
-
-        for (let index = 0; index < length; index++) {
-            result.push({
-                index,
-                total: length,
-                value: pins[index]
-            })
-        }
-
-        return result
-    }
-
-    /**
-     * Returns all pins (input + output)
-     */
-    public get pins() {
-        const result = [
-            ...this.wrapPins(this._pins.inputs),
-            ...this.wrapPins(this._pins.outputs)
-        ]
-
-        return result
-    }
-
-    /**
-     * Generates empty pins for any gate
-     */
-    private static generatePins(options: PinCount, type: number, gate: Gate) {
-        return [...Array(options.count)]
-            .fill(true)
-            .map((v, index) => new Pin(type, gate))
-    }
+  /**
+   * Generates empty pins for any gate
+   */
+  private static generatePins(options: PinCount, type: number, gate: Gate) {
+    return [...Array(options.count)]
+      .fill(true)
+      .map((_v, _index) => new Pin(type, gate))
+  }
 }
diff --git a/src/modules/simulation/constants.ts b/src/modules/simulation/constants.ts
index 726e2f1..5d2f1df 100644
--- a/src/modules/simulation/constants.ts
+++ b/src/modules/simulation/constants.ts
@@ -3,80 +3,80 @@ import { categories } from '../saving/data/categories'
 import { getRendererSafely } from '../logic-gates/helpers/getRendererSafely'
 
 export const DefaultGateTemplate: GateTemplate = {
-    metadata: {
-        name: 'Default template'
+  metadata: {
+    name: 'Default template'
+  },
+  material: {
+    type: 'color',
+    fill: 'blue',
+    stroke: {
+      active: '#76FF02',
+      normal: '#3FC4FF'
     },
-    material: {
-        type: 'color',
-        fill: 'blue',
-        stroke: {
-            active: '#76FF02',
-            normal: '#3FC4FF'
-        },
-        colors: {}
+    colors: {}
+  },
+  pins: {
+    inputs: {
+      count: 1,
+      variable: false
     },
-    pins: {
-        inputs: {
-            count: 1,
-            variable: false
-        },
-        outputs: {
-            count: 1,
-            variable: false
-        }
+    outputs: {
+      count: 1,
+      variable: false
+    }
+  },
+  shape: {
+    radius: 10,
+    rounded: true,
+    scale: [100, 100]
+  },
+  code: {
+    async: false,
+    activation: '',
+    onClick: '',
+    initialisation: ''
+  },
+  simulation: {
+    debounce: {
+      enabled: true,
+      time: 1000 / 60
     },
-    shape: {
-        radius: 10,
-        rounded: true,
-        scale: [100, 100]
-    },
-    code: {
-        async: false,
-        activation: '',
-        onClick: '',
-        initialisation: ''
-    },
-    simulation: {
-        debounce: {
-            enabled: true,
-            time: 1000 / 60
-        },
-        throttle: {
-            enabled: false
-        }
-    },
-    integration: {
-        allowed: true,
-        input: false,
-        output: false
-    },
-    tags: ['base'],
-    properties: {
-        enabled: false,
-        data: [
-            {
-                type: 'boolean',
-                base: false,
-                name: 'external'
-            },
-            {
-                type: 'string',
-                base: 'my-logic-gate',
-                name: 'label'
-            }
-        ]
-    },
-    innerText: {
-        enabled: false,
-        color: 'white'
-    },
-    category: categories.basic,
-    info: []
+    throttle: {
+      enabled: false
+    }
+  },
+  integration: {
+    allowed: true,
+    input: false,
+    output: false
+  },
+  tags: ['base'],
+  properties: {
+    enabled: false,
+    data: [
+      {
+        type: 'boolean',
+        base: false,
+        name: 'external'
+      },
+      {
+        type: 'string',
+        base: 'my-logic-gate',
+        name: 'label'
+      }
+    ]
+  },
+  innerText: {
+    enabled: false,
+    color: 'white'
+  },
+  category: categories.basic,
+  info: []
 }
 
 /**
  * Prop names which need to not be overriten
  */
 export const reservedPropNames = DefaultGateTemplate.properties.data.map(
-    ({ name }: RawProp) => name
+  ({ name }: RawProp) => name
 )
diff --git a/src/modules/simulation/types/GateTemplate.ts b/src/modules/simulation/types/GateTemplate.ts
index 53b5718..7888c0b 100644
--- a/src/modules/simulation/types/GateTemplate.ts
+++ b/src/modules/simulation/types/GateTemplate.ts
@@ -1,96 +1,97 @@
 import { vector2 } from '../../../common/math/types/vector2'
 
 export interface PinCount {
-    variable: boolean
-    count: number
+  variable: boolean
+  count: number
 }
 
 export type PropGroup<
-    T extends boolean | number | string = boolean | number | string
+  T extends boolean | number | string = boolean | number | string
 > = {
-    groupName: string
-    props: Property<T>[]
+  groupName: string
+  props: Property<T>[]
 }
 
 export const isGroup = (prop: Property): prop is PropGroup =>
-    (prop as PropGroup).groupName !== undefined
+  (prop as PropGroup).groupName !== undefined
 
 export type RawProp<
-    T extends boolean | number | string = boolean | number | string
+  T extends boolean | number | string = boolean | number | string
 > = {
-    type: 'number' | 'string' | 'text' | 'boolean'
-    base: T
-    name: string
-    needsUpdate?: boolean
+  type: 'number' | 'string' | 'text' | 'boolean'
+  base: T
+  name: string
+  description?: string
+  needsUpdate?: boolean
 }
 export type Property<
-    T extends boolean | number | string = boolean | number | string
+  T extends boolean | number | string = boolean | number | string
 > = PropGroup<T> | RawProp<T>
 
 export interface Material {
-    type: 'color' | 'image'
-    fill: string
-    stroke: {
-        active: string
-        normal: string
-    }
-    colors: Record<string, string>
+  type: 'color' | 'image'
+  fill: string
+  stroke: {
+    active: string
+    normal: string
+  }
+  colors: Record<string, string>
 }
 
 export interface Shape {
-    rounded: boolean
-    radius: number
-    scale: vector2
+  rounded: boolean
+  radius: number
+  scale: vector2
 }
 
 export type Enabled<T> =
-    | {
-          enabled: false
-      }
-    | ({
-          enabled: true
-      } & T)
+  | {
+      enabled: false
+    }
+  | ({
+      enabled: true
+    } & T)
 
 export type TimePipe = Enabled<{
-    time: number
+  time: number
 }>
 
 export type GateTag = 'base' | 'imported' | 'integrated'
 
 export interface GateTemplate {
-    material: Material
-    shape: Shape
-    pins: {
-        inputs: PinCount
-        outputs: PinCount
-    }
-    metadata: {
-        name: string
-    }
-    code: {
-        async: boolean
-        initialisation: string
-        activation: string
-        onClick: string
-    }
-    simulation: {
-        throttle: TimePipe
-        debounce: TimePipe
-    }
-    integration: {
-        allowed: boolean
-        input: boolean
-        output: boolean
-    }
-    tags: GateTag[]
-    properties: {
-        enabled: boolean
-        data: Property[]
-    }
-    innerText: {
-        color: string
-        enabled: boolean
-    }
-    category: number // for better sorting
-    info: string[]
+  material: Material
+  shape: Shape
+  pins: {
+    inputs: PinCount
+    outputs: PinCount
+  }
+  metadata: {
+    name: string
+  }
+  code: {
+    async: boolean
+    initialisation: string
+    activation: string
+    onClick: string
+  }
+  simulation: {
+    throttle: TimePipe
+    debounce: TimePipe
+  }
+  integration: {
+    allowed: boolean
+    input: boolean
+    output: boolean
+  }
+  tags: GateTag[]
+  properties: {
+    enabled: boolean
+    data: Property[]
+  }
+  innerText: {
+    color: string
+    enabled: boolean
+  }
+  category: number // for better sorting
+  info: string[]
 }
diff --git a/src/modules/simulationRenderer/data/textSettings.ts b/src/modules/simulationRenderer/data/textSettings.ts
index 684182a..45d4084 100644
--- a/src/modules/simulationRenderer/data/textSettings.ts
+++ b/src/modules/simulationRenderer/data/textSettings.ts
@@ -1,5 +1,5 @@
 export const textSettings = {
-    font: '30px Roboto',
-    offset: 35,
-    fill: `rgba(256,256,256,0.75)`
+  font: '30px monospace',
+  offset: 35,
+  fill: `rgba(256,256,256,0.75)`
 }
diff --git a/src/modules/simulationRenderer/helpers/pinFill.ts b/src/modules/simulationRenderer/helpers/pinFill.ts
index 0b1f456..bd1a32c 100644
--- a/src/modules/simulationRenderer/helpers/pinFill.ts
+++ b/src/modules/simulationRenderer/helpers/pinFill.ts
@@ -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))
diff --git a/src/modules/simulationRenderer/helpers/renderGate.ts b/src/modules/simulationRenderer/helpers/renderGate.ts
index aa91ead..35f2f8b 100644
--- a/src/modules/simulationRenderer/helpers/renderGate.ts
+++ b/src/modules/simulationRenderer/helpers/renderGate.ts
@@ -10,64 +10,85 @@ import { idIsSelected } from './idIsSelected'
 import { textSettings } from '../data/textSettings'
 
 export const renderGate = (
-    ctx: CanvasRenderingContext2D,
-    renderer: SimulationRenderer,
-    gate: Gate
+  ctx: CanvasRenderingContext2D,
+  renderer: SimulationRenderer,
+  gate: Gate
 ) => {
-    const { active, normal } = gate.template.material.stroke
+  const { active, normal } = gate.template.material.stroke
 
-    const selected =
-        (renderer.mouseState >> 2 &&
-            !!gatesInSelection(renderer.selectedArea, [gate]).length) ||
-        idIsSelected(renderer, gate.id)
+  const selected =
+    (renderer.mouseState >> 2 &&
+      !!gatesInSelection(renderer.selectedArea, [gate]).length) ||
+    idIsSelected(renderer, gate.id)
 
-    renderPins(ctx, renderer, gate, selected)
+  renderPins(ctx, renderer, gate, selected)
 
-    if (selected) {
-        ctx.strokeStyle = active
-    } else {
-        ctx.strokeStyle = normal
+  if (selected) {
+    ctx.strokeStyle = active
+  } else {
+    ctx.strokeStyle = normal
+  }
+
+  ctx.lineWidth = renderer.options.gates.gateStroke.width
+
+  ctx.save()
+
+  const relativeTransform = useTransform(ctx, gate.transform)
+  const renderingParameters = [
+    relativeTransform.x,
+    relativeTransform.y,
+    relativeTransform.width,
+    relativeTransform.height,
+    gate.template.shape.rounded ? gate.template.shape.radius : 0
+  ]
+
+  if (gate.template.material.type === 'image') {
+    roundImage(
+      ctx,
+      ImageStore.get(gate.template.material.fill),
+      ...renderingParameters
+    )
+  }
+
+  roundRect(ctx, ...renderingParameters)
+
+  if (gate.template.material.type === 'color') {
+    ctx.fillStyle = gate.template.material.fill
+
+    ctx.fill()
+  }
+
+  ctx.stroke()
+
+  if (gate.template.tags.includes('integrated')) {
+    ctx.textBaseline = 'top'
+    ctx.fillStyle = textSettings.fill
+    ctx.fillText(
+      gate.template.metadata.name,
+      relativeTransform.center[0],
+      relativeTransform.maxY + textSettings.offset
+    )
+  }
+
+  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.lineWidth = renderer.options.gates.gateStroke.width
+    ctx.font = `${size}px monospace`
+    ctx.fillText(
+      text,
+      relativeTransform.center[0],
+      relativeTransform.center[1] + 2
+    )
+  }
 
-    ctx.save()
-
-    const relativeTransform = useTransform(ctx, gate.transform)
-    const renderingParameters = [
-        relativeTransform.x,
-        relativeTransform.y,
-        relativeTransform.width,
-        relativeTransform.height,
-        gate.template.shape.rounded ? gate.template.shape.radius : 0
-    ]
-
-    if (gate.template.material.type === 'image') {
-        roundImage(
-            ctx,
-            ImageStore.get(gate.template.material.fill),
-            ...renderingParameters
-        )
-    }
-
-    roundRect(ctx, ...renderingParameters)
-
-    if (gate.template.material.type === 'color') {
-        ctx.fillStyle = gate.template.material.fill
-
-        ctx.fill()
-    }
-
-    ctx.stroke()
-
-    if (gate.template.tags.includes('integrated')) {
-        ctx.fillStyle = textSettings.fill
-        ctx.fillText(
-            gate.template.metadata.name,
-            relativeTransform.center[0],
-            relativeTransform.maxY + textSettings.offset
-        )
-    }
-
-    ctx.restore()
+  ctx.restore()
 }