mirror of
https://github.com/galacean/engine.git
synced 2026-05-06 22:23:05 +08:00
refactor(shader-lab): tidy Preprocessor (single chunk cache, dead code cleanup)
- Merge _macroDefineIncludeMap and the would-be output cache into a single
_chunkCache: Map<path, {output, macros}>. Cache hits now skip the entire
parse() recursion instead of re-running _includeReg.replace.
- Drop the parseMacro=false parameter — it was a workaround for the
cache-not-cached-enough issue, no longer needed.
- Collapse _symbolReg + _funcCallReg into a single _referenceReg.
- Extract _registerDefine, drop dead !!valueRaw guard (the regex's [ ]+
before value already enforces non-empty capture).
- Collapse _mergeMacroDefineLists branches with ??=, also isolates cache
arrays from caller mutation.
Net: -35 lines, no behavior change, 24/24 shader-lab tests pass.
Performance unchanged within measurement noise.
This commit is contained in:
@@ -4,31 +4,18 @@ import { ShaderLib } from "@galacean/engine";
|
||||
import type { ASTNode } from "./parser/AST";
|
||||
|
||||
/**
|
||||
* Record for a single `#define` directive.
|
||||
*
|
||||
* Two shapes coexist and can be combined on the same entry:
|
||||
*
|
||||
* - **Expression macros** — `valueAst` holds an `AssignmentExpression` subtree
|
||||
* produced by the `macro_define` CFG rule. These participate in type
|
||||
* inference, varying flattening, and visitor-based codegen.
|
||||
* - **Legacy opaque macros** — the directive is emitted verbatim as a
|
||||
* `MACRO_DEFINE_EXPRESSION` token and expanded by the GLSL driver. `valueAst`
|
||||
* is undefined.
|
||||
*
|
||||
* `referenceName` captures the top-level external identifier the value refers
|
||||
* to, if any — the callee of a function-call value (`#define F foo(a,b)`
|
||||
* → `foo`) or the single-identifier value (`#define F foo` → `foo`). Empty
|
||||
* for numeric literals, qualifier fragments, or structurally complex values.
|
||||
* Populated by the regex pass; drives symbol-table lookup for macro call
|
||||
* sites, independent of whether the value also has an AST form.
|
||||
* Record for a single `#define` directive. `valueAst` is set for expression
|
||||
* macros (joined into the AST pipeline); opaque macros leave it undefined and
|
||||
* are emitted verbatim to the GLSL driver.
|
||||
*/
|
||||
export interface MacroDefineInfo {
|
||||
isFunction: boolean;
|
||||
name: string;
|
||||
params: string[];
|
||||
/** AST for expression-style macros. Absent for opaque macros. */
|
||||
valueAst?: ASTNode.AssignmentExpression;
|
||||
/** Top-level external identifier the value references, or empty string. */
|
||||
/** Leading identifier of the value (`#define F foo` → `foo`, `#define F foo(a)`
|
||||
* → `foo`), or empty for literals / operator expressions. Drives symbol-table
|
||||
* lookup at macro call sites. */
|
||||
referenceName: string;
|
||||
}
|
||||
|
||||
@@ -40,34 +27,34 @@ export class Preprocessor {
|
||||
private static readonly _includeReg = /^[ \t]*#include +"([\w\d./]+)"/gm;
|
||||
private static readonly _macroRegex =
|
||||
/^\s*#define\s+(\w+)[ ]*(\(([^)]*)\))?[ ]+(\(?\w+\)?.*?)(?:\/\/.*|\/\*.*?\*\/)?\s*$/gm;
|
||||
private static readonly _symbolReg = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||
private static readonly _funcCallReg = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*\)$/;
|
||||
private static readonly _macroDefineIncludeMap = new Map<string, MacroDefineList>();
|
||||
// Matches a bare identifier or a function-call whole-value form only.
|
||||
// Mixed-operator values (`a + b`) are intentionally rejected.
|
||||
private static readonly _referenceReg = /^([a-zA-Z_]\w*)(?:\s*\(.*\))?$/;
|
||||
private static readonly _chunkCache = new Map<string, { output: string; macros: MacroDefineList }>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
static _repeatIncludeSet = new Set<string>();
|
||||
|
||||
static parse(
|
||||
source: string,
|
||||
basePathForIncludeKey: string,
|
||||
outMacroDefineList: MacroDefineList,
|
||||
parseMacro = true
|
||||
): string {
|
||||
if (parseMacro) {
|
||||
this._parseMacroDefines(source, outMacroDefineList);
|
||||
}
|
||||
static parse(source: string, basePathForIncludeKey: string, outMacroDefineList: MacroDefineList): string {
|
||||
this._parseMacroDefines(source, outMacroDefineList);
|
||||
return source.replace(this._includeReg, (_, includeName) =>
|
||||
this._replace(includeName, basePathForIncludeKey, outMacroDefineList)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* For each registered definition of `macroName`, push its `referenceName`
|
||||
* (when non-empty and not a macro parameter) so call sites can drive
|
||||
* symbol-table lookup.
|
||||
*/
|
||||
private static _parseMacroDefines(source: string, outMacroList: MacroDefineList): void {
|
||||
this._macroRegex.lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = this._macroRegex.exec(source)) !== null) {
|
||||
const [, name, paramsGroup, paramsStr, valueRaw] = match;
|
||||
this._registerDefine(outMacroList, name, paramsGroup ? paramsStr : undefined, valueRaw);
|
||||
}
|
||||
}
|
||||
|
||||
/** Collect unique `referenceName`s of `macroName`'s definitions, skipping
|
||||
* names that shadow a macro parameter. */
|
||||
static getReferenceSymbolNames(macroDefineList: MacroDefineList, macroName: string, out: string[]): void {
|
||||
out.length = 0;
|
||||
const infos = macroDefineList[macroName];
|
||||
@@ -82,15 +69,9 @@ export class Preprocessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the top-level identifier a value refers to, or empty string if
|
||||
* the value is a numeric literal, qualifier fragment, or structurally
|
||||
* complex form we don't try to introspect.
|
||||
*/
|
||||
private static _extractReferenceName(value: string): string {
|
||||
if (this._symbolReg.test(value)) return value;
|
||||
const callMatch = this._funcCallReg.exec(value);
|
||||
return callMatch ? callMatch[1] : "";
|
||||
const match = this._referenceReg.exec(value);
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
private static _isExist(list: MacroDefineInfo[], item: MacroDefineInfo): boolean {
|
||||
@@ -103,45 +84,36 @@ export class Preprocessor {
|
||||
);
|
||||
}
|
||||
|
||||
private static _parseMacroDefines(source: string, outMacroList: MacroDefineList): void {
|
||||
let match: RegExpExecArray | null;
|
||||
this._macroRegex.lastIndex = 0;
|
||||
private static _registerDefine(
|
||||
outMacroList: MacroDefineList,
|
||||
name: string,
|
||||
paramsStr: string | undefined,
|
||||
valueRaw: string
|
||||
): void {
|
||||
// `paramsStr` is `undefined` for object-like macros and the `()` contents
|
||||
// (possibly empty) for function-like ones.
|
||||
const isFunction = paramsStr !== undefined;
|
||||
const params = paramsStr ? paramsStr.split(",").map((p) => p.trim()).filter(Boolean) : [];
|
||||
const value = valueRaw.trim();
|
||||
const referenceName = value ? this._extractReferenceName(value) : "";
|
||||
|
||||
while ((match = this._macroRegex.exec(source)) !== null) {
|
||||
const [, name, paramsGroup, paramsStr, valueRaw] = match;
|
||||
const isFunction = !!paramsGroup && !!valueRaw;
|
||||
const params =
|
||||
isFunction && paramsStr
|
||||
? paramsStr
|
||||
.split(",")
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const value = valueRaw ? valueRaw.trim() : "";
|
||||
const referenceName = value ? this._extractReferenceName(value) : "";
|
||||
const info: MacroDefineInfo = { isFunction, name, params, referenceName };
|
||||
|
||||
const info: MacroDefineInfo = { isFunction, name, params, referenceName };
|
||||
|
||||
const arr = outMacroList[name];
|
||||
if (arr) {
|
||||
if (!this._isExist(arr, info)) arr.push(info);
|
||||
} else {
|
||||
outMacroList[name] = [info];
|
||||
}
|
||||
const arr = outMacroList[name];
|
||||
if (arr) {
|
||||
if (!this._isExist(arr, info)) arr.push(info);
|
||||
} else {
|
||||
outMacroList[name] = [info];
|
||||
}
|
||||
}
|
||||
|
||||
private static _mergeMacroDefineLists(from: MacroDefineList, to: MacroDefineList): void {
|
||||
for (const macroName in from) {
|
||||
if (to[macroName]) {
|
||||
const target = to[macroName];
|
||||
const src = from[macroName];
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
const info = src[i];
|
||||
if (!this._isExist(target, info)) target.push(info);
|
||||
}
|
||||
} else {
|
||||
to[macroName] = from[macroName];
|
||||
for (const name in from) {
|
||||
const target = to[name] ?? (to[name] = []);
|
||||
const src = from[name];
|
||||
for (let i = 0, n = src.length; i < n; i++) {
|
||||
const info = src[i];
|
||||
if (!this._isExist(target, info)) target.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,15 +142,14 @@ export class Preprocessor {
|
||||
}
|
||||
this._repeatIncludeSet.add(path);
|
||||
|
||||
if (this._macroDefineIncludeMap.has(path)) {
|
||||
this._mergeMacroDefineLists(this._macroDefineIncludeMap.get(path)!, outMacroDefineList);
|
||||
} else {
|
||||
const chunkMacroDefineList: MacroDefineList = {};
|
||||
this._parseMacroDefines(chunk, chunkMacroDefineList);
|
||||
this._macroDefineIncludeMap.set(path, chunkMacroDefineList);
|
||||
this._mergeMacroDefineLists(chunkMacroDefineList, outMacroDefineList);
|
||||
let entry = this._chunkCache.get(path);
|
||||
if (!entry) {
|
||||
const macros: MacroDefineList = {};
|
||||
const output = this.parse(chunk, basePathForIncludeKey, macros);
|
||||
entry = { output, macros };
|
||||
this._chunkCache.set(path, entry);
|
||||
}
|
||||
|
||||
return this.parse(chunk, basePathForIncludeKey, outMacroDefineList, false);
|
||||
this._mergeMacroDefineLists(entry.macros, outMacroDefineList);
|
||||
return entry.output;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user