import {createEffect, createResource } from "solid-js"; import type {OptionsResult, RuntimeResult} from "../../yarn-spinner/runtime/results"; import {compile, parseYarn, YarnRunner} from "../../yarn-spinner"; import {loadElementSrc, resolvePath} from "../utils/path"; import {createStore} from "solid-js/store"; import {RunnerOptions} from "../../yarn-spinner/runtime/runner"; import {getTtrpgFunctions} from "./ttrpgRunner"; import { getIndexedData } from "../../data-loader/file-index"; type YarnSpinnerStore = { dialogueHistory: (RuntimeResult | OptionsResult['options'][0])[], currentOptions: OptionsResult | null, isEnded: boolean, runnerInstance: YarnRunner | null, } export function createYarnStore(element: HTMLElement, props: {start: string}){ const [store, setStore] = createStore({ dialogueHistory: [], currentOptions: null, isEnded: false, runnerInstance: null }); // 获取文件路径 const {articlePath, rawSrc} = loadElementSrc(element); const yarnPaths = (rawSrc || '').split(',') .map((s: string) => resolvePath(articlePath, s.trim())) .filter(Boolean); // 加载 yarn 内容 const [yarnContent] = createResource(() => yarnPaths, loadYarnFiles); // 创建 runner const createRunner = () => { const content = yarnContent(); if (!content) return null; try { const ast = parseYarn(content); const program = compile(ast); const runner = new YarnRunner(program, { startAt: props.start, }); const {commands, functions} = getTtrpgFunctions(runner); runner.registerCommands(commands); runner.registerFunctions(functions); return runner; } catch (error) { console.error('Failed to initialize YarnRunner:', error); return null; } }; function advance(index?: number){ const runner = store.runnerInstance; if(!runner) return; if(index === undefined && runner.currentResult?.isDialogueEnd) return; if(runner.currentResult?.type === 'options'){ if(index === undefined)return; const option = runner.currentResult.options[index]; setStore('dialogueHistory', [...store.dialogueHistory, option]); } runner.advance(index); processRunnerOutput(); } createEffect(() => { if(!yarnContent()) return; setStore('runnerInstance', createRunner()); requestAnimationFrame(function(){ advance(); }); }); // 处理 runner 输出 const processRunnerOutput = () => { const runner = store.runnerInstance; if(!runner)return; const result = runner.currentResult; if (!result) return; if(result.type === 'options'){ setStore('currentOptions', result); }else{ setStore('currentOptions', null); } setStore('dialogueHistory', [...store.dialogueHistory, result]); }; // 重新开始 const restart = () => { setStore({ dialogueHistory: [], currentOptions: null, isEnded: false, runnerInstance: createRunner(), }); processRunnerOutput(); }; return { store, advance, restart, } } // 加载多个 yarn 文件并拼接 async function loadYarnFiles(paths: string[]): Promise { const contents = await Promise.all(paths.map(path => getIndexedData(path))); return contents.join('\n'); }