Skip to content

Commit

Permalink
feat & perf: 优化配置加载流程,在启动时默认创建模组配置
Browse files Browse the repository at this point in the history
Co-authored-by: S·c <[email protected]>
  • Loading branch information
median-dxz and ScSofts committed Jan 21, 2025
1 parent fb60823 commit 240a9a5
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 76 deletions.
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@sea/server",
"private": true,
"type": "module",
"version": "0.9.1",
"version": "0.10.0",
"main": "./src/index.ts",
"exports": {
".": {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/configHandlers/LauncherConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type LauncherConfigType = typeof defaultConfig;

export class LauncherConfig extends MultiUserConfigHandler<LauncherConfigType> {
async load() {
return super.load(defaultConfig);
return super.loadWithDefaultConfig(defaultConfig);
}

async item<TKey extends keyof LauncherConfigType>(uid: string, key: TKey) {
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/configHandlers/ModIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ConfigHandler } from '../shared/ConfigHandler.ts';

export interface ModState {
enable: boolean;
requireConfig: boolean;
requireData: boolean;
config: object | null;
data: object | null;
builtin: boolean;
preload: boolean;
version: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/configHandlers/PetCatchTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MultiUserConfigHandler } from '../shared/MultiUserConfigHandler.ts';

export class PetCatchTime extends MultiUserConfigHandler<Map<string, number>> {
async load() {
return super.load(new Map());
return super.loadWithDefaultConfig(new Map());
}

async catchTime(uid: string, name: string) {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/configHandlers/TaskOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MultiUserConfigHandler } from '../shared/MultiUserConfigHandler.ts';

export class TaskOptions extends MultiUserConfigHandler<Map<string, object>> {
async load() {
return super.load(new Map());
return super.loadWithDefaultConfig(new Map());
}

async set(uid: string, taskId: string, options: object) {
Expand Down
50 changes: 31 additions & 19 deletions packages/server/src/shared/ConfigHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,64 @@ class DataNotLoadedError extends Error {
}
}

export class ConfigHandler<TData extends object = object> {
private data?: TData;
export class ConfigHandler<TConfig extends object = object> {
private config?: TConfig;
constructor(private storage: IStorage) {}

get loaded() {
return this.data !== undefined;
return this.config !== undefined;
}

async load(defaultData?: TData) {
this.data = (await this.storage.load(defaultData)) as TData;
async load(defaultConfig: undefined, override?: false): Promise<void>;
async load(defaultConfig: TConfig, override?: boolean): Promise<void>;
async load(defaultConfig?: TConfig, override = false): Promise<void> {
if (override) {
await this.create(defaultConfig!);
} else {
if (this.loaded) return;

if (defaultConfig === undefined) {
throw new Error('Default data must be provided when loading for the first time');
}

this.config = (await this.storage.load(defaultConfig)) as TConfig;
}
}

async create(config: TConfig) {
this.config = config!;
await this.save();
}

private async save() {
if (!this.data) {
if (!this.config) {
throw new DataNotLoadedError(this.storage.source);
}

await this.storage.save(this.data);
}

async create(data: TData) {
this.data = data;
await this.save();
await this.storage.save(this.config);
}

async mutate(recipe: Recipe<TData>) {
if (!this.data) {
async mutate(recipe: Recipe<TConfig>) {
if (!this.config) {
throw new DataNotLoadedError(this.storage.source);
}

const r = recipe(this.data);
r && (this.data = r);
const r = recipe(this.config);
r && (this.config = r);

await this.save();
}

query() {
if (!this.data) {
if (!this.config) {
throw new DataNotLoadedError(this.storage.source);
}

return this.data;
return this.config;
}

async destroy() {
await this.storage.delete();
this.data = undefined;
this.config = undefined;
}
}
2 changes: 1 addition & 1 deletion packages/server/src/shared/FileSystemStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class FileSystemStorage implements IStorage {
return defaultData;
}

throw new Error(`Config file does not exist: ${file}`);
throw new Error(`Config file load failed, with no default data provided: ${file}`);
}

async save(data: object) {
Expand Down
50 changes: 23 additions & 27 deletions packages/server/src/shared/ModManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,43 +57,39 @@ export class ModManager {
preload: Boolean(options.preload),
enable: true,
version: options.version,
requireConfig: Boolean(options.config),
requireData: Boolean(options.data)
config: options.config ?? null,
data: options.data ?? null
};

await this.index.set(cid, state);

// 在满足该模组请求数据持久化的前提下:
// 1. 当前数据存在但更新选项为false
// 2. 当前数据不存在
//
// 此时将创建新的数据持久化对象, 覆盖原对象
if (options.config && (!this.configHandlers.has(cid) || options.update === false)) {
const handler = new MultiUserConfigHandler(this.configStorageBuilder(cid));
await handler.create(options.config);
this.configHandlers.set(cid, handler);
}

if (options.data && (!this.dataHandlers.has(cid) || options.update === false)) {
const handler = new MultiUserConfigHandler(this.dataStorageBuilder(cid));
await handler.create(options.data);
this.dataHandlers.set(cid, handler);
}
// update 含义为配置兼容性, 为 true 时说明兼容旧配置, 不需要进行覆盖
await this.load(cid, state, !options.update);

return cid;
}

async load(cid: string, state: ModState) {
if (state.requireConfig) {
const handler = new MultiUserConfigHandler(this.configStorageBuilder(cid));
await handler.load();
this.configHandlers.set(cid, handler);
async load(cid: string, state: ModState, override = false) {
if (state.config) {
if (this.configHandlers.has(cid)) {
const handler = this.configHandlers.get(cid)!;
await handler.loadWithDefaultConfig(state.config, override);
} else {
const handler = new MultiUserConfigHandler(this.configStorageBuilder(cid));
await handler.loadWithDefaultConfig(state.config, override);
this.configHandlers.set(cid, handler);
}
}

if (state.requireData) {
const handler = new MultiUserConfigHandler(this.dataStorageBuilder(cid));
await handler.load();
this.dataHandlers.set(cid, handler);
if (state.data) {
if (this.dataHandlers.has(cid)) {
const handler = this.dataHandlers.get(cid)!;
await handler.loadWithDefaultConfig(state.data, override);
} else {
const handler = new MultiUserConfigHandler(this.dataStorageBuilder(cid));
await handler.loadWithDefaultConfig(state.data, override);
this.dataHandlers.set(cid, handler);
}
}
}

Expand Down
15 changes: 6 additions & 9 deletions packages/server/src/shared/MultiUserConfigHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@ import type { Recipe } from '../client-types.ts';
import { ConfigHandler } from './ConfigHandler.ts';
import type { IStorage } from './utils.ts';

export class MultiUserConfigHandler<TData extends object = object> {
private configHandler: ConfigHandler<Record<string, TData>>;
export class MultiUserConfigHandler<TConfig extends object = object> {
private configHandler: ConfigHandler<Record<string, TConfig>>;
constructor(storage: IStorage) {
this.configHandler = new ConfigHandler(storage);
}

async load(defaultData?: TData) {
await this.configHandler.load(defaultData !== undefined ? { default: defaultData } : undefined);
async loadWithDefaultConfig(config: TConfig, override = false) {
const multiUserDefaultConfig = { default: config };
await this.configHandler.load(multiUserDefaultConfig, override);
}

async create(data: TData) {
await this.configHandler.create({ default: data });
}

async mutate(uid: string, recipe: Recipe<TData>) {
async mutate(uid: string, recipe: Recipe<TConfig>) {
await this.configHandler.mutate((userDataMap) => {
let data = userDataMap[uid];
if (data === undefined) {
Expand Down
13 changes: 7 additions & 6 deletions packages/server/test/mod/manger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import { CID_LIST, FakeStorage, FakeStorageBuilder, SOURCE_INDEX, storageDelete,

const modState1 = {
enable: true,
requireConfig: false,
requireData: false,
config: null,
data: null,
builtin: true,
preload: false,
version: '0.0.1'
} satisfies ModState;

const modState2 = {
enable: true,
requireConfig: true,
requireData: true,
config: { key: 'value' },
data: { key: 'value' },
builtin: false,
preload: false,
version: '0.0.1'
Expand Down Expand Up @@ -51,6 +51,7 @@ const test = base.extend<{
[CID_LIST[2], modState2 as ModState]
])
);

await modManager.init();

use(modManager);
Expand All @@ -74,8 +75,8 @@ test('install a new mod with config and data', async ({ expect, modManager }) =>
builtin: false,
preload: false,
enable: true,
requireConfig: true,
requireData: true
config: { key: 'value' },
data: { key: 'value' }
});
expect(await modManager.data(UID, cid)).toEqual({ key: 'value' });
expect(await modManager.config(UID, cid)).toEqual({ key: 'value' });
Expand Down
12 changes: 4 additions & 8 deletions packages/server/test/mod/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,11 @@ export const storageDelete = vi.fn(async () => {});
export class FakeStorage implements IStorage {
constructor(public source: string) {}
data: object | undefined;
initLoad = true;
async load(defaultData?: object) {
if (!this.data) {
if (defaultData) {
this.data = defaultData;
} else if (this.source === CID_LIST[1] || this.source === CID_LIST[2]) {
this.data = { [UID]: { key: 'value' } };
} else {
throw new Error('should set default data');
}
if (this.initLoad) {
this.data = defaultData;
this.initLoad = false;
}
return this.data;
}
Expand Down

0 comments on commit 240a9a5

Please sign in to comment.