Compare commits
No commits in common. "cc1ef05b95d870dfca723da9e77a91510b6d967c" and "54e82d4a8da5a3fe543b4f5746dc723340a7b7c4" have entirely different histories.
cc1ef05b95
...
54e82d4a8d
716
editor/package-lock.json
generated
716
editor/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -41,10 +41,6 @@ function Canvas({ editor, canvasRef, width, height }: Props): ReactElement {
|
|||||||
editor.mouseMove(deltaPos, pos);
|
editor.mouseMove(deltaPos, pos);
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(ev) => {
|
|
||||||
editor.mouseLeave();
|
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
|
||||||
}}
|
|
||||||
onKeyDown={(ev) => {
|
onKeyDown={(ev) => {
|
||||||
editor.keyDown(ev.key);
|
editor.keyDown(ev.key);
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||||
|
|||||||
@ -9,9 +9,6 @@ import { Renderer } from "./Renderer";
|
|||||||
import * as states from "./states";
|
import * as states from "./states";
|
||||||
import { v2, V2 } from "./V2";
|
import { v2, V2 } from "./V2";
|
||||||
import * as ir from "./ir";
|
import * as ir from "./ir";
|
||||||
import { Sim } from "./sim";
|
|
||||||
import { EventBus } from "./events";
|
|
||||||
import { Mouse } from "./Mouse";
|
|
||||||
|
|
||||||
export type Tool = string;
|
export type Tool = string;
|
||||||
|
|
||||||
@ -31,9 +28,6 @@ export class Cx {
|
|||||||
|
|
||||||
public keysPressed = new Set<string>();
|
public keysPressed = new Set<string>();
|
||||||
|
|
||||||
public eventBus = new EventBus();
|
|
||||||
public mouse = new Mouse(this.eventBus);
|
|
||||||
|
|
||||||
render(canvas: HTMLCanvasElement) {
|
render(canvas: HTMLCanvasElement) {
|
||||||
const r = new Renderer(canvas, this.offset);
|
const r = new Renderer(canvas, this.offset);
|
||||||
|
|
||||||
@ -108,7 +102,7 @@ export class Cx {
|
|||||||
transitionTo(newState: states.State) {
|
transitionTo(newState: states.State) {
|
||||||
this.state.leaveState?.();
|
this.state.leaveState?.();
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
// console.log(`Entering state ${newState.constructor.name}`);
|
console.log(`Entering state ${newState.constructor.name}`);
|
||||||
this.state.enterState?.();
|
this.state.enterState?.();
|
||||||
this.notifyListeners();
|
this.notifyListeners();
|
||||||
}
|
}
|
||||||
@ -145,14 +139,14 @@ export class Cx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runSimulation() {
|
runSimulation() {
|
||||||
// const comp = this.board.toIr();
|
const comp = this.board.toIr();
|
||||||
// console.log("Before optimizing");
|
console.log("Before optimizing");
|
||||||
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||||
// new ir.ComponentOptimizer(comp).optimize();
|
|
||||||
// console.log("After optimizing");
|
new ir.ComponentOptimizer(comp).optimize();
|
||||||
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
|
||||||
// const sim = new Sim(comp, [], []);
|
console.log("After optimizing");
|
||||||
// sim.simulate();
|
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,24 +13,17 @@ export class Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mouseDown(pos: V2) {
|
mouseDown(pos: V2) {
|
||||||
this.cx.eventBus.send({ tag: "MouseDown", pos });
|
|
||||||
this.cx.mouseDown(pos);
|
this.cx.mouseDown(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseUp(pos: V2) {
|
mouseUp(pos: V2) {
|
||||||
this.cx.eventBus.send({ tag: "MouseUp", pos });
|
|
||||||
this.cx.mouseUp(pos);
|
this.cx.mouseUp(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseMove(deltaPos: V2, pos: V2) {
|
mouseMove(deltaPos: V2, pos: V2) {
|
||||||
this.cx.eventBus.send({ tag: "MouseMove", pos, deltaPos });
|
|
||||||
this.cx.mouseMove(deltaPos, pos);
|
this.cx.mouseMove(deltaPos, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseLeave() {
|
|
||||||
this.cx.eventBus.send({ tag: "MouseLeave" });
|
|
||||||
}
|
|
||||||
|
|
||||||
keyDown(key: string) {
|
keyDown(key: string) {
|
||||||
this.cx.keyDown(key);
|
this.cx.keyDown(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,229 +0,0 @@
|
|||||||
import type { EventBus, EventUnsub } from "./events";
|
|
||||||
import { v2, type V2 } from "./V2";
|
|
||||||
|
|
||||||
const doubleClickDelay = 200;
|
|
||||||
|
|
||||||
export class Mouse {
|
|
||||||
private state: State;
|
|
||||||
|
|
||||||
constructor(public eventBus: EventBus) {
|
|
||||||
this.state = new Normal(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
transitionTo(state: State) {
|
|
||||||
this.state.leave();
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
leave(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Normal implements State {
|
|
||||||
private unsubscribe: EventUnsub;
|
|
||||||
|
|
||||||
constructor(private cx: Mouse) {
|
|
||||||
console.log("Mouse::Normal");
|
|
||||||
|
|
||||||
this.unsubscribe = cx.eventBus.subscribe(
|
|
||||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
|
||||||
(ev) => {
|
|
||||||
switch (ev.tag) {
|
|
||||||
case "MouseDown":
|
|
||||||
this.cx.transitionTo(new FirstPress(this.cx, ev.pos));
|
|
||||||
break;
|
|
||||||
case "MouseUp":
|
|
||||||
break;
|
|
||||||
case "MouseMove":
|
|
||||||
break;
|
|
||||||
case "MouseLeave":
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("invalid state");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
leave(): void {
|
|
||||||
this.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FirstPress implements State {
|
|
||||||
private unsubscribe: EventUnsub;
|
|
||||||
|
|
||||||
private time = Date.now();
|
|
||||||
private totalDelta = v2(0, 0);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private cx: Mouse,
|
|
||||||
private pos: V2,
|
|
||||||
) {
|
|
||||||
console.log("Mouse::FirstPress");
|
|
||||||
|
|
||||||
this.unsubscribe = cx.eventBus.subscribe(
|
|
||||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
|
||||||
(ev) => {
|
|
||||||
switch (ev.tag) {
|
|
||||||
case "MouseUp": {
|
|
||||||
if (Date.now() - this.time < doubleClickDelay) {
|
|
||||||
this.cx.transitionTo(new FirstRelease(this.cx, this.pos));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.cx.eventBus.send({ tag: "MouseClick", pos: ev.pos });
|
|
||||||
this.cx.transitionTo(new Normal(this.cx));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "MouseMove": {
|
|
||||||
this.totalDelta = this.totalDelta.add(ev.deltaPos);
|
|
||||||
if (this.totalDelta.len() > 5) {
|
|
||||||
this.cx.eventBus.send({
|
|
||||||
tag: "MouseDragBegin",
|
|
||||||
pos: this.pos,
|
|
||||||
deltaPos: this.totalDelta,
|
|
||||||
});
|
|
||||||
this.cx.transitionTo(new Dragging(this.cx));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "MouseLeave":
|
|
||||||
this.cx.transitionTo(new Normal(this.cx));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`invalid state, unexpected ${ev.tag}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
leave(): void {
|
|
||||||
this.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FirstRelease implements State {
|
|
||||||
private unsubscribe: EventUnsub;
|
|
||||||
|
|
||||||
private timeout: ReturnType<typeof setTimeout>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private cx: Mouse,
|
|
||||||
private pos: V2,
|
|
||||||
) {
|
|
||||||
console.log("Mouse::FirstRelease");
|
|
||||||
|
|
||||||
this.unsubscribe = cx.eventBus.subscribe(
|
|
||||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
|
||||||
(ev) => {
|
|
||||||
switch (ev.tag) {
|
|
||||||
case "MouseDown":
|
|
||||||
this.cx.transitionTo(new SecondPress(this.cx, this.pos));
|
|
||||||
break;
|
|
||||||
case "MouseUp":
|
|
||||||
break;
|
|
||||||
case "MouseMove":
|
|
||||||
break;
|
|
||||||
case "MouseLeave":
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("invalid state");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.cx.transitionTo(new Normal(this.cx));
|
|
||||||
}, doubleClickDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
leave(): void {
|
|
||||||
this.unsubscribe();
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SecondPress implements State {
|
|
||||||
private unsubscribe: EventUnsub;
|
|
||||||
|
|
||||||
private totalDelta = v2(0, 0);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private cx: Mouse,
|
|
||||||
private pos: V2,
|
|
||||||
) {
|
|
||||||
console.log("Mouse::SecondPress");
|
|
||||||
|
|
||||||
this.unsubscribe = cx.eventBus.subscribe(
|
|
||||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
|
||||||
(ev) => {
|
|
||||||
switch (ev.tag) {
|
|
||||||
case "MouseUp":
|
|
||||||
this.cx.eventBus.send({ tag: "MouseDoubleClick", pos: this.pos });
|
|
||||||
this.cx.transitionTo(new Normal(this.cx));
|
|
||||||
break;
|
|
||||||
case "MouseMove":
|
|
||||||
this.totalDelta = this.totalDelta.add(ev.deltaPos);
|
|
||||||
if (this.totalDelta.len() > 5) {
|
|
||||||
this.cx.eventBus.send({
|
|
||||||
tag: "MouseDragBegin",
|
|
||||||
pos: this.pos,
|
|
||||||
deltaPos: this.totalDelta,
|
|
||||||
});
|
|
||||||
this.cx.transitionTo(new Dragging(this.cx));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "MouseLeave":
|
|
||||||
this.cx.transitionTo(new Normal(this.cx));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("invalid state");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
leave(): void {
|
|
||||||
this.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Dragging implements State {
|
|
||||||
private unsubscribe: EventUnsub;
|
|
||||||
|
|
||||||
constructor(private cx: Mouse) {
|
|
||||||
console.log("Mouse::Dragging");
|
|
||||||
|
|
||||||
this.unsubscribe = cx.eventBus.subscribe(
|
|
||||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
|
||||||
(ev) => {
|
|
||||||
switch (ev.tag) {
|
|
||||||
case "MouseDown":
|
|
||||||
this.cx.transitionTo(new FirstPress(this.cx, ev.pos));
|
|
||||||
break;
|
|
||||||
case "MouseUp":
|
|
||||||
this.cx.transitionTo(new Normal(this.cx));
|
|
||||||
break;
|
|
||||||
case "MouseMove":
|
|
||||||
this.cx.eventBus.send({
|
|
||||||
tag: "MouseDrag",
|
|
||||||
pos: ev.pos,
|
|
||||||
deltaPos: ev.deltaPos,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "MouseLeave":
|
|
||||||
this.cx.transitionTo(new Normal(this.cx));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("invalid state");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
leave(): void {
|
|
||||||
this.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
import type { V2 } from "./V2";
|
|
||||||
|
|
||||||
export type Event =
|
|
||||||
| { tag: "MouseDown" | "MouseUp"; pos: V2 }
|
|
||||||
| { tag: "MouseMove"; pos: V2; deltaPos: V2 }
|
|
||||||
| { tag: "MouseLeave" }
|
|
||||||
| { tag: "MouseClick" | "MouseDoubleClick"; pos: V2 }
|
|
||||||
| {
|
|
||||||
tag:
|
|
||||||
| "MouseDragBegin"
|
|
||||||
| "MouseDrag"
|
|
||||||
| "MouseDoubleDragBegin"
|
|
||||||
| "MouseDoubleDrag";
|
|
||||||
pos: V2;
|
|
||||||
deltaPos: V2;
|
|
||||||
}
|
|
||||||
| { tag: "MouseDrag" };
|
|
||||||
|
|
||||||
export type EventOf<Tag extends Event["tag"]> = Event & { tag: Tag };
|
|
||||||
|
|
||||||
export type EventUnsub = () => void;
|
|
||||||
export type EventAction = (event: Event) => void;
|
|
||||||
|
|
||||||
export class EventBus {
|
|
||||||
private eventActionsMap = new Map<Event["tag"], Set<EventAction>>();
|
|
||||||
private actionEventsMap = new Map<EventAction, Event["tag"][]>();
|
|
||||||
|
|
||||||
subscribe<Tags extends Event["tag"]>(
|
|
||||||
tags: Tags[],
|
|
||||||
action: (event: EventOf<Tags>) => void,
|
|
||||||
): EventUnsub {
|
|
||||||
this.addSubscriber<Tags>(tags, action as EventAction);
|
|
||||||
return () => {
|
|
||||||
this.removeSubscriber(action as EventAction);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private addSubscriber<Tags extends Event["tag"]>(
|
|
||||||
tags: Tags[],
|
|
||||||
action: EventAction,
|
|
||||||
) {
|
|
||||||
if (this.actionEventsMap.has(action)) {
|
|
||||||
throw new Error("action was added twice without cleanup");
|
|
||||||
}
|
|
||||||
this.actionEventsMap.set(action, tags);
|
|
||||||
|
|
||||||
for (const tag of tags) {
|
|
||||||
if (!this.eventActionsMap.has(tag)) {
|
|
||||||
this.eventActionsMap.set(tag, new Set());
|
|
||||||
}
|
|
||||||
this.eventActionsMap.get(tag)!.add(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeSubscriber(action: EventAction) {
|
|
||||||
if (!this.actionEventsMap.has(action)) {
|
|
||||||
throw new Error("action was already cleaned up");
|
|
||||||
}
|
|
||||||
for (const tag of this.actionEventsMap.get(action)!) {
|
|
||||||
this.eventActionsMap.get(tag)?.delete(action);
|
|
||||||
}
|
|
||||||
this.actionEventsMap.delete(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
send(event: Event) {
|
|
||||||
const actionSet = this.eventActionsMap.get(event.tag);
|
|
||||||
if (!actionSet) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const actions = [...actionSet];
|
|
||||||
for (const action of actions) {
|
|
||||||
if (!this.actionEventsMap.has(action)) {
|
|
||||||
// has been unsubscribed by prior action
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
action(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
import * as ir from "./ir";
|
|
||||||
|
|
||||||
export class Sim {
|
|
||||||
constructor(
|
|
||||||
private comp: ir.Component,
|
|
||||||
private inputs: boolean[],
|
|
||||||
private outputs: boolean[],
|
|
||||||
) {}
|
|
||||||
|
|
||||||
simulate() {
|
|
||||||
const { comp, inputs, outputs } = this;
|
|
||||||
|
|
||||||
const stmtIdcs = new Map(comp.stmts.map((stmt, i) => [stmt, i]));
|
|
||||||
const state = new Map(comp.states.map((state) => [state, false]));
|
|
||||||
|
|
||||||
const regs = new Array<boolean>(comp.stmts.length).fill(false);
|
|
||||||
|
|
||||||
const operation = <Ops extends ir.Stmt[]>(
|
|
||||||
action: (...ops: boolean[]) => boolean,
|
|
||||||
...ops: Ops
|
|
||||||
) => action(...ops.map((op) => regs[stmtIdcs.get(op)!]));
|
|
||||||
|
|
||||||
for (const [i, stmt] of comp.stmts.entries()) {
|
|
||||||
const k = stmt.kind;
|
|
||||||
switch (k.tag) {
|
|
||||||
case "Null":
|
|
||||||
regs[i] = false;
|
|
||||||
break;
|
|
||||||
case "Input":
|
|
||||||
regs[i] = inputs[k.i];
|
|
||||||
break;
|
|
||||||
case "Output":
|
|
||||||
outputs[k.i] = regs[i];
|
|
||||||
break;
|
|
||||||
case "GetState":
|
|
||||||
regs[i] = state.get(k.state)!;
|
|
||||||
break;
|
|
||||||
case "SetState":
|
|
||||||
state.set(k.state, regs[i]);
|
|
||||||
break;
|
|
||||||
case "Not":
|
|
||||||
regs[i] = operation((v) => !v, k.op);
|
|
||||||
break;
|
|
||||||
case "And":
|
|
||||||
regs[i] = operation((a, b) => a && b, k.lhs, k.rhs);
|
|
||||||
break;
|
|
||||||
case "Or":
|
|
||||||
regs[i] = operation((a, b) => a || b, k.lhs, k.rhs);
|
|
||||||
break;
|
|
||||||
case "Component":
|
|
||||||
throw new Error("not implemented");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user