mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-07-18 02:51:47 +08:00
资源分离
This commit is contained in:
parent
39c655d355
commit
1bdfb9be7d
3
.gitignore
vendored
3
.gitignore
vendored
@ -34,4 +34,5 @@ dist.rar
|
|||||||
index.cjs
|
index.cjs
|
||||||
!public/swap/*.h5save
|
!public/swap/*.h5save
|
||||||
_bundle
|
_bundle
|
||||||
out
|
out
|
||||||
|
dist-resource
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,11 +1,13 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { extname, resolve } from 'path';
|
import { extname, resolve } from 'path';
|
||||||
|
import { formatSize } from './utils.js';
|
||||||
|
|
||||||
(async function () {
|
(async function () {
|
||||||
const dir = process.argv[2] || './src';
|
const dir = process.argv.slice(2) || ['./src'];
|
||||||
let totalLines = 0;
|
let totalLines = 0;
|
||||||
let totalFiles = 0;
|
let totalFiles = 0;
|
||||||
const list: Record<string, [number, number]> = {};
|
let totalSize = 0;
|
||||||
|
const list: Record<string, [number, number, number]> = {};
|
||||||
const ignoreDir = [
|
const ignoreDir = [
|
||||||
'node_modules',
|
'node_modules',
|
||||||
'floors',
|
'floors',
|
||||||
@ -36,32 +38,38 @@ import { extname, resolve } from 'path';
|
|||||||
const file = await fs.readFile(resolve(dir, one), 'utf-8');
|
const file = await fs.readFile(resolve(dir, one), 'utf-8');
|
||||||
const lines = file.split('\n').length;
|
const lines = file.split('\n').length;
|
||||||
const ext = extname(one);
|
const ext = extname(one);
|
||||||
list[ext] ??= [0, 0];
|
list[ext] ??= [0, 0, 0];
|
||||||
list[ext][0]++;
|
list[ext][0]++;
|
||||||
list[ext][1] += lines;
|
list[ext][1] += lines;
|
||||||
|
list[ext][2] += stat.size;
|
||||||
totalLines += lines;
|
totalLines += lines;
|
||||||
totalFiles++;
|
totalFiles++;
|
||||||
|
totalSize += stat.size;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await check(resolve(dir, one));
|
await check(resolve(dir, one));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
await check(dir);
|
await Promise.all(dir.map(v => check(v)));
|
||||||
|
|
||||||
const sorted = Object.entries(list).sort((a, b) => {
|
const sorted = Object.entries(list).sort((a, b) => {
|
||||||
return a[1][1] - b[1][1];
|
return a[1][1] - b[1][1];
|
||||||
});
|
});
|
||||||
for (const [ext, [file, lines]] of sorted) {
|
for (const [ext, [file, lines, size]] of sorted) {
|
||||||
console.log(
|
console.log(
|
||||||
`${ext.slice(1).padEnd(7, ' ')}files: ${file
|
`${ext.slice(1).padEnd(7, ' ')}files: ${file
|
||||||
.toString()
|
.toString()
|
||||||
.padEnd(6, ' ')}lines: ${lines}`
|
.padEnd(6, ' ')}lines: ${lines
|
||||||
|
.toString()
|
||||||
|
.padEnd(9, ' ')}size: ${formatSize(size)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(
|
||||||
`\x1b[33mtotal files: ${totalFiles
|
`\x1b[33mtotal files: ${totalFiles
|
||||||
.toString()
|
.toString()
|
||||||
.padEnd(6, ' ')}lines: ${totalLines}\x1b[0m`
|
.padEnd(6, ' ')}lines: ${totalLines
|
||||||
|
.toString()
|
||||||
|
.padEnd(9, ' ')}size: ${formatSize(totalSize)}\x1b[0m`
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
@ -3,8 +3,8 @@ import { uniqueSymbol } from './utils.js';
|
|||||||
import { extname, resolve } from 'path';
|
import { extname, resolve } from 'path';
|
||||||
|
|
||||||
type ResorceType =
|
type ResorceType =
|
||||||
| 'bgm'
|
| 'bgms'
|
||||||
| 'sound'
|
| 'sounds'
|
||||||
| 'autotiles'
|
| 'autotiles'
|
||||||
| 'images'
|
| 'images'
|
||||||
| 'materials'
|
| 'materials'
|
||||||
@ -12,9 +12,8 @@ type ResorceType =
|
|||||||
| 'animates'
|
| 'animates'
|
||||||
| 'fonts';
|
| 'fonts';
|
||||||
|
|
||||||
let resorceIndex = 0;
|
|
||||||
const compress: ResorceType[] = [
|
const compress: ResorceType[] = [
|
||||||
'sound',
|
'sounds',
|
||||||
'animates',
|
'animates',
|
||||||
'autotiles',
|
'autotiles',
|
||||||
'images',
|
'images',
|
||||||
@ -24,13 +23,16 @@ const compress: ResorceType[] = [
|
|||||||
|
|
||||||
const SYMBOL = uniqueSymbol();
|
const SYMBOL = uniqueSymbol();
|
||||||
const MAX_SIZE = 100 * (1 << 20);
|
const MAX_SIZE = 100 * (1 << 20);
|
||||||
|
const baseDir = './dist';
|
||||||
|
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
|
|
||||||
|
type Stats = fs.Stats & { name?: string };
|
||||||
|
|
||||||
export async function splitResorce(compress: boolean = false) {
|
export async function splitResorce(compress: boolean = false) {
|
||||||
const folder = await fs.stat('./dist');
|
const folder = await fs.stat('./dist');
|
||||||
totalSize = folder.size;
|
totalSize = folder.size;
|
||||||
if (totalSize < MAX_SIZE) return;
|
// if (totalSize < MAX_SIZE) return;
|
||||||
|
|
||||||
await fs.ensureDir('./dist-resource');
|
await fs.ensureDir('./dist-resource');
|
||||||
await doSplit(compress);
|
await doSplit(compress);
|
||||||
@ -38,16 +40,112 @@ export async function splitResorce(compress: boolean = false) {
|
|||||||
|
|
||||||
async function sortDir(dir: string, ext?: string[]) {
|
async function sortDir(dir: string, ext?: string[]) {
|
||||||
const path = await fs.readdir(dir);
|
const path = await fs.readdir(dir);
|
||||||
const stats: fs.Stats[] = [];
|
const stats: Stats[] = [];
|
||||||
|
|
||||||
for await (const one of path) {
|
for await (const one of path) {
|
||||||
if (ext && !ext.includes(extname(one))) continue;
|
if (ext && !ext.includes(extname(one))) continue;
|
||||||
const stat = await fs.stat(resolve(dir, one));
|
const stat = await fs.stat(resolve(dir, one));
|
||||||
if (!stat.isFile()) continue;
|
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);
|
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);
|
||||||
|
}
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
export function uniqueSymbol() {
|
export function uniqueSymbol() {
|
||||||
return Math.ceil(Math.random() * 0xefffffff + 0x10000000).toString(16);
|
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`;
|
||||||
|
}
|
||||||
|
36
src/core/common/disposable.ts
Normal file
36
src/core/common/disposable.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
73
src/core/common/eventEmitter.ts
Normal file
73
src/core/common/eventEmitter.ts
Normal 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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -76,7 +76,7 @@
|
|||||||
"text": "优化加载",
|
"text": "优化加载",
|
||||||
"desc": [
|
"desc": [
|
||||||
"开启后游戏将对加载进行优化,缩短进入游戏时的加载时长,而在游戏中对资源进行部分性按需加载,从而对加载进行优化。",
|
"开启后游戏将对加载进行优化,缩短进入游戏时的加载时长,而在游戏中对资源进行部分性按需加载,从而对加载进行优化。",
|
||||||
"该设置不会影响你的正常游戏。",
|
"该设置不会影响你的正常游戏,但如果网络环境较差,可能会导致楼层转换时间明显变长。",
|
||||||
"<br>",
|
"<br>",
|
||||||
"<br>",
|
"<br>",
|
||||||
"注:修改后刷新页面起效。"
|
"注:修改后刷新页面起效。"
|
||||||
|
@ -130,7 +130,8 @@ const settings: Record<keyof Settings, Ref<boolean>> = {
|
|||||||
useFixed,
|
useFixed,
|
||||||
autoLocate,
|
autoLocate,
|
||||||
antiAliasing,
|
antiAliasing,
|
||||||
fullscreen
|
fullscreen,
|
||||||
|
betterLoad: ref(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ignore: (keyof Settings)[] = ['fullscreen'];
|
const ignore: (keyof Settings)[] = ['fullscreen'];
|
||||||
|
Loading…
Reference in New Issue
Block a user