UNPKG

unity-find-fault

Version:

A tool to find fault in unity project.

244 lines 10.9 kB
import fg from "fast-glob"; import fs from "fs-extra"; import path from "path"; import sizeOf from "image-size"; import { toolchain } from "../toolchain.js"; import { UnityHelper } from "./UnityHelper.js"; import { standardPath } from "./vendor.js"; import normalizePath from "normalize-path"; import _ from "lodash"; export class ImageSorter { refStatusDescMap = { ["" /* ERefStatus.None */]: '', ["\u221A" /* ERefStatus.Exclusive */]: '√', ["*" /* ERefStatus.Bad */]: '*' }; async sortAtlas() { // const allFormOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.allForm.txt`; // await fs.ensureFile(allFormOutFile); // await fs.writeFile(allFormOutFile, '', 'utf-8'); // await UIRelations.build(); // const tabForms = UIRelations.getAllForms(); // tabForms.forEach(v => { // fs.appendFileSync(allFormOutFile, `${v.tsClass.className}: ${v.children.map(v => v.tsClass.className).join(', ')}\n`); // }) // } // private async ttt(): Promise<void> { const dirRefOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.dirRef.txt`; const uiRefOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.uiRef.txt`; const solutionOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.solution.txt`; if (!toolchain.opts.output) { await fs.ensureFile(dirRefOutFile); await fs.writeFile(dirRefOutFile, '', 'utf-8'); await fs.ensureFile(uiRefOutFile); await fs.writeFile(uiRefOutFile, '', 'utf-8'); await fs.ensureFile(solutionOutFile); await fs.writeFile(solutionOutFile, '', 'utf-8'); } const infoMap = {}; // collect image infomations const imgRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources'); const imgs = await fg(['**/*.png', '**/*.jpg'], { cwd: imgRoot }); for (const img of imgs) { if (!img.startsWith('ui') && !img.startsWith('images')) continue; const file = path.join(imgRoot, img); const guid = await UnityHelper.readGUID(file + '.meta'); // get dimensions const dimensions = await sizeOf(file); if (dimensions.width == null || dimensions.height == null) { console.error('get dimensions failed: ', img); continue; } const info = { guid, width: dimensions.width, height: dimensions.height, size: dimensions.width * dimensions.height, refers: [] }; infoMap[file] = info; } // get references const uiRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources/ui/system'); const referMap = await UnityHelper.collectUsedGUID(uiRoot); // turn to Record<dir, IDirInfo>, Record<ui, dirs> const dirMap = {}, ui2dirs = {}; for (const img in infoMap) { const dir = path.dirname(img); // dir map let dinfo = dirMap[dir]; if (dinfo == null) { dirMap[dir] = dinfo = { path: dir, imgs: [], totalSize: 0, referedPrefabs: [] }; } dinfo.imgs.push(img); dinfo.totalSize += infoMap[img].size; // ui map const info = infoMap[img]; const refers = referMap[info.guid]; if (refers == null) { continue; } for (const ui of refers) { if (!dinfo.referedPrefabs.includes(ui)) dinfo.referedPrefabs.push(ui); let dirs = ui2dirs[ui]; if (dirs == null) { ui2dirs[ui] = dirs = []; } const d = dirs.find(v => v.path == dir); if (d != null) { d.imgs.push(img); } else { dirs.push({ path: dir, imgs: [img] }); } } } const allLowerCaseDirs = Object.keys(dirMap).map(v => v.toLowerCase()); // check ui const uiReports = []; for (const ui in ui2dirs) { const dirs = ui2dirs[ui]; if (dirs.length <= 1) continue; const referDirs = []; for (const d of dirs) { // if (!d.path.includes('atlas\\common_')) { let s = 0; d.imgs.forEach(v => s += infoMap[v].size); const dinfo = dirMap[d.path]; const sizePct = Math.round(s / dinfo.totalSize * 100); let status = "" /* ERefStatus.None */; if (dinfo.referedPrefabs.length == 1) { // 专属目录 status = "\u221A" /* ERefStatus.Exclusive */; } else if (sizePct < 30) { status = "*" /* ERefStatus.Bad */; } referDirs.push({ ...d, cntPct: d.imgs.length / dinfo.imgs.length, cntPctDesc: `${d.imgs.length}/${dinfo.imgs.length}`, sizePct, status }); // } } if (referDirs.length > 0) { const score = _.meanBy(referDirs, 'sizePct'); uiReports.push({ prefab: path.basename(ui, '.prefab'), refs: referDirs, score }); } } uiReports.sort((a, b) => a.score - b.score); if (!toolchain.opts.output) { uiReports.forEach(v => { const refLines = v.refs.map(v => ` ${this.refStatusDescMap[v.status]} ${normalizePath(path.relative(imgRoot, v.path))}(${v.cntPctDesc}, ${v.sizePct}%): ${v.imgs.map(v => path.basename(v)).join(', ')}`); fs.appendFileSync(uiRefOutFile, `${v.prefab} (${v.refs.length}): \n${refLines.join('\n')}\n`); }); } // check dirs const dirReports = []; for (const dir in dirMap) { // if (dir.includes('atlas\\common_')) { // continue; // } const dinfo = dirMap[dir]; if (dinfo.referedPrefabs.length > 1) { dirReports.push(dinfo); } } dirReports.sort((a, b) => b.referedPrefabs.length - a.referedPrefabs.length); if (!toolchain.opts.output) { dirReports.forEach(d => { fs.appendFileSync(dirRefOutFile, `${normalizePath(path.relative(imgRoot, d.path))} (${d.referedPrefabs.length}): ${d.referedPrefabs.map(v => path.basename(v, '.prefab')).join(', ')}\n`); }); } // suggestion // // 检查是否被多个ui使用(相互不重叠),这样可以直接拆分 // const splitMap: Record<string, string> = {}; // for (const dir in dirMap) { // if (dir.includes('atlas\\common_')) { // continue; // } // const dinfo = dirMap[dir]; // if (dinfo.referedPrefabs.length > 1) { // for (const p of dinfo.referedPrefabs) { // const uiDirs = ui2dirs[p]; // const ud = uiDirs.find(v => v.path == dir); // for (const img of ud!.imgs) { // if (splitMap[img] == null) { // splitMap[img] = p; // } else { // splitMap[img] = '__SHARED__'; // } // } // } // } // } // // 生成split solution // const splitSolutions: ISplitSolution[] = []; // for (const img in splitMap) { // const prefab = splitMap[img]; // if (prefab == '__SHARED__') { // continue; // } // let solution = splitSolutions.find(v => v.prefab == prefab); // if (solution == null) { // solution = { prefab, imgs: [] }; // splitSolutions.push(solution); // } // solution.imgs.push(img); // } // // 执行split solution // for (const ss of splitSolutions) { // const prefabName = path.basename(ss.prefab, '.prefab'); // const uiDirs = ui2dirs[ss.prefab]; // const td = uiDirs.find(v => dirMap[v.path].referedPrefabs.length == 1); // let target = '', newTarget = false; // if (td) { // target = td.path; // } else { // const tryDir = path.join(specialRoot, prefabName); // target = tryDir; // let tryCnt = 0; // while (allLowerCaseDirs.includes(target.toLowerCase())) { // target = tryDir + tryCnt; // tryCnt++; // } // newTarget = true; // } // for (const img of ss.imgs) { // const bdn = path.basename(path.dirname(img)); // if (bdn.toLowerCase() == prefabName.toLowerCase()) { // continue; // } // if (newTarget) { // await fs.appendFile(solutionOutFile, `mkdir "${standardPath(target)}"\n`); // await fs.appendFile(solutionOutFile, `svn add "${standardPath(target)}"\n`); // newTarget = false; // } // await fs.appendFile(solutionOutFile, `svn mv "${standardPath(img)}" "${standardPath(path.join(target, path.basename(img)))}"\n`); // } // } // output if (toolchain.opts.output) { await fs.ensureDir(toolchain.opts.output); let op = toolchain.opts.output; if (!path.isAbsolute(op)) { op = path.join(toolchain.opts.projectRoot, op); } uiReports.forEach(v => { v.refs.forEach(r => { r.path = normalizePath(standardPath(r.path)); r.imgs = r.imgs.map(i => normalizePath(standardPath(i))); }); }); const uiRefOutJson = path.join(op, '__uiRef.json'); await fs.writeJSON(uiRefOutJson, { uis: uiReports }); dirReports.forEach(v => { v.path = normalizePath(standardPath(v.path)); v.imgs = v.imgs.map(i => normalizePath(standardPath(i))); v.referedPrefabs = v.referedPrefabs.map(r => normalizePath(standardPath(r))); }); const dirRefOutJson = path.join(op, '__dirRef.json'); await fs.writeJSON(dirRefOutJson, { dirs: dirReports }); } } } //# sourceMappingURL=ImageSorter.js.map