diff --git a/src/createEffectsHandler.js b/src/createEffectsHandler.js new file mode 100644 index 00000000..b6a00e61 --- /dev/null +++ b/src/createEffectsHandler.js @@ -0,0 +1,140 @@ +const createTimerState = () => { + let elapsed = 0; + let callback = null; + + return { + getElapsed: () => elapsed, + setElapsed: (value) => { + elapsed = value; + }, + addElapsed: (value) => { + elapsed += value; + }, + getCallback: () => callback, + setCallback: (value) => { + callback = value; + }, + }; +}; + +const render = ({ engine, routeGraphics }, payload) => { + const renderState = engine.selectRenderState(); + routeGraphics.render(renderState); +}; + +const handleLineActions = ({ engine }, payload) => { + engine.handleLineActions(); +}; + +const startAutoNextTimer = ({ engine, ticker, autoTimer }, payload) => { + // Remove old callback if exists + const existingCallback = autoTimer.getCallback(); + if (existingCallback) { + ticker.remove(existingCallback); + } + + // Reset elapsed time + autoTimer.setElapsed(0); + + // Create new ticker callback for auto mode + const newCallback = (time) => { + autoTimer.addElapsed(time.deltaMS); + + // Auto advance every 1000ms (1 second) - hardcoded + // TODO: Speed can adjust in the future + if (autoTimer.getElapsed() >= 1000) { + autoTimer.setElapsed(0); + engine.handleAction("nextLine", {}); + } + }; + + autoTimer.setCallback(newCallback); + + // Add to auto ticker + ticker.add(newCallback); +}; + +const clearAutoNextTimer = ({ ticker, autoTimer }, payload) => { + // Remove ticker callback + const existingCallback = autoTimer.getCallback(); + if (existingCallback) { + ticker.remove(existingCallback); + autoTimer.setCallback(null); + } + autoTimer.setElapsed(0); +}; + +const startSkipNextTimer = ({ engine, ticker, skipTimer }, payload) => { + // Remove old callback if exists + const existingCallback = skipTimer.getCallback(); + if (existingCallback) { + ticker.remove(existingCallback); + } + + // Reset elapsed time + skipTimer.setElapsed(0); + + // Create new ticker callback for skip mode + const newCallback = (time) => { + skipTimer.addElapsed(time.deltaMS); + + // Skip advance every 30ms + if (skipTimer.getElapsed() >= 30) { + skipTimer.setElapsed(0); + engine.handleAction("nextLine", {}); + } + }; + + skipTimer.setCallback(newCallback); + + // Add to skip ticker + ticker.add(newCallback); +}; + +const clearSkipNextTimer = ({ ticker, skipTimer }, payload) => { + // Remove ticker callback + const existingCallback = skipTimer.getCallback(); + if (existingCallback) { + ticker.remove(existingCallback); + skipTimer.setCallback(null); + } + skipTimer.setElapsed(0); +}; + +const effects = { + render, + handleLineActions, + startAutoNextTimer, + clearAutoNextTimer, + startSkipNextTimer, + clearSkipNextTimer, +}; + +const createEffectsHandler = ({ getEngine, routeGraphics, ticker }) => { + const autoTimer = createTimerState(); + const skipTimer = createTimerState(); + + return async (pendingEffects) => { + const engine = getEngine(); + + // Deduplicate effects by name, keeping only the last occurrence + const deduplicatedEffects = pendingEffects.reduce((acc, effect) => { + acc[effect.name] = effect; + return acc; + }, {}); + + // Convert back to array and process deduplicated effects + const uniqueEffects = Object.values(deduplicatedEffects); + + const deps = { engine, routeGraphics, ticker, autoTimer, skipTimer }; + + for (const effect of uniqueEffects) { + const handler = effects[effect.name]; + if (handler) { + handler(deps, effect.payload); + } + } + }; +}; + +export default createEffectsHandler; diff --git a/src/index.js b/src/index.js index 20e69eef..7a59cd6e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ import createRouteEngine from "./RouteEngine"; +import createEffectsHandler from "./createEffectsHandler"; export default createRouteEngine; +export { createEffectsHandler }; diff --git a/src/stores/effectHandlers.js b/src/stores/effectHandlers.js deleted file mode 100644 index c8149c44..00000000 --- a/src/stores/effectHandlers.js +++ /dev/null @@ -1,78 +0,0 @@ -import { base64ToArrayBuffer } from "../util"; - -/** - * Pure functions for handling effects with injected dependencies - */ - -export const render = ({ processAndRender }) => { - processAndRender(); -}; - -export const saveVnData = async ( - { timer, localStorage, captureElement, loadAssets }, - effect, -) => { - const { saveData: _saveData, slotIndex } = effect.options; - const saveData = structuredClone(_saveData); - const url = await captureElement("story"); - console.log("saveData", saveData); - console.log("slotindex", slotIndex); - saveData[slotIndex].image = url; - const assets = { - [`saveImage:${slotIndex}`]: { - buffer: base64ToArrayBuffer(url), - type: "image/png", - }, - }; - await loadAssets(assets); - localStorage.setItem("saveData", JSON.stringify(saveData)); - timer.setTimeout( - "saveData", - { - render: {}, - }, - 100, - ); -}; - -export const saveVariables = ({ localStorage, systemStore }) => { - const deviceVariables = systemStore.selectDeviceVariables(); - localStorage.setItem("deviceVariables", JSON.stringify(deviceVariables)); -}; - -export const startAutoNextTimer = ({ timer }) => { - timer.setTimeout( - "autoMode", - { - nextLine: { - forceSkipAutonext: true, - }, - }, - 1000, - ); -}; - -export const clearAutoNextTimer = ({ timer }) => { - timer.clear("autoMode"); -}; - -export const startSkipNextTimer = ({ timer }) => { - timer.setTimeout( - "skipMode", - { - nextLine: { - forceSkipAutonext: true, - }, - }, - 300, - ); -}; - -export const clearSkipNextTimer = ({ timer }) => { - timer.clear("skipMode"); -}; - -export const startTimer = ({ timer }, effect) => { - const { timerId, payload, delay } = effect.options; - timer.setTimeout(timerId, payload, delay); -}; diff --git a/vt/static/main.js b/vt/static/main.js index 13eea9db..2318dda8 100644 --- a/vt/static/main.js +++ b/vt/static/main.js @@ -1,5 +1,5 @@ import { parse } from "https://cdn.jsdelivr.net/npm/yaml@2.7.1/+esm"; -import createRouteEngine from "./RouteEngine.js"; +import createRouteEngine, { createEffectsHandler } from "./RouteEngine.js"; import { Ticker } from "https://cdn.jsdelivr.net/npm/pixi.js@8.0.0/+esm"; import createRouteGraphics, { @@ -155,97 +155,7 @@ const init = async () => { e.preventDefault(); }); - // Create effectsHandler with closure to persist timer state - const createEffectsHandler = () => { - // Auto mode state (persisted across calls via closure) - let autoModeElapsed = 0; - let autoModeCallback = null; - - // Skip mode state (persisted across calls via closure) - let skipModeElapsed = 0; - let skipModeCallback = null; - - return (effects) => { - // Deduplicate effects by name, keeping only the last occurrence - const deduplicatedEffects = effects.reduce((acc, effect) => { - acc[effect.name] = effect; - return acc; - }, {}); - - // Convert back to array and process deduplicated effects - const uniqueEffects = Object.values(deduplicatedEffects); - - for (const effect of uniqueEffects) { - if (effect.name === 'render') { - const renderState = engine.selectRenderState(); - routeGraphics.render(renderState); - } else if (effect.name === 'handleLineActions') { - engine.handleLineActions(); - } else if (effect.name === 'startAutoNextTimer') { - // Remove old callback if exists - if (autoModeCallback) { - ticker.remove(autoModeCallback); - } - - // Reset elapsed time - autoModeElapsed = 0; - - // Create new ticker callback for auto mode - autoModeCallback = (time) => { - autoModeElapsed += time.deltaMS; - - // Auto advance every 1000ms (1 second) - hardcoded - // TODO: Speed can adjust in the future - if (autoModeElapsed >= 1000) { - autoModeElapsed = 0; - engine.handleAction('nextLine', {}); - } - }; - - // Add to auto ticker - ticker.add(autoModeCallback); - } else if (effect.name === 'clearAutoNextTimer') { - // Remove ticker callback - if (autoModeCallback) { - ticker.remove(autoModeCallback); - autoModeCallback = null; - } - autoModeElapsed = 0; - } else if (effect.name === 'startSkipNextTimer') { - // Remove old callback if exists - if (skipModeCallback) { - ticker.remove(skipModeCallback); - } - - // Reset elapsed time - skipModeElapsed = 0; - - // Create new ticker callback for skip mode - skipModeCallback = (time) => { - skipModeElapsed += time.deltaMS; - - // Skip advance every 30ms - if (skipModeElapsed >= 30) { - skipModeElapsed = 0; - engine.handleAction('nextLine', {}); - } - }; - - // Add to skip ticker - ticker.add(skipModeCallback); - } else if (effect.name === 'clearSkipNextTimer') { - // Remove ticker callback - if (skipModeCallback) { - ticker.remove(skipModeCallback); - skipModeCallback = null; - } - skipModeElapsed = 0; - } - } - }; - }; - - const effectsHandler = createEffectsHandler(); + const effectsHandler = createEffectsHandler({ getEngine: () => engine, routeGraphics, ticker }); const engine = createRouteEngine({ handlePendingEffects: effectsHandler }); engine.init({