Skip to content

Commit

Permalink
Finished rendering implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
newcat committed Mar 24, 2024
1 parent 3aace7e commit c3afb51
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 53 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
"chroma-js": "^2.4.2",
"codemirror": "^6.0.1",
"comlink": "^4.4.1",
"fflate": "^0.8.2",
"fft.js": "^4.0.4",
"jszip": "^3.10.1",
"msgpackr": "^1.10.1",
"open-simplex-noise": "^2.5.0",
"pinia": "^2.1.7",
"primeicons": "^6.0.1",
Expand Down
58 changes: 54 additions & 4 deletions src/components/RenderDialog.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<Dialog :visible="showDialog" :style="{ width: '50vw' }" header="Rendering" modal>
<ProgressBar :value="progress" />
<ProgressBar class="render-bar" :value="progress" />

<template #footer>
<Button @click="cancel">Cancel</Button>
Expand All @@ -9,7 +9,10 @@
</template>

<script setup lang="ts">
import { nextTick, ref } from "vue";
import { ref } from "vue";
import { pack } from "msgpackr";
import { gzipSync } from "fflate";
import Dialog from "primevue/dialog";
import ProgressBar from "primevue/progressbar";
import Button from "primevue/button";
Expand All @@ -18,19 +21,34 @@ import { useToast } from "primevue/usetoast";
import { BaseTimelineProcessor } from "@/timeline";
import { useGlobalState } from "@/globalState";
import { TICKS_PER_BEAT } from "@/constants";
import { useStage } from "@/stage";
import { unitToSeconds } from "@/utils";
import { getNativeAdapter } from "@/native";
const globalState = useGlobalState();
const toast = useToast();
const stage = useStage();
const nativeAdapter = getNativeAdapter();
const showDialog = ref(false);
const cancelRequest = ref(false);
const progress = ref(0);
interface RenderResult {
timestamps: number[];
fixtureValues: Record<string, unknown[]>;
}
async function startRender() {
showDialog.value = true;
const maxUnit = globalState.timeline.items.reduce((max, item) => Math.max(max, item.end), 0);
const processor = new BaseTimelineProcessor();
progress.value = 0;
let nextFrameTime = 0;
const result: RenderResult = { timestamps: [], fixtureValues: {} };
stage.visualization.pause();
for (let unit = 0; unit <= maxUnit; unit++) {
if (cancelRequest.value) {
break;
Expand All @@ -43,19 +61,45 @@ async function startRender() {
cancelRequest.value = true;
break;
}
const secondsPerFrame = 1 / globalState.fps;
const nextTimestamp = unitToSeconds(unit + 1, globalState.bpm);
if (nextTimestamp > nextFrameTime) {
result.timestamps.push(nextFrameTime);
nextFrameTime += secondsPerFrame;
for (const [fixtureId, fixture] of stage.fixtures.entries()) {
if (!result.fixtureValues[fixtureId]) {
result.fixtureValues[fixtureId] = [];
}
result.fixtureValues[fixtureId].push(fixture.value);
}
}
progress.value = Math.floor((unit / maxUnit) * 100);
if (unit % TICKS_PER_BEAT === 0) {
await nextTick();
await new Promise((res) => requestAnimationFrame(res));
await new Promise((res) => setTimeout(res, 0));
}
}
progress.value = 100;
stage.visualization.resume();
if (cancelRequest.value) {
cancelRequest.value = false;
showDialog.value = false;
return;
}
const data = gzipSync(pack(result));
await nativeAdapter.chooseAndWriteFile(data, {
suggestedName: "render.lmr",
accept: [
{
name: "LedMusicStudio Render File",
extensions: ["lmr"],
},
],
});
showDialog.value = false;
}
Expand All @@ -65,3 +109,9 @@ function cancel() {
defineExpose({ startRender });
</script>

<style scoped>
.render-bar :deep(.p-progressbar-value-animate) {
transition: none;
}
</style>
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Tooltip from "primevue/tooltip";

async function main() {
await initializeNativeAdapter();
wasmInterop.init();
await wasmInterop.init();

const pinia = createPinia();

Expand All @@ -35,4 +35,4 @@ async function main() {
createApp(App).use(PrimeVue).use(ToastService).directive("tooltip", Tooltip).use(pinia).use(router).mount("#app");
}

main();
void main();
8 changes: 8 additions & 0 deletions src/stage/fixtures/base.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component } from "vue";
import { v4 as uuidv4 } from "uuid";
import { BaklavaEvent } from "@baklavajs/events";

export enum FixtureType {
LED_STRIP = "LED Strip",
Expand All @@ -20,6 +21,11 @@ export abstract class BaseFixture<V = unknown, C = unknown> {
public name: string = "Fixture";
public readonly settingsComponent: Component | null = null;

public readonly events = {
valueChanged: new BaklavaEvent<V, this>(this),
configChanged: new BaklavaEvent<C, this>(this),
};

protected _value: V;
protected _config: C;

Expand All @@ -40,10 +46,12 @@ export abstract class BaseFixture<V = unknown, C = unknown> {

public setValue(v: V) {
this._value = v;
this.events.valueChanged.emit(v);
}

public setConfig(c: C) {
this._config = c;
this.events.configChanged.emit(c);
}

public save(): FixtureState<V, C> {
Expand Down
35 changes: 5 additions & 30 deletions src/stage/stage.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,24 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { Ref, markRaw, ref } from "vue";
import { Ref, ref } from "vue";
import { defineStore } from "pinia";
import * as Comlink from "comlink";

import { BaseFixture, FixtureState } from "./fixtures";
import { BaseController, ControllerState } from "./controllers";
import { StageVisualization, StageVisualizationState } from "./visualization/stageVisualization";
import { createFixture } from "./fixtures/factory";
import { createController } from "./controllers/factory";
import { ExtendedMap } from "./extendedMap";

import VisualizationWorker from "./visualization/visualization.worker?worker";
import { RemoteStageRenderer } from "./visualization/stageRenderer";
import { createVisualizationWorkerInstance } from "./visualizationWorkerInstance";

export interface StageState {
fixtures: FixtureState[];
controllers: ControllerState[];
visualization: StageVisualizationState;
}

const outsideRenderer = Comlink.wrap<RemoteStageRenderer>(new VisualizationWorker());
const rendererProxy = new Proxy<typeof outsideRenderer>({} as any, {
get(target: any, prop) {
if (typeof prop === "string" && prop.startsWith("__v_")) {
return target[prop];
}
return (outsideRenderer as any)[prop];
},
set(target: any, prop, value) {
if (typeof prop === "string" && prop.startsWith("__v_")) {
target[prop] = value;
return true;
}
(outsideRenderer as any)[prop] = value;
return true;
},
});

export const useStage = defineStore("stage", () => {
const fixtures = ref<ExtendedMap<string, BaseFixture>>(new ExtendedMap()) as Ref<ExtendedMap<string, BaseFixture>>;
const controllers = ref<ExtendedMap<string, BaseController>>(new ExtendedMap()) as Ref<ExtendedMap<string, BaseController>>;
const renderer = markRaw(rendererProxy) as RemoteStageRenderer;
const renderer = createVisualizationWorkerInstance();
const visualization = ref(new StageVisualization(fixtures.value));

function afterFrame() {
Expand All @@ -60,7 +35,7 @@ export const useStage = defineStore("stage", () => {
};
}

function load(state: StageState) {
async function load(state: StageState) {
for (const fixtureState of state.fixtures) {
const fixture = createFixture(fixtureState.type);
fixture.load(fixtureState);
Expand All @@ -73,7 +48,7 @@ export const useStage = defineStore("stage", () => {
controllers.value.set(controller.id, controller);
}

visualization.value.load(state.visualization);
await visualization.value.load(state.visualization);
}

function reset() {
Expand Down
38 changes: 25 additions & 13 deletions src/stage/visualization/fixtureVisualizations/base.visualization.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, watch } from "vue";
import { Component } from "vue";

import type { RemoteStageRenderer } from "../stageRenderer";
import { BaseFixture, FixtureType } from "@/stage/fixtures";
Expand All @@ -21,29 +21,33 @@ export abstract class BaseVisualization<F extends BaseFixture = BaseFixture, C =
protected _config: C;
protected readonly renderer: RemoteStageRenderer = useStage().renderer;

private _paused = false;

public get config(): C {
return this._config;
}

protected get paused() {
return this._paused;
}

public constructor(public readonly fixture: F, initialConfig: C) {
this._config = initialConfig;

watch(
() => fixture.config,
() => this.onFixtureConfigUpdate(),
{ deep: true }
);
watch(
() => fixture.value,
() => this.onFixtureValueUpdate(),
{ deep: true }
);
fixture.events.configChanged.subscribe(this, () => this.onFixtureConfigUpdate());
fixture.events.valueChanged.subscribe(this, () => {
if (!this._paused) {
this.onFixtureValueUpdate();
}
});
}

protected abstract onFixtureConfigUpdate(): void;
protected abstract onFixtureValueUpdate(): void;

public dispose() {}
public dispose() {
this.fixture.events.configChanged.unsubscribe(this);
this.fixture.events.valueChanged.unsubscribe(this);
}

public setConfig(c: C) {
this._config = c;
Expand All @@ -59,4 +63,12 @@ export abstract class BaseVisualization<F extends BaseFixture = BaseFixture, C =
public load(state: VisualizationState<C>) {
this.setConfig(state.config);
}

public pause() {
this._paused = true;
}

public resume() {
this._paused = false;
}
}
17 changes: 13 additions & 4 deletions src/stage/visualization/stageVisualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BaseFixture } from "../fixtures";
import { useStage } from "../stage";
import { BaseVisualization, VisualizationState, VisualizationType } from "./fixtureVisualizations/base.visualization";
import { createFixtureVisualization } from "./fixtureVisualizations/factory";
import { ExtendedMap } from "../extendedMap";

export interface StageVisualizationState {
baseScene: unknown;
Expand All @@ -12,7 +13,7 @@ export interface StageVisualizationState {

export class StageVisualization {
private baseScene: unknown = null;
private _visualizations: Map<string, BaseVisualization> = new Map();
private _visualizations: ExtendedMap<string, BaseVisualization> = new ExtendedMap();

public get isSceneLoaded() {
return this.baseScene !== null;
Expand Down Expand Up @@ -68,10 +69,19 @@ export class StageVisualization {
}

public reset() {
this._visualizations.clear();
}

public pause() {
for (const visualization of this.visualizations.values()) {
visualization.dispose();
visualization.pause();
}
}

public resume() {
for (const visualization of this.visualizations.values()) {
visualization.resume();
}
this._visualizations.clear();
}

private updateFixtures() {
Expand All @@ -88,7 +98,6 @@ export class StageVisualization {
private removeVisualization(fixtureId: string) {
const visualization = this.visualizations.get(fixtureId);
if (visualization) {
visualization.dispose();
this._visualizations.delete(fixtureId);
}
}
Expand Down
30 changes: 30 additions & 0 deletions src/stage/visualizationWorkerInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */

import { markRaw } from "vue";
import * as Comlink from "comlink";
import VisualizationWorker from "./visualization/visualization.worker?worker";
import { RemoteStageRenderer } from "./visualization/stageRenderer";

export function createVisualizationWorkerInstance() {
const renderer = Comlink.wrap<RemoteStageRenderer>(new VisualizationWorker());
const rendererProxy = new Proxy<typeof renderer>({} as any, {
get(target: any, prop) {
if (typeof prop === "string" && prop.startsWith("__v_")) {
return target[prop];
}
return (renderer as any)[prop];
},
set(target: any, prop, value) {
if (typeof prop === "string" && prop.startsWith("__v_")) {
target[prop] = value;
return true;
}
(renderer as any)[prop] = value;
return true;
},
});
return markRaw(rendererProxy) as RemoteStageRenderer;
}
Loading

0 comments on commit c3afb51

Please sign in to comment.