refactor: api surface change
This commit is contained in:
parent
467a56bd84
commit
6e1c42015f
|
|
@ -1,23 +1,23 @@
|
|||
import { ReadonlySignal, Signal } from '@preact/signals-core';
|
||||
import type { CommandSchema, CommandRegistry, PromptEvent } from '@/utils/command';
|
||||
import type {CommandSchema, CommandRegistry, PromptEvent, CommandRunnerContextExport} from '@/utils/command';
|
||||
import type { MutableSignal } from '@/utils/mutable-signal';
|
||||
import { createGameContext } from './game';
|
||||
import {createGameContext, IGameContext} from './game';
|
||||
|
||||
export type GameHostStatus = 'created' | 'running' | 'disposed';
|
||||
|
||||
export interface GameModule<TState extends Record<string, unknown>> {
|
||||
registry: CommandRegistry<MutableSignal<TState>>;
|
||||
registry: CommandRegistry<IGameContext<TState>>;
|
||||
createInitialState: () => TState;
|
||||
}
|
||||
|
||||
export class GameHost<TState extends Record<string, unknown>> {
|
||||
readonly state: ReadonlySignal<TState>;
|
||||
readonly commands: ReturnType<typeof createGameContext<TState>>['commands'];
|
||||
readonly context: IGameContext<TState>;
|
||||
readonly status: ReadonlySignal<GameHostStatus>;
|
||||
readonly activePromptSchema: ReadonlySignal<CommandSchema | null>;
|
||||
readonly activePromptPlayer: ReadonlySignal<string | null>;
|
||||
|
||||
private _state: MutableSignal<TState>;
|
||||
private _commands: CommandRunnerContextExport<IGameContext<TState>>;
|
||||
private _status: Signal<GameHostStatus>;
|
||||
private _activePromptSchema: Signal<CommandSchema | null>;
|
||||
private _activePromptPlayer: Signal<string | null>;
|
||||
|
|
@ -26,17 +26,14 @@ export class GameHost<TState extends Record<string, unknown>> {
|
|||
private _isDisposed = false;
|
||||
|
||||
constructor(
|
||||
registry: CommandRegistry<MutableSignal<TState>>,
|
||||
registry: CommandRegistry<IGameContext<TState>>,
|
||||
createInitialState: () => TState,
|
||||
) {
|
||||
this._createInitialState = createInitialState;
|
||||
this._eventListeners = new Map();
|
||||
|
||||
const initialState = createInitialState();
|
||||
const context = createGameContext(registry, initialState);
|
||||
|
||||
this._state = context.state;
|
||||
this.commands = context.commands;
|
||||
this.context = createGameContext(registry, initialState);
|
||||
|
||||
this._status = new Signal<GameHostStatus>('created');
|
||||
this.status = this._status;
|
||||
|
|
@ -47,7 +44,8 @@ export class GameHost<TState extends Record<string, unknown>> {
|
|||
this._activePromptPlayer = new Signal<string | null>(null);
|
||||
this.activePromptPlayer = this._activePromptPlayer;
|
||||
|
||||
this.state = this._state;
|
||||
this._state = this.context._state;
|
||||
this._commands = this.context._commands;
|
||||
|
||||
this._setupPromptTracking();
|
||||
}
|
||||
|
|
@ -55,13 +53,13 @@ export class GameHost<TState extends Record<string, unknown>> {
|
|||
private _setupPromptTracking() {
|
||||
let currentPromptEvent: PromptEvent | null = null;
|
||||
|
||||
this.commands.on('prompt', (e) => {
|
||||
this._commands.on('prompt', (e) => {
|
||||
currentPromptEvent = e as PromptEvent;
|
||||
this._activePromptSchema.value = currentPromptEvent.schema;
|
||||
this._activePromptPlayer.value = currentPromptEvent.currentPlayer;
|
||||
});
|
||||
|
||||
this.commands.on('promptEnd', () => {
|
||||
this._commands.on('promptEnd', () => {
|
||||
currentPromptEvent = null;
|
||||
this._activePromptSchema.value = null;
|
||||
this._activePromptPlayer.value = null;
|
||||
|
|
@ -76,7 +74,7 @@ export class GameHost<TState extends Record<string, unknown>> {
|
|||
if (this._isDisposed) {
|
||||
return 'GameHost is disposed';
|
||||
}
|
||||
return this.commands._tryCommit(input);
|
||||
return this._commands._tryCommit(input);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,14 +98,14 @@ export class GameHost<TState extends Record<string, unknown>> {
|
|||
throw new Error('GameHost is disposed');
|
||||
}
|
||||
|
||||
this.commands._cancel();
|
||||
this._commands._cancel();
|
||||
|
||||
const initialState = this._createInitialState();
|
||||
this._state.value = initialState as any;
|
||||
|
||||
// Start the setup command but don't wait for it to complete
|
||||
// The command will run in the background and prompt for input
|
||||
this.commands.run(setupCommand).catch(() => {
|
||||
this._commands.run(setupCommand).catch(() => {
|
||||
// Command may be cancelled or fail, which is expected
|
||||
});
|
||||
|
||||
|
|
@ -121,7 +119,7 @@ export class GameHost<TState extends Record<string, unknown>> {
|
|||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this.commands._cancel();
|
||||
this._commands._cancel();
|
||||
this._status.value = 'disposed';
|
||||
|
||||
// Emit dispose event BEFORE clearing listeners
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {MutableSignal, mutableSignal} from "@/utils/mutable-signal";
|
|||
import {
|
||||
Command,
|
||||
CommandRegistry, CommandResult,
|
||||
CommandRunnerContext,
|
||||
CommandRunnerContext, CommandRunnerContextExport,
|
||||
CommandSchema,
|
||||
createCommandRegistry,
|
||||
createCommandRunnerContext,
|
||||
|
|
@ -22,7 +22,7 @@ export interface IGameContext<TState extends Record<string, unknown> = {} > {
|
|||
|
||||
// test only
|
||||
_state: MutableSignal<TState>;
|
||||
_commands: CommandRunnerContext<IGameContext<TState>>;
|
||||
_commands: CommandRunnerContextExport<IGameContext<TState>>;
|
||||
}
|
||||
|
||||
export function createGameContext<TState extends Record<string, unknown> = {} >(
|
||||
|
|
@ -31,7 +31,7 @@ export function createGameContext<TState extends Record<string, unknown> = {} >(
|
|||
): IGameContext<TState> {
|
||||
const stateValue = typeof initialState === 'function' ? initialState() : initialState ?? {} as TState;
|
||||
const state = mutableSignal(stateValue);
|
||||
let commands: CommandRunnerContext<IGameContext<TState>> = null as any;
|
||||
let commands: CommandRunnerContextExport<IGameContext<TState>> = null as any;
|
||||
|
||||
const context: IGameContext<TState> = {
|
||||
get value(): TState {
|
||||
|
|
@ -82,18 +82,30 @@ export function createGameCommandRegistry<TState extends Record<string, unknown>
|
|||
return createCommandRegistry<IGameContext<TState>>();
|
||||
}
|
||||
|
||||
export function registerGameCommand<TState extends Record<string, unknown> = {}, TResult = unknown>(
|
||||
type CmdFunc<TState extends Record<string, unknown> = {}> = (this: IGameContext<TState>, ...args: any[]) => Promise<unknown>;
|
||||
|
||||
export function registerGameCommand<TState extends Record<string, unknown> = {}, TFunc extends CmdFunc<TState> = CmdFunc<TState>>(
|
||||
registry: CommandRegistry<IGameContext<TState>>,
|
||||
schema: CommandSchema | string,
|
||||
run: (this: IGameContext<TState>, ...args: any[]) => Promise<TResult>
|
||||
run: TFunc
|
||||
) {
|
||||
const parsedSchema = typeof schema === 'string' ? parseCommandSchema(schema) : schema;
|
||||
registerCommand(registry, {
|
||||
schema: typeof schema === 'string' ? parseCommandSchema(schema) : schema,
|
||||
schema: parsedSchema,
|
||||
async run(this: CommandRunnerContext<IGameContext<TState>>, command: Command){
|
||||
const params = command.params;
|
||||
return await run.call(this.context, ...params);
|
||||
},
|
||||
});
|
||||
|
||||
return function(game: IGameContext<TState>, ...args: Parameters<TFunc>){
|
||||
return game.runParsed({
|
||||
options: {},
|
||||
params: args,
|
||||
flags: {},
|
||||
name: parsedSchema.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { GameHost, createGameHost } from './game-host';
|
||||
|
|
|
|||
|
|
@ -5,10 +5,36 @@ import { applyCommandSchema } from './command-validate';
|
|||
import { parseCommandSchema } from './schema-parse';
|
||||
import {AsyncQueue} from "@/utils/async-queue";
|
||||
|
||||
export type CommandRegistry<TContext> = Map<string, CommandRunner<TContext, unknown>>;
|
||||
type CanRunParsed = {
|
||||
runParsed<T=unknown>(command: Command): Promise<CommandResult<T>>,
|
||||
}
|
||||
|
||||
type CmdFunc<TContext> = (this: TContext, ...args: any[]) => Promise<unknown>;
|
||||
export class CommandRegistry<TContext> extends Map<string, CommandRunner<TContext>>{
|
||||
register<TFunc extends CmdFunc<TContext> = CmdFunc<TContext>>(schema: CommandSchema | string, run: TFunc) {
|
||||
const parsedSchema = typeof schema === 'string' ? parseCommandSchema(schema) : schema;
|
||||
registerCommand(this, {
|
||||
schema: parsedSchema,
|
||||
async run(this: CommandRunnerContext<TContext>, command: Command){
|
||||
const params = command.params;
|
||||
return await run.call(this.context, ...params);
|
||||
},
|
||||
});
|
||||
|
||||
type TResult = TFunc extends (this: TContext, ...args: any[]) => Promise<infer X> ? X : null;
|
||||
return function(ctx: TContext & CanRunParsed, ...args: Parameters<TFunc>){
|
||||
return ctx.runParsed({
|
||||
options: {},
|
||||
params: args,
|
||||
flags: {},
|
||||
name: parsedSchema.name,
|
||||
}) as Promise<CommandResult<TResult>>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createCommandRegistry<TContext>(): CommandRegistry<TContext> {
|
||||
return new Map();
|
||||
return new CommandRegistry();
|
||||
}
|
||||
|
||||
export function registerCommand<TContext, TResult>(
|
||||
|
|
@ -137,7 +163,7 @@ export function createCommandRunnerContext<TContext>(
|
|||
registry,
|
||||
context,
|
||||
run: <T=unknown>(input: string) => runCommandWithContext(runnerCtx, input) as Promise<CommandResult<T>>,
|
||||
runParsed: (command: Command) => runCommandParsedWithContext(runnerCtx, command),
|
||||
runParsed: <T=unknown>(command: Command) => runCommandParsedWithContext(runnerCtx, command) as Promise<CommandResult<T>>,
|
||||
prompt,
|
||||
on,
|
||||
off,
|
||||
|
|
|
|||
Loading…
Reference in New Issue