Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/parser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ import {

export default class SceneParser {
private readonly SCRIPT_CONFIG_MAP: ConfigMap;
/**
* @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
* `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
*/
constructor(
private readonly assetsPrefetcher: (assetList: IAsset[]) => void,
private readonly assetsPrefetcher: ((assetList: IAsset[]) => void) | undefined,
private readonly assetSetter: (
fileName: string,
assetType: fileType,
Expand Down
8 changes: 4 additions & 4 deletions packages/parser/src/interface/sceneInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export interface ISentence {
commandRaw: string; // 命令的原始内容,方便调试
content: string; // 语句内容
args: Array<arg>; // 参数列表
sentenceAssets: Array<IAsset>; // 语句携带的资源列表
subScene: Array<string>; // 语句包含子场景列表
sentenceAssets: Array<IAsset>; // 语句携带的资源列表;SceneParser 未提供 assetsPrefetcher 时为空
subScene: Array<string>; // 语句包含子场景列表;未提供 assetsPrefetcher 时为空
inlineComment: string; // 行内注释
/**
* 本语句在原始场景文件中占据的行范围(0-based,含首尾)。
Expand All @@ -94,8 +94,8 @@ export interface IScene {
sceneName: string; // 场景名称
sceneUrl: string; // 场景url
sentenceList: Array<ISentence>; // 语句列表
assetsList: Array<IAsset>; // 资源列表
subSceneList: Array<string>; // 子场景的url列表
assetsList: Array<IAsset>; // 资源列表;未提供 assetsPrefetcher 时为空
subSceneList: Array<string>; // 子场景的url列表;未提供 assetsPrefetcher 时为空
}

/**
Expand Down
17 changes: 13 additions & 4 deletions packages/parser/src/sceneParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ export const sceneParser = (
rawScene: string,
sceneName: string,
sceneUrl: string,
assetsPrefetcher: (assetList: Array<IAsset>) => void,
assetsPrefetcher: ((assetList: Array<IAsset>) => void) | undefined,
assetSetter: (fileName: string, assetType: fileType) => string,
ADD_NEXT_ARG_LIST: commandType[],
SCRIPT_CONFIG_MAP: ConfigMap,
): IScene => {
// 没有预取回调时不需要场景级资源列表,连同每条语句的资源扫描一起跳过
const collectAssets = assetsPrefetcher !== undefined;

// 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行,
// 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。
const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表
Expand All @@ -50,7 +53,11 @@ export const sceneParser = (
ADD_NEXT_ARG_LIST,
SCRIPT_CONFIG_MAP,
index,
collectAssets,
);
if (!collectAssets) {
return returnSentence;
}
// 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList。
// 必须就地追加:如果写成 [...assetsList, ...sentenceAssets],每条语句都要复制
// 一次已累积的列表,整体退化成 Θ(资源数²)——资源密集的场景脚本(几乎每条语句
Expand All @@ -67,9 +74,11 @@ export const sceneParser = (

markMultilineRanges(sentenceList, rawSentenceList);

// 开始资源的预加载
assetsList = deduplicateAssets(assetsList);
assetsPrefetcher(assetsList);
if (collectAssets) {
// 开始资源的预加载
assetsList = deduplicateAssets(assetsList);
assetsPrefetcher(assetsList);
}

return {
sceneName: sceneName, // 场景名称
Expand Down
7 changes: 5 additions & 2 deletions packages/parser/src/scriptParser/scriptParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import { ConfigMap } from '../config/scriptConfig';
* @param assetSetter
* @param ADD_NEXT_ARG_LIST
* @param SCRIPT_CONFIG_MAP
* @param lineNumber 语句所在行号
* @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
*/
export const scriptParser = (
sentenceRaw: string,
assetSetter: any,
ADD_NEXT_ARG_LIST: commandType[],
SCRIPT_CONFIG_MAP: ConfigMap,
lineNumber = 0,
collectAssets = true,
): ISentence => {
let command: commandType; // 默认为对话
let content: string; // 语句内容
Expand Down Expand Up @@ -109,8 +112,8 @@ export const scriptParser = (
}

content = contentParser(newSentenceRaw.trim(), command, assetSetter); // 将语句内容里的文件名转为相对或绝对路径
sentenceAssets = assetsScanner(command, content, args, lineNumber); // 扫描语句携带资源
subScene = subSceneScanner(command, content); // 扫描语句携带子场景
sentenceAssets = collectAssets ? assetsScanner(command, content, args, lineNumber) : []; // 扫描语句携带资源
subScene = collectAssets ? subSceneScanner(command, content) : []; // 扫描语句携带子场景
return {
command: command, // 语句类型
commandRaw: commandRaw.trim(), // 命令原始内容,方便调试
Expand Down
91 changes: 91 additions & 0 deletions packages/parser/test/parserOptionalAssets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { expect, test } from 'vitest';

import { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG } from '../src/config/scriptConfig';
import SceneParser from '../src/index';
import { IAsset } from '../src/interface/sceneInterface';

const SCENE = `changeBg:a.webp;
character:hello -vocal=b.mp3;
callScene:chapter.txt;`;

const identityAssetSetter = (fileName: string) => fileName;

test('省略 assetsPrefetcher 时资源与子场景都不收集', () => {
const parser = new SceneParser(
undefined,
identityAssetSetter,
ADD_NEXT_ARG_LIST,
SCRIPT_CONFIG,
);

const result = parser.parse(SCENE, 'test', 'test');

expect(result.sentenceList).toHaveLength(3);
expect(result.assetsList).toEqual([]);
expect(result.subSceneList).toEqual([]);
expect(result.sentenceList.map((sentence) => sentence.sentenceAssets)).toEqual([
[],
[],
[],
]);
expect(result.sentenceList.map((sentence) => sentence.subScene)).toEqual([
[],
[],
[],
]);
});

test('提供 assetsPrefetcher 时收集行为与之前一致', () => {
let prefetched: IAsset[] = [];
const parser = new SceneParser(
(assetList) => {
prefetched = assetList;
},
identityAssetSetter,
ADD_NEXT_ARG_LIST,
SCRIPT_CONFIG,
);

const result = parser.parse(SCENE, 'test', 'test');

expect(result.assetsList.map((asset) => asset.url)).toEqual([
'a.webp',
'b.mp3',
]);
expect(prefetched).toEqual(result.assetsList);
expect(result.subSceneList).toEqual(['chapter.txt']);
expect(result.sentenceList[1]?.sentenceAssets).toHaveLength(1);
expect(result.sentenceList[2]?.subScene).toEqual(['chapter.txt']);
});

test('跳过资源收集不影响语句切分与行范围', () => {
const multiLineScene = [
'changeFigure:hero.png',
' -id=hero -left;',
'say:next;',
].join('\n');

const withAssets = new SceneParser(
() => { },
identityAssetSetter,
ADD_NEXT_ARG_LIST,
SCRIPT_CONFIG,
).parse(multiLineScene, 'test', 'test');
const withoutAssets = new SceneParser(
undefined,
identityAssetSetter,
ADD_NEXT_ARG_LIST,
SCRIPT_CONFIG,
).parse(multiLineScene, 'test', 'test');

const pickStructure = (scene: typeof withAssets) =>
scene.sentenceList.map((sentence) => [
sentence.command,
sentence.content,
sentence.startLine,
sentence.endLine,
sentence.args,
]);

expect(pickStructure(withoutAssets)).toEqual(pickStructure(withAssets));
});
Loading