Commit 82eb4be8 authored by nanahira's avatar nanahira

load cdb in another thread

parent 86d30310
Pipeline #43366 passed with stages
in 3 minutes and 13 seconds
......@@ -31,7 +31,7 @@
"typeorm": "^0.3.28",
"ws": "^8.19.0",
"yaml": "^2.8.2",
"ygopro-cdb-encode": "^1.0.4",
"ygopro-cdb-encode": "^1.0.5",
"ygopro-deck-encode": "^1.0.15",
"ygopro-lflist-encode": "^1.0.3",
"ygopro-msg-encode": "^1.1.31",
......@@ -84,7 +84,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
......@@ -575,7 +574,6 @@
"resolved": "https://registry.npmjs.org/@cordisjs/core/-/core-3.18.1.tgz",
"integrity": "sha512-yRuATOamFxeD1ztE2L3o1SaHuT2zw5DTXijQYxt9azVNeILbvtomfGG6sLZegrynJO9XZbNvXbOlQ7LBJDOlAg==",
"license": "MIT",
"peer": true,
"dependencies": {
"cosmokit": "^1.6.2"
}
......@@ -612,7 +610,6 @@
"resolved": "https://registry.npmjs.org/@cordisjs/plugin-http/-/plugin-http-0.6.3.tgz",
"integrity": "sha512-kmw4G1t39ZZzFGpBKUNuTv2aefEUtxa1e4qu4+bTrM5Z8uYHTRzPpyJfkj9zuZwopP/Vz271OUr2UfDN5lrJKQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"cosmokit": "^1.6.3",
"file-type": "^16.5.4",
......@@ -1468,7 +1465,6 @@
"resolved": "https://registry.npmjs.org/@koishijs/core/-/core-4.18.10.tgz",
"integrity": "sha512-P2dc9EqoMasqqdUz3iKyGbcd0krAG7dMT7Ju6CqrzAobj0w7i548cCE89mD3qTzhvdTKzbf4UBYDRg21lXrb4A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@koishijs/i18n-utils": "^1.0.1",
"@koishijs/utils": "^7.2.1",
......@@ -2122,7 +2118,6 @@
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/types": "8.55.0",
......@@ -2628,7 +2623,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
......@@ -3024,7 +3018,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
......@@ -3479,7 +3472,6 @@
"resolved": "https://registry.npmjs.org/cordis/-/cordis-3.18.1.tgz",
"integrity": "sha512-9IbthFbFBVJ15WBDNPqHdl59TyEAMNWxF1Dxsdi14625ePQkMX35WvcqgGno0BcXlpXDbUaBZgQ+jfBYT+uuSQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@cordisjs/core": "3.18.1",
"@cordisjs/loader": "^0.13.1",
......@@ -3900,7 +3892,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
......@@ -3957,7 +3948,6 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
......@@ -4951,7 +4941,6 @@
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz",
"integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ioredis/commands": "1.5.0",
"cluster-key-slot": "^1.1.0",
......@@ -5239,7 +5228,6 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
......@@ -6033,7 +6021,6 @@
"resolved": "https://registry.npmjs.org/koishi/-/koishi-4.18.10.tgz",
"integrity": "sha512-rEbF2NuHDAsBJ0+5B12RsRk7QWM/1sZvMnlaSj2TqUP+558Z1+lVZO9HhsyxrEp+r+bheRNfpJDN8aO369/myg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@koishijs/core": "4.18.10",
"@koishijs/loader": "4.6.10",
......@@ -6703,7 +6690,6 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
"integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.11.0",
"pg-pool": "^3.11.0",
......@@ -7024,7 +7010,6 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
......@@ -8274,7 +8259,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -8539,7 +8523,6 @@
"resolved": "https://registry.npmjs.org/typed-struct/-/typed-struct-2.7.1.tgz",
"integrity": "sha512-GluzA9kYlHjATJmzBDA2X9G9237Md5zsJsc8uEkmpvUFeuUvt+e7Sq11/nQnVB2VZIfKNR1CrwTCgpJVz52pAA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -8674,7 +8657,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
......@@ -9126,9 +9108,9 @@
}
},
"node_modules/ygopro-cdb-encode": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/ygopro-cdb-encode/-/ygopro-cdb-encode-1.0.4.tgz",
"integrity": "sha512-951ab8yrhmc69EbIqRSumRKbsI1D8OiDpiG37Ws5gQAjRvJsx8d0BOYoAgLrjadOdBtl+UUhJyk4pWpSCp9Z+w==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/ygopro-cdb-encode/-/ygopro-cdb-encode-1.0.5.tgz",
"integrity": "sha512-WnhMJTdXWZxZtcW8OnfxxbCEHehuYByrqyZXnyQ739422CcZks7ulVIlb2pdn1D8EeWb2kQxnxBoMzX/4g/+3g==",
"license": "MIT",
"dependencies": {
"@types/sql.js": "^1.4.9",
......
......@@ -70,7 +70,7 @@
"typeorm": "^0.3.28",
"ws": "^8.19.0",
"yaml": "^2.8.2",
"ygopro-cdb-encode": "^1.0.4",
"ygopro-cdb-encode": "^1.0.5",
"ygopro-deck-encode": "^1.0.15",
"ygopro-lflist-encode": "^1.0.3",
"ygopro-msg-encode": "^1.1.31",
......@@ -91,4 +91,4 @@
"ts-jest": "^29.4.6",
"typescript": "^5.9.3"
}
}
\ No newline at end of file
}
......@@ -8,7 +8,8 @@ export class OcgcoreWorkerOptions {
extraScriptPaths: string[];
@TransportType(() => CardStorage)
cardStorage: CardStorage;
ocgcoreWasmPath?: string;
@TransportType(() => Buffer)
ocgcoreWasmBinary?: Buffer;
seed: number[];
hostinfo: HostInfo;
@TransportType(() => [YGOProDeck])
......
......@@ -35,7 +35,6 @@ import {
YGOProMsgResponseBase,
YGOProMsgRetry,
} from 'ygopro-msg-encode';
import * as fs from 'node:fs';
const { OcgcoreScriptConstants } = _OcgcoreConstants;
const OCGCORE_MESSAGE_REPLAY_BUFFER_SIZE = 128;
......@@ -95,10 +94,7 @@ export class OcgcoreWorker {
@WorkerInit()
async init() {
let wasmBinary: Uint8Array | undefined;
if (this.options.ocgcoreWasmPath) {
wasmBinary = await fs.promises.readFile(this.options.ocgcoreWasmPath);
}
const wasmBinary = this.options.ocgcoreWasmBinary;
// Create ocgcore wrapper
this.ocgcore = await createOcgcoreWrapper(
......
......@@ -62,9 +62,9 @@ export class RoomEventRegister {
continue;
}
clientParamIndex = fallbackClientIndex;
this.logger.warn(
`Method ${method} has no explicit Client param metadata, fallback to arg[${clientParamIndex}] for client`,
);
// this.logger.warn(
// `Method ${method} has no explicit Client param metadata, fallback to arg[${clientParamIndex}] for client`,
// );
}
// 获取方法选项
......
......@@ -98,7 +98,6 @@ import { getMessageIdentifier } from '../utility/get-message-identifier';
import { canIncreaseTime } from '../utility/can-increase-time';
import { TimerState } from './timer-state';
import { makeArray } from 'aragami/dist/src/utility/utility';
import path from 'path';
import { OnRoomCreate } from './room-event/on-room-create';
import { OnRoomFinalize } from './room-event/on-room-finalize';
import { OnRoomSidingStart } from './room-event/on-room-siding-start';
......@@ -1349,12 +1348,8 @@ export class Room {
'Initializing OCGCoreWorker',
);
const ocgcoreWasmPathConfig =
this.ctx.config.getString('OCGCORE_WASM_PATH');
const ocgcoreWasmPath = ocgcoreWasmPathConfig
? path.resolve(process.cwd(), ocgcoreWasmPathConfig)
: undefined;
const cardStorage = await this.resourceLoader.getCardStorage();
const ocgcoreWasmBinary = await this.resourceLoader.getOcgcoreWasmBinary();
try {
this.ocgcore = await initWorker(OcgcoreWorker, {
......@@ -1363,7 +1358,7 @@ export class Room {
ygoproPaths: this.resourceLoader.ygoproPaths,
extraScriptPaths,
cardStorage,
ocgcoreWasmPath,
ocgcoreWasmBinary,
registry,
decks: duelRecord.toSwappedPlayers().map((p) => p.deck),
});
......
import * as fs from 'node:fs';
import { searchYGOProResource } from 'koishipro-core.js';
import type { CardDataEntry } from 'ygopro-cdb-encode';
import { YGOProCdb } from 'ygopro-cdb-encode';
import initSqlJs from 'sql.js';
import { DefineWorker, TransportType, WorkerMethod, toShared } from 'yuzuthread';
import { CardStorage } from './card-storage';
const isFileNotFoundError = (error: unknown): error is NodeJS.ErrnoException =>
typeof error === 'object' &&
error !== null &&
'code' in error &&
(error as NodeJS.ErrnoException).code === 'ENOENT';
export class CardLoadWorkerResult {
@TransportType(() => CardStorage)
cardStorage: CardStorage;
dbCount: number;
failedFiles: string[];
@TransportType(() => Buffer)
ocgcoreWasmBinary?: Buffer;
}
@DefineWorker()
export class CardLoadWorker {
constructor(
private ygoproPaths: string[],
private ocgcoreWasmPath?: string,
) {}
@WorkerMethod()
@TransportType(() => CardLoadWorkerResult)
async load(): Promise<CardLoadWorkerResult> {
const SQL = await initSqlJs();
const cards: CardDataEntry[] = [];
const seen = new Set<number>();
let dbCount = 0;
const failedFiles: string[] = [];
for await (const file of searchYGOProResource(...this.ygoproPaths)) {
if (!file.path.endsWith('.cdb')) {
continue;
}
try {
const currentDb = new SQL.Database(await file.read());
try {
const currentCdb = new YGOProCdb(currentDb).noTexts();
for (const card of currentCdb.step()) {
const cardId = (card.code ?? 0) >>> 0;
if (cardId === 0 || seen.has(cardId)) {
continue;
}
seen.add(cardId);
cards.push(card);
}
++dbCount;
} finally {
currentDb.close();
}
} catch (error) {
failedFiles.push(`${file.path}: ${error}`);
continue;
}
}
let ocgcoreWasmBinary: Buffer | undefined;
if (this.ocgcoreWasmPath) {
try {
const wasmBinary = await fs.promises.readFile(this.ocgcoreWasmPath);
ocgcoreWasmBinary = toShared(wasmBinary);
} catch (error) {
if (!isFileNotFoundError(error)) {
throw error;
}
}
}
const result = new CardLoadWorkerResult();
result.cardStorage = toShared(CardStorage.fromCards(cards));
result.dbCount = dbCount;
result.failedFiles = failedFiles;
result.ocgcoreWasmBinary = ocgcoreWasmBinary;
return result;
}
}
......@@ -3,11 +3,10 @@ import type { CardReaderFn } from 'koishipro-core.js';
import { searchYGOProResource } from 'koishipro-core.js';
import { YGOProLFList } from 'ygopro-lflist-encode';
import path from 'node:path';
import type { CardDataEntry } from 'ygopro-cdb-encode';
import { YGOProCdb } from 'ygopro-cdb-encode';
import { toShared } from 'yuzuthread';
import { runInWorker } from 'yuzuthread';
import BetterLock from 'better-lock';
import { CardStorage } from './card-storage';
import { CardLoadWorker } from './card-load-worker';
export class YGOProResourceLoader {
constructor(private ctx: Context) {
......@@ -27,6 +26,7 @@ export class YGOProResourceLoader {
private loadingCardStorage?: Promise<CardStorage>;
private currentCardStorage?: CardStorage;
private currentCardReader?: CardReaderFn;
private currentOcgcoreWasmBinary?: Buffer;
async getCardStorage() {
if (this.currentCardStorage) {
......@@ -48,6 +48,11 @@ export class YGOProResourceLoader {
return reader;
}
async getOcgcoreWasmBinary() {
await this.getCardStorage();
return this.currentOcgcoreWasmBinary;
}
async loadYGOProCdbs() {
if (this.loadingCardStorage) {
return this.loadingCardStorage;
......@@ -69,45 +74,30 @@ export class YGOProResourceLoader {
}
private async loadCardStorage() {
const cards: CardDataEntry[] = [];
const seen = new Set<number>();
let dbCount = 0;
const ocgcoreWasmPathConfig =
this.ctx.config.getString('OCGCORE_WASM_PATH');
const ocgcoreWasmPath = ocgcoreWasmPathConfig
? path.resolve(process.cwd(), ocgcoreWasmPathConfig)
: undefined;
const { cardStorage, dbCount, failedFiles, ocgcoreWasmBinary } =
await runInWorker(
CardLoadWorker,
(worker) => worker.load(),
this.ygoproPaths,
ocgcoreWasmPath,
);
for await (const file of searchYGOProResource(...this.ygoproPaths)) {
const filename = path.basename(file.path);
if (!filename?.endsWith('.cdb')) {
continue;
}
try {
const currentDb = new this.ctx.SQL.Database(await file.read());
try {
const currentCdb = new YGOProCdb(currentDb).noTexts();
for (const card of currentCdb.find()) {
const cardId = card.code >>> 0;
if (seen.has(cardId)) {
continue;
}
seen.add(cardId);
cards.push(card);
}
++dbCount;
} finally {
currentDb.close();
}
} catch (e) {
this.logger.warn(`Failed to read ${file.path}: ${e}`);
continue;
}
this.currentOcgcoreWasmBinary = ocgcoreWasmBinary;
for (const failedFile of failedFiles) {
this.logger.warn(`Failed to read ${failedFile}`);
}
const storage = toShared(CardStorage.fromCards(cards));
this.logger.info(
{
size: storage.byteLength,
size: cardStorage.byteLength,
},
`Merged database from ${dbCount} databases with ${storage.size} cards`,
`Merged database from ${dbCount} databases with ${cardStorage.size} cards`,
);
return storage;
return cardStorage;
}
async *getLFLists() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment