diff --git a/Design/WasmSplit.md b/Design/WasmSplit.md index 8f82e1377e8b0d2b34c31c52b4b18546d9d4f113..f8b8acb5dad9be3abaa533409ae93ab206316946 100644 --- a/Design/WasmSplit.md +++ b/Design/WasmSplit.md @@ -85,3 +85,30 @@ unity导出小游戏项目后,代码是在一个wasm文件里,经过brotli ### 关闭分包 如果想回退到未分包的版本,点击插件页的关闭代码分包按钮即可 + + +### 增量分包 + +从unity每一次导出后的小游戏,都需要重新分包,对于小版本改动会产生比较烦人的工作量 + +因此我们支持了增量分包,可以在二次导出时,通过选择之前已经进行过分包的版本,在之前分包的基础上进行增量分包 + + + +这里主要是通过symbol文件,按函数签名识别相同函数来实现的,因此需要导出时有symbol文件 + +**注意** + +由于unity 2021目前导出symbol的流程有问题,在unity修复之前,需要先手动执行下工具来导出symbol,具体见导出后的unity console信息。 + +### FAQ + +* iOS上进不去游戏 + +iOS由于加载子包的实现不同,所以最开始的收集会比较卡,这个时候可以观察分包插件面板,如果能看到有新增函数个数的变化,一般就是没问题的。如果新增函数较多(超过1000),可以先继续往下生成分包,再进行收集,这样可以加速已收集部分的运行。 + +* 没有看到增量分包的界面 + +由于增量分包是新增的功能,因此之前的项目不能被用来增量更新。更新分包插件后,第二次导出的版本开始才可以使用 + +另外对于unity 2021的版本,注意检查下minigame目录下有没有webgl.wasm.symbols.unityweb这个文件,没有的话注意按照导出时unity console的指引执行工具生成这个文件。 \ No newline at end of file diff --git a/image/wasmsplit/incremental-split.png b/image/wasmsplit/incremental-split.png new file mode 100755 index 0000000000000000000000000000000000000000..e17eb072743d2e8cb71a360077072ac94fb461bb Binary files /dev/null and b/image/wasmsplit/incremental-split.png differ diff --git a/tools/rewrite_exception_symbol.js b/tools/rewrite_exception_symbol.js new file mode 100644 index 0000000000000000000000000000000000000000..09b704f9c433dfe312bd24403588ede819585c5d --- /dev/null +++ b/tools/rewrite_exception_symbol.js @@ -0,0 +1,275 @@ +var fs = require("fs"); +var process = require("process"); + +function demangle(func) { + // var hasLibcxxabi = !!Module["___cxa_demangle"]; + var hasLibcxxabi = false; + // if (hasLibcxxabi) { + // try { + // var buf = _malloc(func.length); + // writeStringToMemory(func.substr(1), buf); + // var status = _malloc(4); + // var ret = Module["___cxa_demangle"](buf, 0, 0, status); + // if (getValue(status, "i32") === 0 && ret) { + // return Pointer_stringify(ret); + // } + // } catch (e) { + // } finally { + // if (buf) _free(buf); + // if (status) _free(status); + // if (ret) _free(ret); + // } + // } + var i = 3; + var basicTypes = { + v: "void", + b: "bool", + c: "char", + s: "short", + i: "int", + l: "long", + f: "float", + d: "double", + w: "wchar_t", + a: "signed char", + h: "unsigned char", + t: "unsigned short", + j: "unsigned int", + m: "unsigned long", + x: "long long", + y: "unsigned long long", + z: "...", + }; + var subs = []; + var first = true; + function dump(x) { + if (x) Module.print(x); + Module.print(func); + var pre = ""; + for (var a = 0; a < i; a++) pre += " "; + Module.print(pre + "^"); + } + function parseNested() { + i++; + if (func[i] === "K") i++; + var parts = []; + while (func[i] !== "E") { + if (func[i] === "S") { + i++; + var next = func.indexOf("_", i); + var num = func.substring(i, next) || 0; + parts.push(subs[num] || "?"); + i = next + 1; + continue; + } + if (func[i] === "C") { + parts.push(parts[parts.length - 1]); + i += 2; + continue; + } + var size = parseInt(func.substr(i)); + var pre = size.toString().length; + if (!size || !pre) { + i--; + break; + } + var curr = func.substr(i + pre, size); + parts.push(curr); + subs.push(curr); + i += pre + size; + } + i++; + return parts; + } + function parse(rawList, limit, allowVoid) { + limit = limit || Infinity; + var ret = "", + list = []; + function flushList() { + return "(" + list.join(", ") + ")"; + } + var name; + if (func[i] === "N") { + name = parseNested().join("::"); + limit--; + if (limit === 0) return rawList ? [name] : name; + } else { + if (func[i] === "K" || (first && func[i] === "L")) i++; + var size = parseInt(func.substr(i)); + if (size) { + var pre = size.toString().length; + name = func.substr(i + pre, size); + i += pre + size; + } + } + first = false; + if (func[i] === "I") { + i++; + var iList = parse(true); + var iRet = parse(true, 1, true); + ret += iRet[0] + " " + name + "<" + iList.join(", ") + ">"; + } else { + ret = name; + } + paramLoop: while (i < func.length && limit-- > 0) { + var c = func[i++]; + if (c in basicTypes) { + list.push(basicTypes[c]); + } else { + switch (c) { + case "P": + list.push(parse(true, 1, true)[0] + "*"); + break; + case "R": + list.push(parse(true, 1, true)[0] + "&"); + break; + case "L": { + i++; + var end = func.indexOf("E", i); + var size = end - i; + list.push(func.substr(i, size)); + i += size + 2; + break; + } + case "A": { + var size = parseInt(func.substr(i)); + i += size.toString().length; + if (func[i] !== "_") throw "?"; + i++; + list.push(parse(true, 1, true)[0] + " [" + size + "]"); + break; + } + case "E": + break paramLoop; + default: + ret += "?" + c; + break paramLoop; + } + } + } + if (!allowVoid && list.length === 1 && list[0] === "void") list = []; + if (rawList) { + if (ret) { + list.push(ret + "?"); + } + return list; + } else { + return ret + flushList(); + } + } + var parsed = func; + try { + if (func == "Object._main" || func == "_main") { + return "main()"; + } + // if (typeof func === "number") func = Pointer_stringify(func); + if (func[0] !== "_") return func; + if (func[1] !== "_") return func; + if (func[2] !== "Z") return func; + switch (func[3]) { + case "n": + return "operator new()"; + case "d": + return "operator delete()"; + } + parsed = parse(); + } catch (e) { + parsed += "?"; + } + if (parsed.indexOf("?") >= 0 && !hasLibcxxabi) { + // Runtime.warnOnce( + // "warning: a problem occurred in builtin C++ name demangling; build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling" + // ); + return func; + } + return parsed; +} + +function printHelp() { + console.log( + "Usage: node rewrite_exception_symbol.js \n" + ); +} + +if (process.argv.length < 4) { + printHelp(); + process.exit(0); +} + +var exceptionText = fs.readFileSync(process.argv[2], { encoding: "utf8" }); +var symbolText = fs.readFileSync(process.argv[3], { encoding: "utf8" }); + +function parseSymbol(symbolText) { + var symbolMap = new Map(); + var startLine = "var debugSymbols = {"; + var start = symbolText.indexOf(startLine); + start += startLine.length; + for (;;) { + var next = symbolText.indexOf(",", start); + var s = symbolText.substr(start, next - start).trim(); + // console.log("symbol line:", s); + var b = s.length > 0 ? s.charCodeAt(0) : 0; + if (b < 48 || b > 57) { + // not in [0-9] + break; + } + var mid = s.indexOf(":"); + if (mid < 0) { + break; + } + var left = s.substr(0, mid); + var right = s.substr(mid + 1, s.length - mid - 1); + if (right[0] === "'" && right[right.length - 1] === "'") { + right = right.substr(1, right.length - 2); + } + // console.log("symbol:", left, right); + right = demangle(right); + // console.log("after demangle:", right); + + // console.log("symbol:", start, mid, left, right); + symbolMap.set(left, right); + start = next + 1; + } + return symbolMap; +} + +var symbolMap = parseSymbol(symbolText); + +function replaceWithSymbol(src, symbolMap, regex) { + var res = src.matchAll(regex); + // console.log("to replace symbol:", src, regex); + var output = ""; + var start = 0; + for (;;) { + var d = res.next(); + if (d.value) { + var s = symbolMap.get(d.value[1]); + // console.log("to replace:", d.value, s, start); + if (s) { + output += src.substr(start, d.value.index - start); + output += s + ":wasm-function[" + d.value[1] + "]"; + } else { + output += src.substr(start, d.value.index - start); + output += d.value[0]; + } + start = d.value.index + d.value[0].length; + } + if (d.done) { + output += src.substr(start); + break; + } + } + return output; +} + +var output = replaceWithSymbol( + exceptionText, + symbolMap, + /j(\d+):wasm-function\[\d+]/g +); +output = replaceWithSymbol( + output, + symbolMap, + /:wasm-function\[(\d+)\]/g +); +console.log(output);