refactor: 收口生成进度 tick 判定
This commit is contained in:
@@ -498,6 +498,7 @@ import type {
|
||||
import { PlatformEntryWorldDetailView } from './PlatformEntryWorldDetailView';
|
||||
import { PlatformErrorDialog } from './PlatformErrorDialog';
|
||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
||||
import { resolvePlatformGenerationProgressTickDecision } from './platformGenerationProgressTickModel';
|
||||
import {
|
||||
buildMatch3DProfileFromSession,
|
||||
hasMatch3DRuntimeAsset,
|
||||
@@ -3807,32 +3808,25 @@ export function PlatformEntryFlowShellImpl({
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const activeGenerationState =
|
||||
selectionStage === 'puzzle-generating'
|
||||
? puzzleGenerationState
|
||||
: selectionStage === 'match3d-generating'
|
||||
? match3dGenerationState
|
||||
: selectionStage === 'big-fish-generating'
|
||||
? bigFishGenerationState
|
||||
: selectionStage === 'square-hole-generating'
|
||||
? squareHoleGenerationState
|
||||
: selectionStage === 'jump-hop-generating'
|
||||
? jumpHopGenerationState
|
||||
: selectionStage === 'wooden-fish-generating'
|
||||
? woodenFishGenerationState
|
||||
: selectionStage === 'baby-object-match-generating'
|
||||
? babyObjectMatchGenerationState
|
||||
: null;
|
||||
const shouldTickProgress =
|
||||
selectionStage === 'visual-novel-generating'
|
||||
? visualNovelGenerationStartedAtMs != null &&
|
||||
visualNovelGenerationPhase !== 'ready' &&
|
||||
visualNovelGenerationPhase !== 'failed'
|
||||
: activeGenerationState != null &&
|
||||
activeGenerationState.phase !== 'ready' &&
|
||||
activeGenerationState.phase !== 'failed';
|
||||
const progressTickDecision =
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage,
|
||||
miniGameStates: {
|
||||
puzzle: puzzleGenerationState,
|
||||
match3d: match3dGenerationState,
|
||||
'big-fish': bigFishGenerationState,
|
||||
'square-hole': squareHoleGenerationState,
|
||||
'jump-hop': jumpHopGenerationState,
|
||||
'wooden-fish': woodenFishGenerationState,
|
||||
'baby-object-match': babyObjectMatchGenerationState,
|
||||
},
|
||||
visualNovel: {
|
||||
startedAtMs: visualNovelGenerationStartedAtMs,
|
||||
phase: visualNovelGenerationPhase,
|
||||
},
|
||||
});
|
||||
|
||||
if (!shouldTickProgress) {
|
||||
if (!progressTickDecision.shouldTick) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import type {
|
||||
MiniGameDraftGenerationKind,
|
||||
MiniGameDraftGenerationPhase,
|
||||
MiniGameDraftGenerationState,
|
||||
} from '../../services/miniGameDraftGenerationProgress';
|
||||
import type { SelectionStage } from './platformEntryTypes';
|
||||
import { resolvePlatformGenerationProgressTickDecision } from './platformGenerationProgressTickModel';
|
||||
|
||||
function buildGenerationState(
|
||||
kind: MiniGameDraftGenerationKind,
|
||||
phase: MiniGameDraftGenerationPhase = 'compile',
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
kind,
|
||||
phase,
|
||||
startedAtMs: 1000,
|
||||
completedAssetCount: 0,
|
||||
totalAssetCount: 1,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe('platformGenerationProgressTickModel', () => {
|
||||
test('ticks while a mini-game generation stage has a running state', () => {
|
||||
const cases: Array<
|
||||
[stage: SelectionStage, kind: MiniGameDraftGenerationKind]
|
||||
> = [
|
||||
['puzzle-generating', 'puzzle'],
|
||||
['match3d-generating', 'match3d'],
|
||||
['big-fish-generating', 'big-fish'],
|
||||
['square-hole-generating', 'square-hole'],
|
||||
['jump-hop-generating', 'jump-hop'],
|
||||
['wooden-fish-generating', 'wooden-fish'],
|
||||
['baby-object-match-generating', 'baby-object-match'],
|
||||
];
|
||||
|
||||
for (const [selectionStage, kind] of cases) {
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage,
|
||||
miniGameStates: {
|
||||
[kind]: buildGenerationState(kind),
|
||||
},
|
||||
visualNovel: {
|
||||
startedAtMs: null,
|
||||
phase: 'generating',
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: kind,
|
||||
shouldTick: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('does not tick mini-game generation when state is missing or terminal', () => {
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage: 'puzzle-generating',
|
||||
miniGameStates: {},
|
||||
visualNovel: {
|
||||
startedAtMs: null,
|
||||
phase: 'generating',
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: 'puzzle',
|
||||
shouldTick: false,
|
||||
});
|
||||
|
||||
for (const phase of ['ready', 'failed'] as const) {
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage: 'puzzle-generating',
|
||||
miniGameStates: {
|
||||
puzzle: buildGenerationState('puzzle', phase),
|
||||
},
|
||||
visualNovel: {
|
||||
startedAtMs: null,
|
||||
phase: 'generating',
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: 'puzzle',
|
||||
shouldTick: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('does not tick when stage and mini-game state do not match', () => {
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage: 'puzzle-generating',
|
||||
miniGameStates: {
|
||||
match3d: buildGenerationState('match3d'),
|
||||
},
|
||||
visualNovel: {
|
||||
startedAtMs: null,
|
||||
phase: 'generating',
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: 'puzzle',
|
||||
shouldTick: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('ticks visual novel generation only after it has started and before terminal phases', () => {
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage: 'visual-novel-generating',
|
||||
miniGameStates: {},
|
||||
visualNovel: {
|
||||
startedAtMs: 1000,
|
||||
phase: 'generating',
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: 'visual-novel',
|
||||
shouldTick: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage: 'visual-novel-generating',
|
||||
miniGameStates: {},
|
||||
visualNovel: {
|
||||
startedAtMs: null,
|
||||
phase: 'generating',
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: 'visual-novel',
|
||||
shouldTick: false,
|
||||
});
|
||||
|
||||
for (const phase of ['ready', 'failed'] as const) {
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage: 'visual-novel-generating',
|
||||
miniGameStates: {},
|
||||
visualNovel: {
|
||||
startedAtMs: 1000,
|
||||
phase,
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: 'visual-novel',
|
||||
shouldTick: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('does not tick non-generation stages even when states are present', () => {
|
||||
expect(
|
||||
resolvePlatformGenerationProgressTickDecision({
|
||||
selectionStage: 'platform',
|
||||
miniGameStates: {
|
||||
puzzle: buildGenerationState('puzzle'),
|
||||
},
|
||||
visualNovel: {
|
||||
startedAtMs: 1000,
|
||||
phase: 'generating',
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
activeKind: null,
|
||||
shouldTick: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import type {
|
||||
MiniGameDraftGenerationKind,
|
||||
MiniGameDraftGenerationState,
|
||||
} from '../../services/miniGameDraftGenerationProgress';
|
||||
import type { SelectionStage } from './platformEntryTypes';
|
||||
|
||||
export type PlatformVisualNovelGenerationPhase =
|
||||
| 'generating'
|
||||
| 'ready'
|
||||
| 'failed';
|
||||
|
||||
export type PlatformGenerationProgressTickKind =
|
||||
| MiniGameDraftGenerationKind
|
||||
| 'visual-novel';
|
||||
|
||||
export type PlatformGenerationProgressTickInput = {
|
||||
selectionStage: SelectionStage;
|
||||
miniGameStates: Partial<
|
||||
Record<MiniGameDraftGenerationKind, MiniGameDraftGenerationState | null>
|
||||
>;
|
||||
visualNovel: {
|
||||
startedAtMs: number | null;
|
||||
phase: PlatformVisualNovelGenerationPhase;
|
||||
};
|
||||
};
|
||||
|
||||
export type PlatformGenerationProgressTickDecision = {
|
||||
activeKind: PlatformGenerationProgressTickKind | null;
|
||||
shouldTick: boolean;
|
||||
};
|
||||
|
||||
const MINI_GAME_GENERATION_STAGE_TO_KIND: Partial<
|
||||
Record<SelectionStage, MiniGameDraftGenerationKind>
|
||||
> = {
|
||||
'puzzle-generating': 'puzzle',
|
||||
'match3d-generating': 'match3d',
|
||||
'big-fish-generating': 'big-fish',
|
||||
'square-hole-generating': 'square-hole',
|
||||
'jump-hop-generating': 'jump-hop',
|
||||
'wooden-fish-generating': 'wooden-fish',
|
||||
'baby-object-match-generating': 'baby-object-match',
|
||||
};
|
||||
|
||||
function shouldTickMiniGameGenerationState(
|
||||
state: MiniGameDraftGenerationState | null | undefined,
|
||||
) {
|
||||
return state != null && state.phase !== 'ready' && state.phase !== 'failed';
|
||||
}
|
||||
|
||||
/** 收口生成页进度 tick 判定,壳层只保留 interval 副作用。 */
|
||||
export function resolvePlatformGenerationProgressTickDecision(
|
||||
input: PlatformGenerationProgressTickInput,
|
||||
): PlatformGenerationProgressTickDecision {
|
||||
if (input.selectionStage === 'visual-novel-generating') {
|
||||
return {
|
||||
activeKind: 'visual-novel',
|
||||
shouldTick:
|
||||
input.visualNovel.startedAtMs != null &&
|
||||
input.visualNovel.phase !== 'ready' &&
|
||||
input.visualNovel.phase !== 'failed',
|
||||
};
|
||||
}
|
||||
|
||||
const activeKind =
|
||||
MINI_GAME_GENERATION_STAGE_TO_KIND[input.selectionStage] ?? null;
|
||||
if (!activeKind) {
|
||||
return {
|
||||
activeKind: null,
|
||||
shouldTick: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
activeKind,
|
||||
shouldTick: shouldTickMiniGameGenerationState(
|
||||
input.miniGameStates[activeKind],
|
||||
),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user