Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move camera from CircuitInternal to CircuitView #1291

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 0 additions & 42 deletions src/app/core/internal/impl/CircuitInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,8 @@ import {FastCircuitDiff, FastCircuitDiffBuilder} from "./FastCircuitDiff
export type InternalEvent = {
type: "CircuitOp";
diff: FastCircuitDiff;
} | {
type: "CameraOp";
diff: {
dx: number;
dy: number;
dz: number;
};
}


// CircuitInternal is a low-level session for editing a circuit. It encapsulates the CircuitDocument and the CircuitLog
// to provide a transaction mechanism for building LogEntries. It also provides a higher-level interface for producing
// legal CircuitOps based on the desired change and the CircuitDocument state.
Expand Down Expand Up @@ -54,7 +46,6 @@ export class CircuitInternal extends Observable<InternalEvent> {
private diffBuilder: FastCircuitDiffBuilder;

// Schemas
protected camera: Schema.Camera;
protected metadata: Schema.CircuitMetadata;

public constructor(log: CircuitLog, doc: CircuitDocument) {
Expand All @@ -69,12 +60,6 @@ export class CircuitInternal extends Observable<InternalEvent> {

this.diffBuilder = new FastCircuitDiffBuilder();

this.camera = {
x: 0,
y: 0,
zoom: 0.02,
};

this.metadata = { id: "", name: "", desc: "", thumb: "", version: "type/v0" };

this.log.subscribe((evt) => {
Expand Down Expand Up @@ -307,33 +292,6 @@ export class CircuitInternal extends Observable<InternalEvent> {
this.metadata = { ...this.metadata, ...newMetadata } as Schema.CircuitMetadata;
}

//
// Revisit where this should go
//

public getCamera(): Readonly<Schema.Camera> {
return this.camera;
}

public setCameraProps(props: Partial<Schema.Camera>) {
const dx = (props.x ?? this.camera.x) - this.camera.x;
const dy = (props.y ?? this.camera.y) - this.camera.y;
const dz = (props.zoom ?? this.camera.zoom) - this.camera.zoom;

// No change, no need to emit event
if (dx === 0 && dy === 0 && dz === 0)
return;

this.camera.x = (props.x ?? this.camera.x);
this.camera.y = (props.y ?? this.camera.y);
this.camera.zoom = (props.zoom ?? this.camera.zoom);

this.publish({
type: "CameraOp",
diff: { dx, dy, dz },
});
}

public getMetadata(): Readonly<Schema.CircuitMetadata> {
return this.metadata;
}
Expand Down
59 changes: 49 additions & 10 deletions src/app/core/internal/view/CircuitView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {Matrix2x3} from "math/Matrix";
import {Transform} from "math/Transform";

import {None,Option,Some} from "core/utils/Result";
import {Observable} from "core/utils/Observable";
import {Observable, MultiObservable} from "core/utils/Observable";

import {Schema} from "core/schema";

import {GUID} from "..";
import {CircuitInternal} from "../impl/CircuitInternal";
Expand All @@ -23,6 +25,17 @@ import {DefaultRenderOptions, RenderOptions} from "./rendering/RenderOptions";
import {RenderScheduler} from "./rendering/RenderScheduler";


export type CircuitViewEvents = {
"onrender": {
renderer: RenderHelper;
};
"oncamerachange": {
dx: number;
dy: number;
dz: number;
};
}

/**
* Utility class to manage assets for the circuit view.
*
Expand Down Expand Up @@ -51,7 +64,7 @@ export class CircuitViewAssetManager<T> extends Observable<{ key: string, val: T
}
}

export abstract class CircuitView extends Observable<{ renderer: RenderHelper }> {
export abstract class CircuitView extends MultiObservable<CircuitViewEvents> {
public readonly circuit: CircuitInternal;
public readonly selections: SelectionsManager;

Expand All @@ -60,6 +73,8 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
public readonly scheduler: RenderScheduler;
public readonly renderer: RenderHelper;

// SoT for the camera, since it's tied to the view
public camera: Schema.Camera;
public cameraMat: Matrix2x3;

public componentTransforms: Map<GUID, Transform>;
Expand Down Expand Up @@ -89,6 +104,11 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
this.scheduler = new RenderScheduler();
this.renderer = new RenderHelper();

this.camera = {
x: 0,
y: 0,
zoom: 0.02,
};
this.cameraMat = this.calcCameraMat();

this.componentTransforms = new Map();
Expand All @@ -111,11 +131,6 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
this.scheduler.setBlocked(true);

this.circuit.subscribe((ev) => {
if (ev.type === "CameraOp") {
this.cameraMat = this.calcCameraMat();
this.scheduler.requestRender();
return;
}
// TODO[model_refactor_api](leon) - use events better, i.e. how do we collect the diffs until the next
// render cycle or query for the dirty object(s)?
const diff = ev.diff;
Expand Down Expand Up @@ -216,9 +231,9 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
}

protected calcCameraMat() {
const camera = this.circuit.getCamera();
const { x, y, zoom } = this.camera;

return new Matrix2x3(V(camera.x, camera.y), 0, V(camera.zoom, -camera.zoom));
return new Matrix2x3(V(x, y), 0, V(zoom, -zoom));
}

public toWorldPos(pos: Vector): Vector {
Expand Down Expand Up @@ -311,7 +326,7 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
// Debug rendering

// Callback for post-rendering
this.publish({ renderer: this.renderer });
this.publish("onrender", { renderer: this.renderer });
this.renderer.restore();
}

Expand All @@ -327,6 +342,30 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
this.scheduler.requestRender();
}

public setCameraProps(props: Partial<Schema.Camera>) {
const dx = (props.x ?? this.camera.x) - this.camera.x;
const dy = (props.y ?? this.camera.y) - this.camera.y;
const dz = (props.zoom ?? this.camera.zoom) - this.camera.zoom;

// No change, no need to emit event
if (dx === 0 && dy === 0 && dz === 0)
return;

this.camera.x = (props.x ?? this.camera.x);
this.camera.y = (props.y ?? this.camera.y);
this.camera.zoom = (props.zoom ?? this.camera.zoom);

// Update camera matrix and request re-render
this.cameraMat = this.calcCameraMat();
this.scheduler.requestRender();

this.publish("oncamerachange", { dx, dy, dz });
}

public getCamera(): Readonly<Schema.Camera> {
return this.camera;
}

public setCanvas(canvas?: HTMLCanvasElement) {
// Unblock scheduler once a canvas is set
this.scheduler.setBlocked(false);
Expand Down
4 changes: 2 additions & 2 deletions src/app/core/internal/view/rendering/RenderState.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {Schema} from "core/schema";
import {CircuitInternal} from "core/internal/impl/CircuitInternal";
import {SelectionsManager} from "core/internal/impl/SelectionsManager";
import {Matrix2x3} from "math/Matrix";
import {RenderHelper} from "./RenderHelper";
import {RenderOptions} from "./RenderOptions";


export interface RenderState {
circuit: CircuitInternal;
selections: SelectionsManager;
cameraMat: Matrix2x3;
camera: Schema.Camera;
renderer: RenderHelper;
options: RenderOptions;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import {V, Vector} from "Vector";
import {RenderState} from "../RenderState";


export function RenderGrid({ circuit, renderer, options }: RenderState) {
const camera = circuit.getCamera();

export function RenderGrid({ camera, renderer, options }: RenderState) {
const step = options.gridSize;

const size = renderer.size;
Expand Down
18 changes: 8 additions & 10 deletions src/app/core/public/api/impl/Camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,39 @@ import {CircuitState, CircuitTypes} from "./CircuitState";
import {ObservableImpl} from "./Observable";


export function CameraImpl<T extends CircuitTypes>({ internal, view }: CircuitState<T>) {
export function CameraImpl<T extends CircuitTypes>({ view }: CircuitState<T>) {
function camera() {
return internal.getCamera();
return view.getCamera();
}

const observable = ObservableImpl<CameraEvent>();

internal.subscribe((ev) => {
if (ev.type !== "CameraOp")
return;
observable.emit({ type: "change", ...ev.diff });
view.subscribe("oncamerachange", (ev) => {
observable.emit({ type: "change", ...ev });
});

return extend(observable, {
set cx(x: number) {
internal.setCameraProps({ x });
view.setCameraProps({ x });
},
get cx(): number {
return camera().x;
},
set cy(y: number) {
internal.setCameraProps({ y });
view.setCameraProps({ y });
},
get cy(): number {
return camera().y;
},
set pos({ x, y }: Vector) {
internal.setCameraProps({ x, y });
view.setCameraProps({ x, y });
},
get pos(): Vector {
return V(this.cx, this.cy);
},

set zoom(zoom: number) {
internal.setCameraProps({ zoom });
view.setCameraProps({ zoom });
},
get zoom(): number {
return camera().zoom;
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/public/api/impl/Circuit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export function CircuitImpl<CircuitT extends Circuit, T extends CircuitTypes>(st
renderer: RenderHelper;
options: RenderOptions;
}) => void): CleanupFunc {
return state.view.subscribe(({ renderer }) => cb({
return state.view.subscribe("onrender", ({ renderer }) => cb({
renderer,
options: state.view.options,
}));
Expand Down
59 changes: 58 additions & 1 deletion src/app/core/utils/Observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export abstract class Observable<Event = unknown> {

protected constructor() {
this.blocked = false;
this.callbacks = new Set<(data: Event) => void>();
this.callbacks = new Set();
}

protected publish(data: Event) {
Expand Down Expand Up @@ -49,3 +49,60 @@ export abstract class Observable<Event = unknown> {
this.callbacks.delete(callbackFn);
}
}


export abstract class MultiObservable<Events extends Record<string, unknown>> {
protected blocked: boolean;
protected callbacks: { [K in keyof Events]?: Set<(data: Events[K]) => void> };

protected constructor() {
this.blocked = false;
this.callbacks = {};
}

protected publish<K extends keyof Events>(ev: K, data: Events[K]) {
// Blocked so don't trigger callbacks
if (this.blocked)
return;

// Shallow copy in case the callbacks try to sub/unsub while iterating
[...(this.callbacks[ev] ?? [])].forEach((c) => c(data));
}

protected unsubscribeAll<K extends keyof Events>(ev: K) {
this.callbacks[ev]?.clear();
}

public setBlocked(blocked: boolean): void {
this.blocked = blocked;
}

/**
* Sets blocked to true, prevents Callbacks from getting Events.
*/
public block(): void {
this.blocked = true;
}

/**
* Sets blocked to false, allows Callbacks to get Events again.
*/
public unblock(): void {
this.blocked = false;
}

public subscribe<K extends keyof Events>(ev: K, callbackFn: (data: Events[K]) => void) {
if (!(ev in this.callbacks))
this.callbacks[ev] = new Set();
this.callbacks[ev]!.add(callbackFn);

// Return a callback to unsubscribe
return () => this.unsubscribe(ev, callbackFn);
}

public unsubscribe<K extends keyof Events>(ev: K, callbackFn: (data: Events[K]) => void) {
if (!(ev in this.callbacks))
return;
this.callbacks[ev]!.delete(callbackFn);
}
}