1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-11 09:11:50 +00:00

Remove main process compression

To be simplified and replaced with renderer-side web workers.
This commit is contained in:
Даниїл Григор'єв 2025-06-10 12:15:38 +03:00
parent 3abfa9c35d
commit efb9eee286
No known key found for this signature in database
GPG Key ID: B890DF16341D8C1D
5 changed files with 20 additions and 98 deletions

View File

@ -2,7 +2,6 @@ import { BrowserWindow, dialog, FileFilter } from "electron";
import fs from "fs/promises"; import fs from "fs/promises";
import path from "path"; import path from "path";
import { userData } from "./config.js"; import { userData } from "./config.js";
import { StorageInterface } from "./storage/interface.js";
interface GenericFsJob { interface GenericFsJob {
id: string; id: string;
@ -11,30 +10,28 @@ interface GenericFsJob {
export type InitializeFsJob = GenericFsJob & { type: "initialize" }; export type InitializeFsJob = GenericFsJob & { type: "initialize" };
type ListFsJob = GenericFsJob & { type: "list"; filename: string }; type ListFsJob = GenericFsJob & { type: "list"; filename: string };
type ReadFsJob = GenericFsJob & { type: "read"; filename: string }; type ReadFsJob = GenericFsJob & { type: "read"; filename: string };
type WriteFsJob<T> = GenericFsJob & { type: "write"; filename: string; contents: T }; type WriteFsJob = GenericFsJob & { type: "write"; filename: string; contents: Uint8Array };
type DeleteFsJob = GenericFsJob & { type: "delete"; filename: string }; type DeleteFsJob = GenericFsJob & { type: "delete"; filename: string };
type OpenExternalFsJob = GenericFsJob & { type: "open-external"; extension: string }; type OpenExternalFsJob = GenericFsJob & { type: "open-external"; extension: string };
type SaveExternalFsJob<T> = GenericFsJob & { type: "save-external"; filename: string; contents: T }; type SaveExternalFsJob = GenericFsJob & { type: "save-external"; filename: string; contents: Uint8Array };
export type FsJob<T> = export type FsJob =
| InitializeFsJob | InitializeFsJob
| ListFsJob | ListFsJob
| ReadFsJob | ReadFsJob
| WriteFsJob<T> | WriteFsJob
| DeleteFsJob | DeleteFsJob
| OpenExternalFsJob | OpenExternalFsJob
| SaveExternalFsJob<T>; | SaveExternalFsJob;
type FsJobResult<T> = T | string[] | void; type FsJobResult = Uint8Array | string[] | void;
export class FsJobHandler<T> { export class FsJobHandler {
readonly rootDir: string; readonly rootDir: string;
private readonly storage: StorageInterface<T>;
private initialized = false; private initialized = false;
constructor(subDir: string, storage: StorageInterface<T>) { constructor(subDir: string) {
this.rootDir = path.join(userData, subDir); this.rootDir = path.join(userData, subDir);
this.storage = storage;
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {
@ -47,7 +44,7 @@ export class FsJobHandler<T> {
this.initialized = true; this.initialized = true;
} }
handleJob(job: FsJob<T>): Promise<FsJobResult<T>> { handleJob(job: FsJob): Promise<FsJobResult> {
switch (job.type) { switch (job.type) {
case "initialize": case "initialize":
return this.initialize(); return this.initialize();
@ -63,18 +60,18 @@ export class FsJobHandler<T> {
case "list": case "list":
return this.list(filename); return this.list(filename);
case "read": case "read":
return this.storage.read(filename); return fs.readFile(filename);
case "write": case "write":
return this.write(filename, job.contents); return this.write(filename, job.contents);
case "delete": case "delete":
return this.storage.delete(filename); return fs.unlink(filename);
} }
// @ts-expect-error this method can actually receive garbage // @ts-expect-error this method can actually receive garbage
throw new Error(`Unknown FS job type: ${job.type}`); throw new Error(`Unknown FS job type: ${job.type}`);
} }
private async openExternal(extension: string): Promise<T | undefined> { private async openExternal(extension: string): Promise<Uint8Array | undefined> {
const filters = this.getFileDialogFilters(extension === "*" ? undefined : extension); const filters = this.getFileDialogFilters(extension === "*" ? undefined : extension);
const window = BrowserWindow.getAllWindows()[0]!; const window = BrowserWindow.getAllWindows()[0]!;
@ -83,10 +80,10 @@ export class FsJobHandler<T> {
return undefined; return undefined;
} }
return await this.storage.read(result.filePaths[0]); return await fs.readFile(result.filePaths[0]);
} }
private async saveExternal(filename: string, contents: T): Promise<void> { private async saveExternal(filename: string, contents: Uint8Array): Promise<void> {
// Try to guess extension // Try to guess extension
const ext = filename.indexOf(".") < 1 ? filename.split(".").at(-1)! : undefined; const ext = filename.indexOf(".") < 1 ? filename.split(".").at(-1)! : undefined;
const filters = this.getFileDialogFilters(ext); const filters = this.getFileDialogFilters(ext);
@ -97,7 +94,7 @@ export class FsJobHandler<T> {
return; return;
} }
return await this.storage.write(result.filePath, contents); return await fs.writeFile(result.filePath, contents);
} }
private getFileDialogFilters(extension?: string): FileFilter[] { private getFileDialogFilters(extension?: string): FileFilter[] {
@ -118,12 +115,13 @@ export class FsJobHandler<T> {
return fs.readdir(subdir); return fs.readdir(subdir);
} }
private async write(file: string, contents: T): Promise<void> { private async write(file: string, contents: Uint8Array): Promise<void> {
// The target directory might not exist, ensure it does // The target directory might not exist, ensure it does
const parentDir = path.dirname(file); const parentDir = path.dirname(file);
await fs.mkdir(parentDir, { recursive: true }); await fs.mkdir(parentDir, { recursive: true });
await this.storage.write(file, contents); console.log(contents);
await fs.writeFile(file, contents);
} }
private safeFileName(name: string) { private safeFileName(name: string) {

View File

@ -1,10 +1,9 @@
import { BrowserWindow, IpcMainInvokeEvent, ipcMain } from "electron"; import { BrowserWindow, IpcMainInvokeEvent, ipcMain } from "electron";
import { FsJob, FsJobHandler } from "./fsjob.js"; import { FsJob, FsJobHandler } from "./fsjob.js";
import { ModLoader } from "./mods/loader.js"; import { ModLoader } from "./mods/loader.js";
import { SavesStorage } from "./storage/saves.js";
export class IpcHandler { export class IpcHandler {
private readonly savesHandler = new FsJobHandler("saves", new SavesStorage()); private readonly savesHandler = new FsJobHandler("saves");
private readonly modLoader: ModLoader; private readonly modLoader: ModLoader;
constructor(modLoader: ModLoader) { constructor(modLoader: ModLoader) {
@ -20,7 +19,7 @@ export class IpcHandler {
// ipcMain.handle("open-mods-folder", ...) // ipcMain.handle("open-mods-folder", ...)
} }
private handleFsJob(_event: IpcMainInvokeEvent, job: FsJob<unknown>) { private handleFsJob(_event: IpcMainInvokeEvent, job: FsJob) {
if (job.id !== "saves") { if (job.id !== "saves") {
throw new Error("Storages other than saves/ are not implemented yet"); throw new Error("Storages other than saves/ are not implemented yet");
} }

View File

@ -1,5 +0,0 @@
export interface StorageInterface<T> {
read(file: string): Promise<T>;
write(file: string, contents: T): Promise<void>;
delete(file: string): Promise<void>;
}

View File

@ -1,16 +0,0 @@
import fs from "node:fs/promises";
import { StorageInterface } from "./interface.js";
export class RawStorage implements StorageInterface<string> {
read(file: string): Promise<string> {
return fs.readFile(file, "utf-8");
}
write(file: string, contents: string): Promise<void> {
return fs.writeFile(file, contents, "utf-8");
}
delete(file: string): Promise<void> {
return fs.unlink(file);
}
}

View File

@ -1,54 +0,0 @@
import { decodeAsync, encode } from "@msgpack/msgpack";
import fs from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGunzip, createGzip } from "node:zlib";
import { StorageInterface } from "./interface.js";
/**
* This storage implementation is used for savegame files and other
* ReadWriteProxy objects. It uses gzipped MessagePack as the file format.
*/
export class SavesStorage implements StorageInterface<unknown> {
async read(file: string): Promise<unknown> {
const stream = fs.createReadStream(file);
const gunzip = createGunzip();
try {
// Any filesystem errors will be uncovered here. This code ensures we return the most
// relevant rejection, or resolve with the decoded data
const [readResult, decodeResult] = await Promise.allSettled([
pipeline(stream, gunzip),
decodeAsync(gunzip),
]);
if (decodeResult.status === "fulfilled") {
return decodeResult.value;
}
// Return the most relevant error
throw readResult.status === "rejected" ? readResult.reason : decodeResult.reason;
} finally {
stream.close();
gunzip.close();
}
}
async write(file: string, contents: unknown): Promise<void> {
const stream = fs.createWriteStream(file);
const gzip = createGzip();
try {
const encoded = encode(contents);
const blob = new Blob([encoded]);
return await pipeline(blob.stream(), gzip, stream);
} finally {
gzip.close();
stream.close();
}
}
delete(file: string): Promise<void> {
return fs.promises.unlink(file);
}
}