资源分离

This commit is contained in:
unanmed 2023-06-04 23:06:56 +08:00
parent 39c655d355
commit 1bdfb9be7d
14 changed files with 245 additions and 18 deletions

3
.gitignore vendored
View File

@ -34,4 +34,5 @@ dist.rar
index.cjs
!public/swap/*.h5save
_bundle
out
out
dist-resource

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,13 @@
import fs from 'fs-extra';
import { extname, resolve } from 'path';
import { formatSize } from './utils.js';
(async function () {
const dir = process.argv[2] || './src';
const dir = process.argv.slice(2) || ['./src'];
let totalLines = 0;
let totalFiles = 0;
const list: Record<string, [number, number]> = {};
let totalSize = 0;
const list: Record<string, [number, number, number]> = {};
const ignoreDir = [
'node_modules',
'floors',
@ -36,32 +38,38 @@ import { extname, resolve } from 'path';
const file = await fs.readFile(resolve(dir, one), 'utf-8');
const lines = file.split('\n').length;
const ext = extname(one);
list[ext] ??= [0, 0];
list[ext] ??= [0, 0, 0];
list[ext][0]++;
list[ext][1] += lines;
list[ext][2] += stat.size;
totalLines += lines;
totalFiles++;
totalSize += stat.size;
}
} else {
await check(resolve(dir, one));
}
}
};
await check(dir);
await Promise.all(dir.map(v => check(v)));
const sorted = Object.entries(list).sort((a, b) => {
return a[1][1] - b[1][1];
});
for (const [ext, [file, lines]] of sorted) {
for (const [ext, [file, lines, size]] of sorted) {
console.log(
`${ext.slice(1).padEnd(7, ' ')}files: ${file
.toString()
.padEnd(6, ' ')}lines: ${lines}`
.padEnd(6, ' ')}lines: ${lines
.toString()
.padEnd(9, ' ')}size: ${formatSize(size)}`
);
}
console.log(
`\x1b[33mtotal files: ${totalFiles
.toString()
.padEnd(6, ' ')}lines: ${totalLines}\x1b[0m`
.padEnd(6, ' ')}lines: ${totalLines
.toString()
.padEnd(9, ' ')}size: ${formatSize(totalSize)}\x1b[0m`
);
})();

View File

@ -3,8 +3,8 @@ import { uniqueSymbol } from './utils.js';
import { extname, resolve } from 'path';
type ResorceType =
| 'bgm'
| 'sound'
| 'bgms'
| 'sounds'
| 'autotiles'
| 'images'
| 'materials'
@ -12,9 +12,8 @@ type ResorceType =
| 'animates'
| 'fonts';
let resorceIndex = 0;
const compress: ResorceType[] = [
'sound',
'sounds',
'animates',
'autotiles',
'images',
@ -24,13 +23,16 @@ const compress: ResorceType[] = [
const SYMBOL = uniqueSymbol();
const MAX_SIZE = 100 * (1 << 20);
const baseDir = './dist';
let totalSize = 0;
type Stats = fs.Stats & { name?: string };
export async function splitResorce(compress: boolean = false) {
const folder = await fs.stat('./dist');
totalSize = folder.size;
if (totalSize < MAX_SIZE) return;
// if (totalSize < MAX_SIZE) return;
await fs.ensureDir('./dist-resource');
await doSplit(compress);
@ -38,16 +40,112 @@ export async function splitResorce(compress: boolean = false) {
async function sortDir(dir: string, ext?: string[]) {
const path = await fs.readdir(dir);
const stats: fs.Stats[] = [];
const stats: Stats[] = [];
for await (const one of path) {
if (ext && !ext.includes(extname(one))) continue;
const stat = await fs.stat(resolve(dir, one));
if (!stat.isFile()) continue;
stats.push(stat);
const status: Stats = {
...stat,
name: one
};
stats.push(status);
}
return stats.sort((a, b) => b.size - a.size);
}
async function doSplit(compress: boolean) {}
async function calResourceSize() {
return (
await Promise.all(
[
'animates',
'autotiles',
'bgms',
'fonts',
'images',
'materials',
'sounds',
'tilesets'
].map(v => fs.stat(resolve('./dist/project/', v)))
)
).reduce((pre, cur) => pre + cur.size, 0);
}
async function doSplit(compress: boolean) {
let size = await calResourceSize();
await fs.emptyDir('./dist-resource');
const priority: ResorceType[] = [
'materials',
'tilesets',
'autotiles',
'animates',
'images',
'sounds',
'fonts',
'bgms'
];
const dirInfo: Record<ResorceType, Stats[]> = Object.fromEntries(
await Promise.all(
priority.map(async v => [
v,
await sortDir(resolve(baseDir, 'project', v))
])
)
);
let currSize = 0;
const split = async (index: number): Promise<void> => {
size -= currSize;
currSize = 0;
const cut: string[] = [];
(() => {
const mapped: ResorceType[] = [];
while (1) {
const toCut = priority.find(
v => dirInfo[v].length > 0 && !mapped.includes(v)
);
if (!toCut) return;
mapped.push(toCut);
let pass = 0;
while (1) {
const stats = dirInfo[toCut];
const stat = stats[pass];
if (!stat) {
break;
}
if (currSize + stat.size >= MAX_SIZE) {
pass++;
continue;
}
cut.push(`${toCut}/${stat.name}`);
stats.splice(pass, 1);
currSize += stat.size;
}
}
})();
const dir = `./dist-resource/${index}`;
await fs.ensureDir(dir);
await Promise.all(priority.map(v => fs.ensureDir(resolve(dir, v))));
await Promise.all(
cut.map(v =>
fs.move(
resolve('./dist/project', v),
resolve(dir, `${v}-${SYMBOL}`)
)
)
);
if (Object.values(dirInfo).every(v => v.length === 0)) return;
else return split(index + 1);
};
await split(0);
}

View File

@ -1,3 +1,13 @@
export function uniqueSymbol() {
return Math.ceil(Math.random() * 0xefffffff + 0x10000000).toString(16);
}
export function formatSize(size: number) {
return size < 1 << 10
? `${size.toFixed(2)}B`
: size < 1 << 20
? `${(size / (1 << 10)).toFixed(2)}KB`
: size < 1 << 30
? `${(size / (1 << 20)).toFixed(2)}MB`
: `${(size / (1 << 30)).toFixed(2)}GB`;
}

View File

@ -0,0 +1,36 @@
import { EmitableEvent, EventEmitter } from './eventEmitter';
interface DisposableEvent<T> extends EmitableEvent {
active: (value: T) => void;
dispose: (value: T) => void;
}
export class Disposable<T> extends EventEmitter<DisposableEvent<T>> {
protected _data: T;
set data(value: T | null) {
if (value !== null) this._data = value;
}
get data(): T | null {
if (!this.activated) {
return null;
}
return this._data;
}
protected activated: boolean = false;
constructor(data: T) {
super();
this._data = data;
}
active() {
this.activated = true;
this.emit('active', this._data);
}
dispose() {
this.activated = false;
this.emit('dispose', this._data);
}
}

View File

@ -0,0 +1,73 @@
export interface EmitableEvent {
[event: string]: (...params: any) => any;
}
interface Listener<T extends (...params: any) => any> {
fn: T;
once?: boolean;
}
interface ListenerOptions {
once: boolean;
}
export class EventEmitter<T extends EmitableEvent = {}> {
private events: {
[P in keyof T]?: Listener<T[P]>[];
} = {};
/**
*
* @param event
* @param fn
* @param options
*/
on<K extends keyof T>(
event: K,
fn: T[K],
options?: Partial<ListenerOptions>
) {
this.events[event] ??= [];
this.events[event]?.push({
fn,
once: options?.once
});
}
/**
*
* @param event
* @param fn
*/
off<K extends keyof T>(event: K, fn: T[K]) {
const index = this.events[event]?.findIndex(v => v.fn === fn);
if (index === -1 || index === void 0) return;
this.events[event]?.splice(index, 1);
}
/**
*
* @param event
* @param fn
*/
once<K extends keyof T>(event: K, fn: T[K]) {
this.on(event, fn, { once: true });
}
/**
*
* @param event
* @param params
*/
emit<K extends keyof T>(event: K, ...params: Parameters<T[K]>) {
const events = (this.events[event] ??= []);
for (let i = 0; i < events.length; i++) {
const e = events[i];
e.fn(...(params as any));
if (e.once) {
events.splice(i, 1);
i--;
}
}
}
}

View File

@ -76,7 +76,7 @@
"text": "优化加载",
"desc": [
"开启后游戏将对加载进行优化,缩短进入游戏时的加载时长,而在游戏中对资源进行部分性按需加载,从而对加载进行优化。",
"该设置不会影响你的正常游戏。",
"该设置不会影响你的正常游戏,但如果网络环境较差,可能会导致楼层转换时间明显变长。",
"<br>",
"<br>",
"注:修改后刷新页面起效。"

View File

@ -130,7 +130,8 @@ const settings: Record<keyof Settings, Ref<boolean>> = {
useFixed,
autoLocate,
antiAliasing,
fullscreen
fullscreen,
betterLoad: ref(false)
};
const ignore: (keyof Settings)[] = ['fullscreen'];