Skip to content
This repository has been archived by the owner on Jun 14, 2021. It is now read-only.

Support missing effect ignoring #156

Merged
merged 17 commits into from Sep 1, 2018
Merged
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -14,7 +14,7 @@
"declare": "cross-env tsc -p src/delir-core -d --declarationDir src/delir-core/types",
"precommit": "lint-staged",
"test-core": "jest -c packages/delir-core/jest.config.js",
"test-front": "cross-env mocha --colors --reporter spec --watch-extensions ts,tsx --require ./src/frontend/__spec__/__bootstrap__.js './src/frontend/**/__spec__/*-spec.{ts,tsx}'"
"test-front": "jest -c packages/delir/jest.config.js"
},
"workspaces": [
"packages/*"
Expand Down
52 changes: 31 additions & 21 deletions packages/delir-core/src/Engine/Engine.ts
Expand Up @@ -9,7 +9,7 @@ import PluginRegistry from '../plugin-support/plugin-registry'

import * as _ from 'lodash'
import * as timecodes from 'node-timecodes'
import { RenderingAbortedException, RenderingFailedException } from '../exceptions/'
import { EffectPluginMissingException, RenderingAbortedException, RenderingFailedException } from '../exceptions/'
import { mergeInto as mergeAudioBufferInto } from '../helper/Audio'
import defaults from '../helper/defaults'
import FPSCounter from '../helper/FPSCounter'
Expand All @@ -36,6 +36,7 @@ interface RenderingOption {
beginFrame: number
endFrame: number
loop: boolean
ignoreMissingEffect: boolean
}

interface RenderProgression {
Expand Down Expand Up @@ -141,10 +142,11 @@ export default class Engine

public renderSequencial(compositionId: string, options: Partial<RenderingOption> = {}): ProgressPromise<void, RenderProgression>
{
const _options: RenderingOption = defaults(options, {
const renderingOption: RenderingOption = defaults(options, {
beginFrame: 0,
loop: false,
endFrame: -1,
ignoreMissingEffect: false,
})

this.stopCurrentRendering()
Expand All @@ -157,8 +159,8 @@ export default class Engine
this._seqRenderPromise = null
})

let request = this._initStage(compositionId, _options.beginFrame)
const renderTasks = await this._taskingStage(request)
let request = this._initStage(compositionId, renderingOption)
const renderTasks = await this._taskingStage(request, renderingOption)
this._fpsCounter.reset()

const reqDestCanvasCtx = request.destCanvas.getContext('2d')!
Expand All @@ -169,7 +171,7 @@ export default class Engine
let lastAudioBufferTime = -1

const render = _.throttle(async () => {
const currentFrame = _options.beginFrame + renderedFrames
const currentFrame = renderingOption.beginFrame + renderedFrames
const currentTime = currentFrame / framerate

// 最後のバッファリングから1秒経過 & 次のフレームがレンダリングの終わりでなければバッファリング
Expand Down Expand Up @@ -212,8 +214,8 @@ export default class Engine
}
}

if (_options.beginFrame + renderedFrames >= lastFrame) {
if (_options.loop) {
if (renderingOption.beginFrame + renderedFrames >= lastFrame) {
if (renderingOption.loop) {
renderedFrames = 0
lastAudioBufferTime = -1
} else {
Expand All @@ -235,7 +237,7 @@ export default class Engine
notifier({
state: `time: ${timecode.slice(0, -3)} (${this._fpsCounter.latestFPS()} / ${request.framerate} fps)`,
currentFrame: request.frame,
rangeEndFrame: _options.endFrame,
rangeEndFrame: renderingOption.endFrame,
isAudioBuffered: isAudioBufferingNeeded,
audioBuffers: request.destAudioBuffer,
})
Expand All @@ -248,7 +250,7 @@ export default class Engine
})
}

private _initStage(compositionId: string, beginFrame: number): RenderingRequest
private _initStage(compositionId: string, option: RenderingOption): RenderingRequest
{
if (!this._project) throw new RenderingFailedException('Project must be set before rendering')
if (!this._pluginRegistry) throw new RenderingFailedException('Plugin registry not set')
Expand All @@ -273,7 +275,7 @@ export default class Engine

const audioBuffers = _.times(rootComposition.audioChannels, () => new Float32Array(new ArrayBuffer(bufferSizeBytePerSec)))

const currentFrame = beginFrame
const currentFrame = option.beginFrame
const currentTime = currentFrame / rootComposition.framerate

return new RenderingRequest({
Expand Down Expand Up @@ -301,7 +303,7 @@ export default class Engine
})
}

private async _taskingStage(this: Engine, req: RenderingRequest): Promise<LayerRenderTask[]>
private async _taskingStage(req: RenderingRequest, option: RenderingOption): Promise<LayerRenderTask[]>
{
const layerTasks: LayerRenderTask[] = []

Expand All @@ -322,16 +324,24 @@ export default class Engine
// Initialize effects
const effects: EffectRenderTask[] = []
for (const effect of clip.effects) {
const effectRenderTask = EffectRenderTask.build({
effect,
clip,
req,
effectCache: this._effectCache,
resolver: req.resolver
})

await effectRenderTask.initialize(req)
effects.push(effectRenderTask)
try {
const effectRenderTask = EffectRenderTask.build({
effect,
clip,
req,
effectCache: this._effectCache,
resolver: req.resolver
})

await effectRenderTask.initialize(req)
effects.push(effectRenderTask)
} catch (e) {
if (e instanceof EffectPluginMissingException && option.ignoreMissingEffect) {
continue
} else {
throw e
}
}
}

clipRenderTask.effectRenderTask = effects
Expand Down
5 changes: 5 additions & 0 deletions packages/delir-core/src/Engine/Task/EffectRenderTask.ts
@@ -1,6 +1,7 @@
import * as _ from 'lodash'

import { Clip, Effect } from '../../Entity'
import { EffectPluginMissingException } from '../../exceptions'
import EffectPluginBase from '../../plugin-support/PostEffectBase'
import { TypeDescriptor } from '../../plugin-support/type-descriptor'
import { AssetPointer, Expression } from '../../Values'
Expand All @@ -22,6 +23,10 @@ export default class EffectRenderTask {
}): EffectRenderTask {
const EffectPluginClass = resolver.resolveEffectPlugin(effect.processor)!

if (EffectPluginClass == null) {
throw new EffectPluginMissingException(`Missing effect plugin ${effect.processor}`, { effectId: effect.processor })
}

const effectParams = EffectPluginClass.provideParameters()
const effectAssetParamNames = effectParams.properties.filter(prop => prop.type === 'ASSET').map(prop => prop.paramName)

Expand Down
12 changes: 12 additions & 0 deletions packages/delir-core/src/exceptions/EffectPluginMissingException.ts
@@ -0,0 +1,12 @@
interface EffectPluginMissingExceptionDetail {
effectId: string
}

export default class EffectPluginMissingException extends Error {
constructor(
message: string,
public detail: EffectPluginMissingExceptionDetail
) {
super(message)
}
}
1 change: 1 addition & 0 deletions packages/delir-core/src/exceptions/index.ts
Expand Up @@ -5,3 +5,4 @@ export {default as PluginAssertionFailedException} from './plugin-assertion-fail
export {default as InvalidPluginLoadedException} from './invalid-plugin-loaded-exception'
export {default as RenderingFailedException} from './rendering-failed-exception'
export {default as RenderingAbortedException} from './rendering-aborted-exception'
export {default as EffectPluginMissingException} from './EffectPluginMissingException'
20 changes: 18 additions & 2 deletions packages/delir/actions/App.ts
Expand Up @@ -7,6 +7,7 @@ import * as _ from 'lodash'
import * as MsgPack from 'msgpack5'
import * as path from 'path'

import PreferenceStore from '../domain/Preference/PreferenceStore'
import EditorStateStore from '../stores/EditorStateStore'
import RendererStore from '../stores/RendererStore'

Expand Down Expand Up @@ -90,16 +91,25 @@ export const changeActiveClip = operation((context, { clipId }: { clipId: string
// Preview
//
export const startPreview = operation((context, { compositionId, beginFrame = 0 }: { compositionId: string, beginFrame?: number }) => {
context.dispatch(AppActions.startPreviewAction, { compositionId, beginFrame })
const preference = context.getStore(PreferenceStore).getPreferences()

context.dispatch(AppActions.startPreviewAction, {
compositionId,
beginFrame,
ignoreMissingEffect: preference.renderer.ignoreMissingEffect,
})
})

export const stopPreview = operation((context) => {
context.dispatch(AppActions.stopPreviewAction, {})
})

export const renderDestinate = operation((context, arg: { compositionId: string }) => {
const preference = context.getStore(PreferenceStore).getPreferences()

context.dispatch(AppActions.renderDestinateAction, {
compositionId: arg.compositionId
compositionId: arg.compositionId,
ignoreMissingEffect: preference.renderer.ignoreMissingEffect,
})
})

Expand Down Expand Up @@ -213,3 +223,9 @@ export const autoSaveProject = operation(async (context) => {
timeout: 2000
})
})

export const changePreferenceOpenState = operation((context, { open }: { open: boolean }) => {
context.dispatch(AppActions.changePreferenceOpenStateAction, { open })
})

// console.log(remote.app.getPath('userData'))
6 changes: 4 additions & 2 deletions packages/delir/actions/actions.ts
@@ -1,5 +1,6 @@
import * as Delir from '@ragg/delir-core'
import { action } from '@ragg/fleur'

import { DragEntity } from './App'

export const AppActions = {
Expand All @@ -9,13 +10,14 @@ export const AppActions = {
clearDragEntityAction: action<{}>(),
changeActiveCompositionAction: action<{ compositionId: string }>(),
changeActiveClipAction: action<{ clipId: string }>(),
startPreviewAction: action<{ compositionId: string, beginFrame: number }>(),
startPreviewAction: action<{ compositionId: string, beginFrame: number, ignoreMissingEffect: boolean }>(),
stopPreviewAction: action<{}>(),
renderDestinateAction: action<{ compositionId: string }>(),
renderDestinateAction: action<{ compositionId: string, ignoreMissingEffect: boolean }>(),
updateProcessingStateAction: action<{ stateText: string }>(),
addMessageAction: action<{ id: string, title?: string, level: 'info' | 'error', message: string, detail?: string }>(),
removeMessageAction: action<{ id: string }>(),
seekPreviewFrameAction: action<{ frame: number }>(),
changePreferenceOpenStateAction: action<{ open: boolean }>()
}

export const ProjectModActions = {
Expand Down
4 changes: 4 additions & 0 deletions packages/delir/assets/styles/_variables.styl
Expand Up @@ -2,11 +2,15 @@ $pane-border-width = 1px
// $pane-background = linear-gradient(to bottom, hsl(0, 0%, 10%), hsl(0, 0%, 9%))
$pane-background = transparent

$color-theming = #7b14ea

$color-info = #00acee
$color-error = #f83737
$color-disabled = #9a9a9a

$color-app-bg = #353535
$color-app-bg-dark = #2c2c2c
$color-app-bg-border-dark = #222
$color-dropdown-bg = var(--background)
$color-dropdown-border = var(--pane-border-color)

Expand Down
33 changes: 33 additions & 0 deletions packages/delir/domain/Preference/PreferenceStore.ts
@@ -0,0 +1,33 @@
import { listen, Store } from '@ragg/fleur'

import { PreferenceActions } from './actions'

export interface Preference {
renderer: {
ignoreMissingEffect: boolean
}
}

export default class PreferenceStore extends Store<Preference> {
public static storeName = 'PreferenceStore'

public state: Preference = {
renderer: {
ignoreMissingEffect: false,
},
}

// @ts-ignore
private restorePreference = listen(PreferenceActions.restorePreference, ({ preference }) => {
this.updateWith(draft => Object.assign(draft, preference))
})

// @ts-ignore
private handlePreferenceChange = listen(PreferenceActions.changePreference, ({ patch }) => {
this.updateWith(draft => { Object.assign(draft, patch) })
})

public getPreferences() {
return this.state
}
}
8 changes: 8 additions & 0 deletions packages/delir/domain/Preference/actions.ts
@@ -0,0 +1,8 @@
import { action } from '@ragg/fleur'

import { Preference } from './PreferenceStore'

export const PreferenceActions = {
restorePreference: action<{ preference: Preference }>(),
changePreference: action<{ patch: Partial<Preference> }>(),
}
62 changes: 62 additions & 0 deletions packages/delir/domain/Preference/operations.ts
@@ -0,0 +1,62 @@
import { operation } from '@ragg/fleur'
import { remote } from 'electron'
import { existsSync, readFileSync, writeFile } from 'fs'
import * as path from 'path'

import * as AppOps from '../../actions/App'
import { PreferenceActions } from './actions'

import PreferenceStore from '@ragg/delir/domain/Preference/PreferenceStore'
import { validateSchema } from './validation'

const userDir = remote.app.getPath('userData')
const preferencePath = path.join(userDir, 'preferences.json')

export const restoreApplicationPreference = operation((context) => {
if (!existsSync(preferencePath)) {
return
}

const json = readFileSync(preferencePath, { encoding: 'UTF-8' })

let preference: any
try {
preference = JSON.parse(json)
} catch {
context.executeOperation(AppOps.notify, {
title: 'App preference loading failed',
message: 'preferences.json invalid. Use initial preference instead.',
level: 'error',
timeout: 5000,
})

return
}

const error = validateSchema(preference)

if (error) {
context.executeOperation(AppOps.notify, {
title: 'App preference loading failed',
message: 'preferences.json invalid. Use initial preference instead.\n' + error.message,
level: 'error',
timeout: 5000,
})

return
}

context.dispatch(PreferenceActions.restorePreference, { preference })
})

export const savePreferences = operation(async (context) => {
const preference = context.getStore(PreferenceStore).dehydrate()
await new Promise((resolve, reject) => writeFile(preferencePath, JSON.stringify(preference), (err) => {
err ? reject(err) : resolve()
}))
})

export const setRendererIgnoreMissingEffectPreference = operation(async (context, { ignore }: { ignore: boolean }) => {
context.dispatch(PreferenceActions.changePreference, { patch: { renderer: { ignoreMissingEffect: ignore } } })
await context.executeOperation(savePreferences, {})
})
25 changes: 25 additions & 0 deletions packages/delir/domain/Preference/validation.spec.ts
@@ -0,0 +1,25 @@
import { validateSchema } from './validation'

describe('PreferenceStore', () => {
describe('Schema validation', () => {
it('Should passing validation with correct schema', () => {
const actual = validateSchema({
renderer: {
ignoreMissingEffect: true,
}
})

expect(actual).toBe(null)
})

it('Should failed validation with correct schema', () => {
const actual = validateSchema({
renderer: {
ignoreMissingEffect: '💩',
}
})

expect(actual).toBeInstanceOf(Error)
})
})
})