From 8a4e5d32b1e55f054bf3e98ef54c49f66655c034 Mon Sep 17 00:00:00 2001 From: aarthificial Date: Wed, 30 Mar 2022 02:44:11 +0200 Subject: [PATCH] feat: renderer ui --- public/index.html | 1 + src/Renderer.ts | 38 ------------------ src/components/Sprite.ts | 3 -- src/player/Controls.ts | 83 +++++++++++++++++++++++++++------------ src/player/Player.ts | 47 +++++++++++++++++----- tools/utils/load-image.js | 1 - 6 files changed, 97 insertions(+), 76 deletions(-) delete mode 100644 src/Renderer.ts diff --git a/public/index.html b/public/index.html index 3f4514c2..2be2bd17 100644 --- a/public/index.html +++ b/public/index.html @@ -108,6 +108,7 @@ />
Loading...
+ diff --git a/src/Renderer.ts b/src/Renderer.ts deleted file mode 100644 index 9b2b5e71..00000000 --- a/src/Renderer.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type {Project} from './Project'; - -export const Renderer = (factory: () => Project) => { - document.addEventListener('click', () => - render(factory()).catch(console.error), - ); -}; - -async function render(project: Project) { - let totalSize = 0; - const startTime = Date.now(); - - project.start(); - const directory = await window.showDirectoryPicker(); - - while (!(await project.next())) { - project.draw(); - const name = project.frame.toString().padStart(6, '0'); - const content = await new Promise(resolve => project.toCanvas().toBlob(resolve, 'image/png')); - const size = (content.size) / 1024; - totalSize += size; - - const file = await directory.getFileHandle(`frame-${name}.png`, { - create: true, - }); - const stream = await file.createWritable(); - await stream.write(content); - await stream.close(); - - console.log( - `Frame: ${name}, Size: ${Math.round(size)} kB, Total: ${Math.round( - totalSize, - )} kB, Elapsed: ${Math.round((Date.now() - startTime) / 1000)}`, - ); - - await new Promise(resolve => setTimeout(resolve, 0)); - } -} diff --git a/src/components/Sprite.ts b/src/components/Sprite.ts index 9da7d883..ca4c12b1 100644 --- a/src/components/Sprite.ts +++ b/src/components/Sprite.ts @@ -5,11 +5,9 @@ import {LayoutShape, LayoutShapeConfig} from './LayoutShape'; import {cancel, TimeTween, waitFor} from '../animations'; import {AnimatedGetSet, getset, KonvaNode, threadable} from '../decorators'; import {GeneratorHelper} from '../helpers'; -import {ImageData} from 'canvas'; export interface SpriteData { fileName: string; - url: string; data: number[]; width: number; height: number; @@ -46,7 +44,6 @@ export class Sprite extends LayoutShape { private frame: SpriteData = { height: 0, width: 0, - url: '', data: [], fileName: '', }; diff --git a/src/player/Controls.ts b/src/player/Controls.ts index a6441dcc..448954dc 100644 --- a/src/player/Controls.ts +++ b/src/player/Controls.ts @@ -8,23 +8,16 @@ export class Controls { private loadingIndicator: HTMLElement; private stepRequested: boolean = false; private resetRequested: boolean = false; + private rendering: boolean = false; private lastUpdate: number = 0; private updateTimes: number[] = []; private overallTime: number = 0; + private directory: FileSystemDirectoryHandle; public set loading(value: boolean) { this.loadingIndicator.hidden = !value; } - public get isPlaying(): boolean { - if (this.stepRequested) { - this.stepRequested = false; - return true; - } - - return this.play.checked; - } - public get isLooping(): boolean { return this.loop.checked; } @@ -33,19 +26,14 @@ export class Controls { return parseInt(this.from.value); } - public get shouldReset(): boolean { - if (this.resetRequested) { - this.resetRequested = false; - return true; - } - - return false; - } - public get playbackSpeed(): number { return parseFloat(this.speed.value); } + public get isRendering(): boolean { + return this.rendering; + } + public constructor(private form: HTMLFormElement) { this.play = form.play; this.loop = form.loop; @@ -57,22 +45,43 @@ export class Controls { form.next.addEventListener('click', this.handleNext); form.refresh.addEventListener('click', this.handleReset); + form.render.addEventListener('click', () => this.toggleRendering()); + this.current.addEventListener( + 'click', + () => (this.from.value = this.current.value), + ); this.play.checked = localStorage.getItem('play') === 'true'; - this.play.addEventListener('change', () => this.toggle(this.play.checked)); + this.play.addEventListener('change', () => + this.togglePlayback(this.play.checked), + ); document.addEventListener('keydown', event => { switch (event.key) { case ' ': - this.toggle(); + event.preventDefault(); + this.togglePlayback(); break; case 'ArrowRight': + event.preventDefault(); this.handleNext(); break; } }); } + public consumeState() { + const state = { + isPlaying: this.play.checked || this.stepRequested, + shouldReset: this.resetRequested, + }; + + this.stepRequested = false; + this.resetRequested = false; + + return state; + } + public onReset() { this.overallTime = 0; this.updateTimes = []; @@ -94,16 +103,40 @@ export class Controls { this.lastUpdate = performance.now(); } + public async onRender(frame: number, content: Blob) { + const name = frame.toString().padStart(6, '0'); + const size = content.size / 1024; + + try { + this.directory ??= await window.showDirectoryPicker(); + const file = await this.directory.getFileHandle(`frame-${name}.png`, { + create: true, + }); + const stream = await file.createWritable(); + await stream.write(content); + await stream.close(); + console.log(`Frame: ${name}, Size: ${Math.round(size)} kB`); + this.onFrame(frame); + } catch (e) { + console.error(e); + await this.toggleRendering(false); + } + } + private handleReset = () => { this.resetRequested = true; - } + }; private handleNext = () => { this.stepRequested = true; - } + }; - private toggle = (value?: boolean) => { + private togglePlayback = (value?: boolean) => { this.play.checked = value ?? !this.play.checked; localStorage.setItem('play', this.play.checked ? 'true' : 'false'); - } -} \ No newline at end of file + }; + + public toggleRendering = async (value?: boolean) => { + this.rendering = value ?? !this.rendering; + }; +} diff --git a/src/player/Player.ts b/src/player/Player.ts index 02e362ed..c4bfca23 100644 --- a/src/player/Player.ts +++ b/src/player/Player.ts @@ -1,6 +1,6 @@ import {Project} from '../Project'; import {Controls} from './Controls'; -import {ThreadsMonitor} from "./ThreadsMonitor"; +import {ThreadsMonitor} from './ThreadsMonitor'; const MINIMUM_ANIMATION_DURATION = 1000; const MAX_AUDIO_DESYNC = 1 / 50; @@ -34,26 +34,45 @@ export class Player { } private async reset() { - this.startTime = performance.now(); this.project.start(); - await this.project.next(); + this.finished = await this.project.next(); + while (this.project.frame < this.controls.startFrom && !this.finished) { + this.finished = await this.project.next(); + } this.project.draw(); this.controls.onReset(); this.controls.onFrame(this.project.frame); - this.finished = false; + this.startTime = performance.now(); if (this.audio) { this.audio.currentTime = 0; } } private async run() { - if (this.controls.shouldReset) { + const {isPlaying, shouldReset} = this.controls.consumeState(); + + if (shouldReset) { await this.reset(); } + if (this.controls.isRendering) { + if (!this.audio?.paused) { + this.audio?.pause(); + } + this.finished = await this.project.next(); + this.project.draw(); + await this.controls.onRender(this.project.frame, await this.getContent()); + if (this.finished) { + await this.controls.toggleRendering(false); + } + + this.request(); + return; + } + if (this.controls.playbackSpeed !== 1 && this.audio) { this.audio.currentTime = this.project.time; - } else if (this.controls.isPlaying) { + } else if (isPlaying) { if (this.audio?.paused) { await this.audio?.play(); } @@ -64,9 +83,10 @@ export class Player { } if ( - !this.controls.isPlaying || + !isPlaying || (this.controls.playbackSpeed === 1 && - this.audio?.currentTime < this.project.time) + this.audio && + this.audio.currentTime < this.project.time) ) { this.request(); return; @@ -77,7 +97,10 @@ export class Player { // Prevent animation from restarting too quickly. const animationDuration = performance.now() - this.startTime; if (animationDuration < MINIMUM_ANIMATION_DURATION) { - setTimeout(this.run, MINIMUM_ANIMATION_DURATION - animationDuration); + setTimeout( + () => this.run(), + MINIMUM_ANIMATION_DURATION - animationDuration, + ); return; } await this.reset(); @@ -120,4 +143,10 @@ export class Player { } }); } + + private async getContent(): Promise { + return new Promise(resolve => + this.project.toCanvas().toBlob(resolve, 'image/png'), + ); + } } diff --git a/tools/utils/load-image.js b/tools/utils/load-image.js index cf133bde..6f5ff25b 100644 --- a/tools/utils/load-image.js +++ b/tools/utils/load-image.js @@ -23,7 +23,6 @@ module.exports = async function (fileName) { return { fileName, data: Array.from(imageData.data), - url: `data:image/png;base64,${data}`, width: dimensions.width, height: dimensions.height, };