Skip to content

Commit 94fe023

Browse files
committed
Add initial CompPort
1 parent aa0f28a commit 94fe023

13 files changed

+345
-27
lines changed

app/layout.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22
import React, { useEffect } from 'react';
33
import s from './layout.module.scss';
4-
import '@/styles/main.scss';
4+
import '@/styles/main.css';
55
import { inject } from '@vercel/analytics';
66
import { config } from '@fortawesome/fontawesome-svg-core'
77
import '@fortawesome/fontawesome-svg-core/styles.css'
@@ -26,4 +26,4 @@ export default function RootLayout({
2626
<link rel="preload" href="/fonts/font-atlas.png" as="image" />
2727
<link rel="preload" href="/fonts/font-def.json" as="fetch" />
2828
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&family=Merriweather:ital@0;1" />
29-
</head> */
29+
</head> */

src/cpu/CompLibraryView.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const CompLibraryView: React.FC = () => {
1010

1111
let { compLibrary } = editorState;
1212

13-
let compDefs = [...compLibrary.libraryLookup.values()];
13+
let compDefs = [...new Set([...compLibrary.libraryLookup.values()])];
1414

1515
let [, setDragStart] = useGlobalDrag<number>(function handleMove(ev, ds, end) {
1616
setEditorState(a => {

src/cpu/CpuCanvas.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export const CpuCanvas: React.FC = () => {
211211
<div className={s.compDomElementsInner} style={{ transform: `matrix(${editorState.mtx.toTransformParams().join(',')})` }}>
212212
{compDivs}
213213
</div>
214-
{/* {showTransparentComponents && <div className={s.compDomEventMask} />} */}
214+
{editorState.transparentComps && <div className={s.compDomEventMask} />}
215215
</div>
216216
</CanvasEventHandler>}
217217
<div className={s.toolsLeftTop}>

src/cpu/CpuExecution.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ export function backpropagateUnusedSignals(exeSystem: IExeSystem) {
574574
}
575575

576576
if (allOutputsUnused) {
577+
// console.log('marking net as unused', netToString(net, exeSystem.comps));
577578
for (let portRef of net.outputs) {
578579
portRef.exePort.dataUsed = false;
579580
}

src/cpu/comps/CompPort.tsx

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import React, { memo } from "react";
2+
import { StateSetter, assignImm, hasFlag } from "@/src/utils/data";
3+
import { Vec3 } from "@/src/utils/vector";
4+
import { IComp, IEditorState, IExeComp, IExePort, PortType } from "../CpuModel";
5+
import { ICompBuilderArgs, ICompDef } from "./CompBuilder";
6+
import { CompRectBase } from "./RenderHelpers";
7+
import { editCompConfig, useEditorContext } from "../Editor";
8+
import { HexValueEditor, HexValueInputType, clampToSignedWidth } from "../displayTools/HexValueEditor";
9+
import { CheckboxMenuTitle, ConfigMenu, MenuRow } from "./InputOutput";
10+
11+
export interface ICompPortConfig {
12+
portId: string;
13+
name: string;
14+
type: PortType;
15+
width: number;
16+
signed: boolean;
17+
inputOverride: boolean;
18+
inputValueOverride: number;
19+
valueMode: HexValueInputType;
20+
}
21+
22+
export interface ICompPortData {
23+
port: IExePort;
24+
value: number;
25+
}
26+
27+
export function createCompIoComps(args: ICompBuilderArgs) {
28+
29+
let w = 6;
30+
let h = 6;
31+
let compPort: ICompDef<ICompPortData, ICompPortConfig> = {
32+
defId: 'comp/port',
33+
name: "Port",
34+
size: new Vec3(w, h),
35+
ports: (args, compDef) => {
36+
37+
let internalPortDir = switchPortDir(args.type);
38+
39+
return [
40+
{ id: 'a', name: '', pos: new Vec3(w, 3), type: internalPortDir, width: args.width },
41+
];
42+
},
43+
initConfig: () => ({
44+
portId: '',
45+
name: '',
46+
type: PortType.Out,
47+
width: 1,
48+
signed: false,
49+
valueMode: HexValueInputType.Dec,
50+
inputOverride: false,
51+
inputValueOverride: 0,
52+
}),
53+
applyConfig(comp, args) {
54+
// let mat = rotateAboutAffineInt(args.rotate, rotateCenter);
55+
// comp.ports = comp.ports.map(p => {
56+
// return { ...p, pos: mat.mulVec3(p.pos) }
57+
// });
58+
},
59+
build: (builder) => {
60+
let args = builder.comp.args;
61+
let isInput = hasFlag(args.type, PortType.In);
62+
63+
let data = builder.addData({
64+
port: builder.getPort('a'),
65+
value: isInput && args.inputOverride ? args.inputValueOverride : 0,
66+
});
67+
68+
if (isInput) {
69+
builder.addPhase(({ data }) => {
70+
data.port.value = data.value;
71+
data.port.ioEnabled = true;
72+
}, [], [data.port]);
73+
74+
} else {
75+
builder.addPhase(({ data }) => {
76+
data.value = data.port.value;
77+
data.port.ioEnabled = true;
78+
}, [data.port], []);
79+
}
80+
81+
return builder.build();
82+
},
83+
renderAll: true,
84+
render: ({ comp, ctx, cvs, exeComp }) => {
85+
ctx.save();
86+
87+
ctx.fillStyle = 'rgb(251, 146, 60)';
88+
// let mtx = rotateAboutAffineInt(comp.args.rotate, comp.pos.add(rotateCenter));
89+
// ctx.transform(...mtx.toTransformParams());
90+
91+
ctx.beginPath();
92+
// basic structure is a circle
93+
let p = comp.pos;
94+
let s = comp.size;
95+
let center = p.mulAdd(s, 0.5);
96+
ctx.moveTo(p.x + s.x, center.y);
97+
ctx.arc(center.x, center.y, s.x / 2, 0, 2 * Math.PI);
98+
99+
ctx.closePath();
100+
ctx.fill();
101+
ctx.stroke();
102+
ctx.restore();
103+
},
104+
renderDom: ({ comp, exeComp, ctx, styles }) => {
105+
106+
return <PortEditor comp={comp} exeComp={exeComp} styles={styles} />;
107+
},
108+
};
109+
110+
return [compPort];
111+
}
112+
113+
export function switchPortDir(dir: PortType) {
114+
let newDir = dir & ~(PortType.In | PortType.Out);
115+
116+
if (hasFlag(dir, PortType.In)) {
117+
newDir |= PortType.Out;
118+
}
119+
if (hasFlag(dir, PortType.Out)) {
120+
newDir |= PortType.In;
121+
}
122+
123+
return newDir;
124+
}
125+
126+
function makeEditFunction<T, A>(setEditorState: StateSetter<IEditorState>, comp: IComp<T>, updateFn: (value: A, prev: T) => Partial<T>) {
127+
return (end: boolean, value: A) => {
128+
setEditorState(editCompConfig(end, comp, a => assignImm(a, updateFn(value, a))));
129+
};
130+
}
131+
132+
const PortEditor: React.FC<{
133+
comp: IComp<ICompPortConfig>,
134+
exeComp: IExeComp<ICompPortData>, styles: any,
135+
}> = memo(function PortEditor({ comp, exeComp, styles }) {
136+
let { setEditorState } = useEditorContext();
137+
138+
let editIsOverriden = makeEditFunction(setEditorState, comp, (value: boolean) => ({ inputOverride: value }));
139+
let editBitWidth = makeEditFunction(setEditorState, comp, (value: number) => ({ width: value, inputValueOverride: clampToSignedWidth(comp.args.inputValueOverride ?? 0, value, comp.args.signed) }));
140+
let editSigned = makeEditFunction(setEditorState, comp, (value: boolean) => ({ signed: value, inputValueOverride: clampToSignedWidth(comp.args.inputValueOverride ?? 0, comp.args.width, value) }));
141+
let editPortType = makeEditFunction(setEditorState, comp, (isInputPort: boolean, prev) => {
142+
let type = prev.type;
143+
if (isInputPort) {
144+
type |= PortType.In;
145+
type &= ~PortType.Out;
146+
} else {
147+
type |= PortType.Out;
148+
type &= ~PortType.In;
149+
}
150+
return { type };
151+
});
152+
153+
function editValueOverride(end: boolean, value: number, valueMode: HexValueInputType) {
154+
setEditorState(editCompConfig(end, comp, a => assignImm(a, { inputValueOverride: value, valueMode })));
155+
}
156+
157+
let isInput = hasFlag(comp.args.type, PortType.In);
158+
let isInputOverride = comp.args.inputOverride;
159+
160+
return <CompRectBase comp={comp} className={"pr-1"} hideHover={true}>
161+
{isInput && <HexValueEditor
162+
className="absolute inset-0 px-2"
163+
inputType={comp.args.valueMode}
164+
value={comp.args.inputValueOverride}
165+
update={editValueOverride}
166+
minimalBackground
167+
inputClassName="text-center"
168+
maxBits={comp.args.width}
169+
padBits={comp.args.width}
170+
signed={comp.args.signed}
171+
hidePrefix
172+
/>}
173+
{!isInput && <HexValueEditor
174+
className="absolute inset-0 px-2"
175+
value={exeComp?.data.value ?? 0}
176+
minimalBackground
177+
inputClassName="text-center"
178+
update={(end, _val, inputType) => editValueOverride(end, comp.args.inputValueOverride, inputType)}
179+
inputType={comp.args.valueMode}
180+
padBits={comp.args.width}
181+
signed={comp.args.signed}
182+
readonly
183+
hidePrefix
184+
/>}
185+
<ConfigMenu className={"absolute top-[12px] right-[12px]"}>
186+
<MenuRow title={<CheckboxMenuTitle title="Input" value={isInput} update={editPortType} />} />
187+
<MenuRow title={<CheckboxMenuTitle title="Override Value" value={isInputOverride} update={editIsOverriden} />} disabled={!isInput}>
188+
<HexValueEditor
189+
inputType={comp.args.valueMode}
190+
value={comp.args.inputValueOverride}
191+
update={editValueOverride}
192+
maxBits={comp.args.width}
193+
padBits={comp.args.width}
194+
signed={comp.args.signed}
195+
/>
196+
</MenuRow>
197+
<MenuRow title={"Bit Width"}>
198+
<HexValueEditor inputType={HexValueInputType.Dec} hidePrefix value={comp.args.width} update={editBitWidth} />
199+
</MenuRow>
200+
<MenuRow title={<CheckboxMenuTitle title="Signed" value={comp.args.signed} update={editSigned} />} />
201+
</ConfigMenu>
202+
</CompRectBase>;
203+
});

src/cpu/comps/InputOutput.tsx

+28-9
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export const InputEditor: React.FC<{
121121

122122
return <CompRectBase comp={comp} className={s.inputNumber} hideHover={true}>
123123
<HexValueEditor inputType={comp.args.valueMode} value={comp.args.value} update={editValue} minimalBackground />
124-
<ConfigMenu btnClassName={s.configMenuTopRight}>
124+
<ConfigMenu className={s.configMenuTopRight}>
125125
<MenuRow title={"Value"}>
126126
<HexValueEditor inputType={comp.args.valueMode} value={comp.args.value} update={editValue} />
127127
</MenuRow>
@@ -134,20 +134,33 @@ export const InputEditor: React.FC<{
134134

135135

136136
export const MenuRow: React.FC<{
137-
title: string,
137+
title: React.ReactNode,
138138
children?: React.ReactNode,
139-
}> = ({ title, children }) => {
139+
disabled?: boolean,
140+
}> = ({ title, children, disabled }) => {
140141

141-
return <div className={s.menuRow}>
142-
<div className={s.title}>{title}</div>
143-
<div className={s.content}>{children}</div>
142+
return <div className={clsx("flex flex-col mx-4 my-2", disabled && "opacity-50")}>
143+
<div className={"text-sm"}>{title}</div>
144+
<div className={""}>{children}</div>
144145
</div>
145146
};
146147

148+
export const CheckboxMenuTitle: React.FC<{
149+
title: React.ReactNode,
150+
value: boolean,
151+
update: (end: boolean, value: boolean) => void,
152+
}> = ({ title, value, update }) => {
153+
154+
return <label className="text-sm flex items-center group cursor-pointer">
155+
<input type="checkbox" className="mr-2 relative group-hover:drop-shadow" checked={value} onChange={e => update(true, e.target.checked)} />
156+
{title}
157+
</label>;
158+
};
159+
147160
export const ConfigMenu: React.FC<{
148-
btnClassName?: string,
161+
className?: string,
149162
children?: React.ReactNode,
150-
}> = ({ btnClassName: className, children }) => {
163+
}> = ({ className, children }) => {
151164

152165
let [btnRef, setBtnRef] = useState<HTMLElement | null>(null);
153166

@@ -157,7 +170,13 @@ export const ConfigMenu: React.FC<{
157170
<button className={clsx(s.configMenuBtn, className)} ref={setBtnRef} onClick={() => setVisible(true)}>
158171
<FontAwesomeIcon icon={faCog} />
159172
</button>
160-
{visible && <Popup targetEl={btnRef} placement={PopupPos.BottomLeft} className={s.compPopup} onClose={() => setVisible(false)} closeBackdrop={true}>
173+
{visible && <Popup
174+
targetEl={btnRef}
175+
placement={PopupPos.BottomLeft}
176+
className={"tex-lg shadow-lg border-gray-700 bg-gray-400 rounded"}
177+
onClose={() => setVisible(false)}
178+
closeBackdrop={true}>
179+
161180
{children}
162181
</Popup>}
163182
</>;

src/cpu/comps/builtins.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createAddressingComps } from "./Addressing";
1010
import { createInputOutputComps } from "./InputOutput";
1111
import { createLedOutputComps } from "./peripheral/LedOutputSimple";
1212
import { createRegFileCtrlComps } from "./riscv/RegisterControl";
13+
import { createCompIoComps } from "./CompPort";
1314

1415
export function buildCompLibrary() {
1516
let compLibrary = new CompLibrary();
@@ -28,6 +29,7 @@ export function buildCompLibrary() {
2829
...createInputOutputComps(args),
2930
...createLedOutputComps(args),
3031
...createRegFileCtrlComps(args),
32+
...createCompIoComps(args),
3133
];
3234

3335
for (let comp of comps) {
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import clsx from 'clsx';
2+
import React from 'react';
3+
4+
export const BooleanEditor: React.FC<{
5+
className?: string,
6+
value: boolean,
7+
update: (end: boolean, value: boolean) => void,
8+
}> = ({ className, value, update }) => {
9+
10+
function editValue(ev: React.ChangeEvent<HTMLInputElement>) {
11+
update(true, ev.target.checked);
12+
}
13+
14+
return <label className={clsx("", className)}>
15+
<input type="checkbox" className={clsx()} checked={value} onChange={editValue} />
16+
</label>;
17+
}

src/cpu/displayTools/HexValueEditor.module.scss

+9-2
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,26 @@
3535
pointer-events: auto;
3636
background-color: #fff3;
3737

38-
&:hover {
38+
&:hover:not(.readonly) {
3939
background-color: #fff3;
4040
}
4141

42+
&.minimal.readonly {
43+
background-color: transparent;
44+
}
45+
4246
&.minimal:not(:hover):not(:focus) {
4347
background-color: transparent;
4448
}
4549

4650
&:focus {
47-
background-color: #ffff;
4851
outline: none;
4952
}
5053

54+
&:focus:not(.readonly) {
55+
background-color: #ffff;
56+
}
57+
5158
&.invalid {
5259
color: $color-red;
5360
}

0 commit comments

Comments
 (0)