From 6c39ecb0ebe98aa39b93dfcf4c216a385af53a8f Mon Sep 17 00:00:00 2001 From: Luciano Hanyon Wu Date: Fri, 26 Dec 2025 11:30:09 +0800 Subject: [PATCH 1/5] refactor effects hander code --- src/createEffectsHandler.js | 90 +++++++++++++++++++++++++++++++++ src/index.js | 2 + src/stores/effectHandlers.js | 78 ----------------------------- vt/static/main.js | 97 ++---------------------------------- 4 files changed, 96 insertions(+), 171 deletions(-) create mode 100644 src/createEffectsHandler.js delete mode 100644 src/stores/effectHandlers.js diff --git a/src/createEffectsHandler.js b/src/createEffectsHandler.js new file mode 100644 index 00000000..ca40047b --- /dev/null +++ b/src/createEffectsHandler.js @@ -0,0 +1,90 @@ +const createEffectsHandler = ({ engine, routeGraphics, ticker }) => { + // 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 async (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; + } + } + }; +}; + +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..29f753ad 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,98 +155,9 @@ 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 engine = createRouteEngine({ handlePendingEffects: effectsHandler }); + const engine = {}; + const effectsHandler = createEffectsHandler({ engine, routeGraphics, ticker }); + Object.assign(engine, createRouteEngine({ handlePendingEffects: effectsHandler })); engine.init({ initialState: { From 4937fa3f35ebd5d51d95085ddd28128a0db32369 Mon Sep 17 00:00:00 2001 From: Luciano Hanyon Wu Date: Fri, 26 Dec 2025 11:34:42 +0800 Subject: [PATCH 2/5] update --- src/createEffectsHandler.js | 5 +++-- vt/static/main.js | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/createEffectsHandler.js b/src/createEffectsHandler.js index ca40047b..62a0ce20 100644 --- a/src/createEffectsHandler.js +++ b/src/createEffectsHandler.js @@ -1,4 +1,4 @@ -const createEffectsHandler = ({ engine, routeGraphics, ticker }) => { +const createEffectsHandler = ({ getEngine, routeGraphics, ticker }) => { // Auto mode state (persisted across calls via closure) let autoModeElapsed = 0; let autoModeCallback = null; @@ -7,7 +7,8 @@ const createEffectsHandler = ({ engine, routeGraphics, ticker }) => { let skipModeElapsed = 0; let skipModeCallback = null; - return async (effects) => { + return (effects) => { + const engine = getEngine(); // Deduplicate effects by name, keeping only the last occurrence const deduplicatedEffects = effects.reduce((acc, effect) => { acc[effect.name] = effect; diff --git a/vt/static/main.js b/vt/static/main.js index 29f753ad..2318dda8 100644 --- a/vt/static/main.js +++ b/vt/static/main.js @@ -155,9 +155,8 @@ const init = async () => { e.preventDefault(); }); - const engine = {}; - const effectsHandler = createEffectsHandler({ engine, routeGraphics, ticker }); - Object.assign(engine, createRouteEngine({ handlePendingEffects: effectsHandler })); + const effectsHandler = createEffectsHandler({ getEngine: () => engine, routeGraphics, ticker }); + const engine = createRouteEngine({ handlePendingEffects: effectsHandler }); engine.init({ initialState: { From 0e8dc3496b78983d0209c11dcb3a128d899a3f91 Mon Sep 17 00:00:00 2001 From: Luciano Hanyon Wu Date: Fri, 26 Dec 2025 11:37:41 +0800 Subject: [PATCH 3/5] update --- src/createEffectsHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/createEffectsHandler.js b/src/createEffectsHandler.js index 62a0ce20..2ed0dd9b 100644 --- a/src/createEffectsHandler.js +++ b/src/createEffectsHandler.js @@ -7,7 +7,7 @@ const createEffectsHandler = ({ getEngine, routeGraphics, ticker }) => { let skipModeElapsed = 0; let skipModeCallback = null; - return (effects) => { + return async (effects) => { const engine = getEngine(); // Deduplicate effects by name, keeping only the last occurrence const deduplicatedEffects = effects.reduce((acc, effect) => { From 09f867680e79be088ed39dad9b2bee495872983a Mon Sep 17 00:00:00 2001 From: Luciano Hanyon Wu Date: Fri, 26 Dec 2025 11:56:28 +0800 Subject: [PATCH 4/5] move handlers to functions --- src/createEffectsHandler.js | 189 ++++++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 73 deletions(-) diff --git a/src/createEffectsHandler.js b/src/createEffectsHandler.js index 2ed0dd9b..60e6c3ac 100644 --- a/src/createEffectsHandler.js +++ b/src/createEffectsHandler.js @@ -1,16 +1,118 @@ -const createEffectsHandler = ({ getEngine, routeGraphics, ticker }) => { - // Auto mode state (persisted across calls via closure) - let autoModeElapsed = 0; - let autoModeCallback = null; +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); + } - // Skip mode state (persisted across calls via closure) - let skipModeElapsed = 0; - let skipModeCallback = null; + // 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); +}; - return async (effects) => { +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 = effects.reduce((acc, effect) => { + const deduplicatedEffects = pendingEffects.reduce((acc, effect) => { acc[effect.name] = effect; return acc; }, {}); @@ -18,71 +120,12 @@ const createEffectsHandler = ({ getEngine, routeGraphics, ticker }) => { // 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) { - 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 handler = effects[effect.name]; + if (handler) { + handler(deps, effect.payload); } } }; From 24bd1e3f961070e2f29ecc3df5a4fed96ab01eec Mon Sep 17 00:00:00 2001 From: Luciano Hanyon Wu Date: Fri, 26 Dec 2025 11:56:42 +0800 Subject: [PATCH 5/5] move handlers to functions --- src/createEffectsHandler.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/createEffectsHandler.js b/src/createEffectsHandler.js index 60e6c3ac..b6a00e61 100644 --- a/src/createEffectsHandler.js +++ b/src/createEffectsHandler.js @@ -4,10 +4,16 @@ const createTimerState = () => { return { getElapsed: () => elapsed, - setElapsed: (value) => { elapsed = value; }, - addElapsed: (value) => { elapsed += value; }, + setElapsed: (value) => { + elapsed = value; + }, + addElapsed: (value) => { + elapsed += value; + }, getCallback: () => callback, - setCallback: (value) => { callback = value; }, + setCallback: (value) => { + callback = value; + }, }; };