Skip to content

Commit

Permalink
Move camera from circuitinternal to circuitview
Browse files Browse the repository at this point in the history
  • Loading branch information
LeonMontealegre committed Feb 14, 2024
1 parent 8b5d48b commit fa211dd
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 69 deletions.
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);
}
}

0 comments on commit fa211dd

Please sign in to comment.