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.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onMouseLeave={(ev) => {
|
||||
editor.mouseLeave();
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onKeyDown={(ev) => {
|
||||
editor.keyDown(ev.key);
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
|
||||
@ -9,9 +9,6 @@ import { Renderer } from "./Renderer";
|
||||
import * as states from "./states";
|
||||
import { v2, V2 } from "./V2";
|
||||
import * as ir from "./ir";
|
||||
import { Sim } from "./sim";
|
||||
import { EventBus } from "./events";
|
||||
import { Mouse } from "./Mouse";
|
||||
|
||||
export type Tool = string;
|
||||
|
||||
@ -31,9 +28,6 @@ export class Cx {
|
||||
|
||||
public keysPressed = new Set<string>();
|
||||
|
||||
public eventBus = new EventBus();
|
||||
public mouse = new Mouse(this.eventBus);
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
const r = new Renderer(canvas, this.offset);
|
||||
|
||||
@ -108,7 +102,7 @@ export class Cx {
|
||||
transitionTo(newState: states.State) {
|
||||
this.state.leaveState?.();
|
||||
this.state = newState;
|
||||
// console.log(`Entering state ${newState.constructor.name}`);
|
||||
console.log(`Entering state ${newState.constructor.name}`);
|
||||
this.state.enterState?.();
|
||||
this.notifyListeners();
|
||||
}
|
||||
@ -145,14 +139,14 @@ export class Cx {
|
||||
}
|
||||
|
||||
runSimulation() {
|
||||
// const comp = this.board.toIr();
|
||||
// console.log("Before optimizing");
|
||||
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||
// new ir.ComponentOptimizer(comp).optimize();
|
||||
// console.log("After optimizing");
|
||||
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||
// const sim = new Sim(comp, [], []);
|
||||
// sim.simulate();
|
||||
const comp = this.board.toIr();
|
||||
console.log("Before optimizing");
|
||||
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||
|
||||
new ir.ComponentOptimizer(comp).optimize();
|
||||
|
||||
console.log("After optimizing");
|
||||
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,24 +13,17 @@ export class Editor {
|
||||
}
|
||||
|
||||
mouseDown(pos: V2) {
|
||||
this.cx.eventBus.send({ tag: "MouseDown", pos });
|
||||
this.cx.mouseDown(pos);
|
||||
}
|
||||
|
||||
mouseUp(pos: V2) {
|
||||
this.cx.eventBus.send({ tag: "MouseUp", pos });
|
||||
this.cx.mouseUp(pos);
|
||||
}
|
||||
|
||||
mouseMove(deltaPos: V2, pos: V2) {
|
||||
this.cx.eventBus.send({ tag: "MouseMove", pos, deltaPos });
|
||||
this.cx.mouseMove(deltaPos, pos);
|
||||
}
|
||||
|
||||
mouseLeave() {
|
||||
this.cx.eventBus.send({ tag: "MouseLeave" });
|
||||
}
|
||||
|
||||
keyDown(key: string) {
|
||||
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