diff --git a/src/core/context.ts b/src/core/context.ts index a20230e..dfbd9ba 100644 --- a/src/core/context.ts +++ b/src/core/context.ts @@ -2,6 +2,7 @@ import {createModel, Signal, signal} from '@preact/signals-core'; import {createEntityCollection} from "../utils/entity"; import {Part} from "./part"; import {Region} from "./region"; +import {RuleRegistry, RuleContext, dispatchCommand as dispatchRuleCommand} from "./rule"; export type Context = { type: string; @@ -10,18 +11,23 @@ export type Context = { export const GameContext = createModel((root: Context) => { const parts = createEntityCollection(); const regions = createEntityCollection(); + const rules = signal(new Map()); + const ruleContexts = signal[]>([]); const contexts = signal[]>([]); contexts.value = [signal(root)]; + function pushContext(context: Context) { const ctxSignal = signal(context); contexts.value = [...contexts.value, ctxSignal]; return context; } + function popContext() { if (contexts.value.length > 1) { contexts.value = contexts.value.slice(0, -1); } } + function latestContext(type: T['type']): Signal | undefined { for(let i = contexts.value.length - 1; i >= 0; i--){ if(contexts.value[i].value.type === type){ @@ -31,17 +37,28 @@ export const GameContext = createModel((root: Context) => { return undefined; } + function dispatchCommand(input: string) { + return dispatchRuleCommand({ + rules: rules.value, + ruleContexts: ruleContexts.value, + contexts, + }, input); + } + return { parts, regions, + rules, + ruleContexts, contexts, pushContext, popContext, latestContext, + dispatchCommand, } }) /** 创建游戏上下文实例 */ export function createGameContext(root: Context = { type: 'game' }) { return new GameContext(root); -} \ No newline at end of file +} diff --git a/src/core/rule.ts b/src/core/rule.ts index 09e12e8..8882e7c 100644 --- a/src/core/rule.ts +++ b/src/core/rule.ts @@ -1,80 +1,129 @@ -import {Context} from "./context"; -import {Command} from "../utils/command"; -import {effect} from "@preact/signals-core"; +import {Command, CommandSchema, parseCommand, parseCommandSchema, validateCommand} from "../utils/command"; -export type RuleContext = Context & { - actions: Command[]; - handledActions: number; - invocations: RuleContext[]; +export type RuleState = 'running' | 'yielded' | 'waiting' | 'done'; + +export type RuleContext = { + type: string; + schema?: CommandSchema; + generator: Generator; + parent?: RuleContext; + children: RuleContext[]; + state: RuleState; resolution?: T; } -/** - * 调用规则生成器并管理其上下文 - * @param pushContext - 用于推送上下文到上下文栈的函数 - * @param type - 规则类型 - * @param rule - 规则生成器函数 - * @returns 规则执行结果 - */ -export function invokeRuleContext( - pushContext: (context: Context) => void, - type: string, - rule: Generator +export type RuleDef = { + schema: CommandSchema; + create: (cmd: Command) => Generator; +}; + +export type RuleRegistry = Map>; + +export function createRule( + schemaStr: string, + fn: (cmd: Command) => Generator +): RuleDef { + return { + schema: parseCommandSchema(schemaStr), + create: fn as RuleDef['create'], + }; +} + +function parseYieldedSchema(value: string | CommandSchema): CommandSchema { + if (typeof value === 'string') { + return parseCommandSchema(value); + } + return value; +} + +function pushContextToGame(game: GameContextLike, ctx: RuleContext) { + game.contexts.value = [...game.contexts.value, { value: ctx } as any]; + game.ruleContexts.push(ctx); +} + +function discardChildren(game: GameContextLike, parent: RuleContext) { + for (const child of parent.children) { + const idx = game.ruleContexts.indexOf(child); + if (idx !== -1) game.ruleContexts.splice(idx, 1); + + const ctxIdx = game.contexts.value.findIndex((c: any) => c.value === child); + if (ctxIdx !== -1) { + const arr = [...game.contexts.value]; + arr.splice(ctxIdx, 1); + game.contexts.value = arr; + } + } + parent.children = []; + parent.state = 'yielded'; +} + +function invokeRule( + game: GameContextLike, + command: Command, + ruleDef: RuleDef, + parent?: RuleContext ): RuleContext { const ctx: RuleContext = { - type, - actions: [], - handledActions: 0, - invocations: [], + type: ruleDef.schema.name, + schema: undefined, + generator: ruleDef.create(command), + parent, + children: [], + state: 'running', resolution: undefined, }; - let disposed = false; + if (parent) { + discardChildren(game, parent); + parent.children.push(ctx as RuleContext); + parent.state = 'waiting'; + } - const executeRule = () => { - if (disposed || ctx.resolution !== undefined) return; + pushContextToGame(game, ctx as RuleContext); - try { - const result = rule.next(); - - if (result.done) { - ctx.resolution = result.value; - return; - } - - const actionType = result.value; - - if (actionType) { - // 暂停于 yield 点,等待外部处理动作 - // 当外部更新 actions 后,effect 会重新触发 - } - } catch (error) { - throw error; - } - }; - - const dispose = effect(() => { - if (ctx.resolution !== undefined) { - dispose(); - disposed = true; - return; - } - executeRule(); - }); - - pushContext(ctx); + const result = ctx.generator.next(); + if (result.done) { + ctx.resolution = result.value; + ctx.state = 'done'; + } else { + ctx.schema = parseYieldedSchema(result.value); + ctx.state = 'yielded'; + } return ctx; } -/** - * 创建一个规则生成器辅助函数 - * @param type - 规则类型 - * @param fn - 规则逻辑函数 - */ -export function createRule( - type: string, - fn: (ctx: RuleContext) => Generator -): (ctx: RuleContext) => Generator { - return fn; +export function dispatchCommand(game: GameContextLike, input: string): RuleContext | undefined { + const command = parseCommand(input); + + if (game.rules.has(command.name)) { + const ruleDef = game.rules.get(command.name)!; + return invokeRule(game, command, ruleDef); + } + + for (let i = game.ruleContexts.length - 1; i >= 0; i--) { + const ctx = game.ruleContexts[i]; + if (ctx.state === 'yielded' && ctx.schema) { + const validation = validateCommand(command, ctx.schema); + if (validation.valid) { + const result = ctx.generator.next(command); + if (result.done) { + ctx.resolution = result.value; + ctx.state = 'done'; + } else { + ctx.schema = parseYieldedSchema(result.value); + ctx.state = 'yielded'; + } + return ctx; + } + } + } + + return undefined; } + +type GameContextLike = { + rules: RuleRegistry; + ruleContexts: RuleContext[]; + contexts: { value: any[] }; +}; diff --git a/src/index.ts b/src/index.ts index 5486f4c..8cf82c8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,8 +13,8 @@ export { flip, flipTo, roll } from './core/part'; export type { Region, RegionAxis } from './core/region'; export { applyAlign, shuffle } from './core/region'; -export type { RuleContext } from './core/rule'; -export { invokeRuleContext, createRule } from './core/rule'; +export type { RuleContext, RuleState, RuleDef, RuleRegistry } from './core/rule'; +export { createRule, dispatchCommand } from './core/rule'; // Utils export type { Command, CommandSchema, CommandParamSchema, CommandOptionSchema, CommandFlagSchema } from './utils/command';