diff --git a/src/data/schemata/consoleKind.ts b/src/data/schemata/consoleKind.ts new file mode 100644 index 00000000..6850de2e --- /dev/null +++ b/src/data/schemata/consoleKind.ts @@ -0,0 +1,13 @@ +import * as S from '@effect/schema/Schema'; + +export const consoleKindSchema = S.union( + S.literal('debug'), + S.literal('error'), + S.literal('log'), + S.literal('info'), + S.literal('trace'), + S.literal('warn'), +); + +export type ConsoleKind = S.Schema.To; +export const parseConsoleKind = S.parseSync(consoleKindSchema); diff --git a/src/data/schemata/mainThreadMessage.ts b/src/data/schemata/mainThreadMessage.ts new file mode 100644 index 00000000..6ad62463 --- /dev/null +++ b/src/data/schemata/mainThreadMessage.ts @@ -0,0 +1,6 @@ +import * as S from '@effect/schema/Schema'; + +const mainThreadMessageSchema = S.union(S.struct({ kind: S.literal('exit') })); + +export type MainThreadMessage = S.Schema.To; +export const decodeMainThreadMessage = S.parseSync(mainThreadMessageSchema); diff --git a/src/data/schemata/workerThreadMessage.ts b/src/data/schemata/workerThreadMessage.ts new file mode 100644 index 00000000..841e0393 --- /dev/null +++ b/src/data/schemata/workerThreadMessage.ts @@ -0,0 +1,18 @@ +import * as S from '@effect/schema/Schema'; +import { consoleKindSchema } from './consoleKind'; + +const workerThreadMessageSchema = S.union( + S.struct({ + kind: S.literal('error'), + message: S.string, + path: S.union(S.string, S.undefined), + }), + S.struct({ + kind: S.literal('console'), + consoleKind: consoleKindSchema, + message: S.string, + }), +); + +export type WorkerThreadMessage = S.Schema.To; +export const decodeWorkerThreadMessage = S.parseSync(workerThreadMessageSchema); diff --git a/src/extension.ts b/src/extension.ts index 8964e8c6..da88b8a9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -47,6 +47,9 @@ import { GlobalStateTokenStorage, UserService } from './components/userService'; import { HomeDirectoryService } from './data/readHomeDirectoryCases'; import { isLeft } from 'fp-ts/lib/Either'; import { createClearStateCommand } from './commands/clearStateCommand'; +import { isMainThread, Worker } from 'node:worker_threads'; +import { executeWorkerThread } from './worker'; +import { decodeWorkerThreadMessage } from './data/schemata/workerThreadMessage'; export const enum SEARCH_PARAMS_KEYS { ENGINE = 'engine', @@ -60,9 +63,45 @@ export const enum SEARCH_PARAMS_KEYS { ACCESS_TOKEN = 'accessToken', } +const WORKER_THREADS_COUNT = 10; + const messageBus = new MessageBus(); export async function activate(context: vscode.ExtensionContext) { + if (isMainThread) { + for (let i = 0; i < WORKER_THREADS_COUNT; ++i) { + const worker = new Worker(__filename); + + worker.on('message', (m: unknown) => { + const workerThreadMessage = decodeWorkerThreadMessage(m); + + if (workerThreadMessage.kind === 'console') { + console[workerThreadMessage.consoleKind]( + workerThreadMessage.message, + ); + return; + } + + if (workerThreadMessage.kind === 'error') { + console.error( + workerThreadMessage.message, + workerThreadMessage.path, + ); + } + }); + } + + execute(context).catch((error) => { + if (error instanceof Error) { + console.error(JSON.stringify({ message: error.message })); + } + }); + } else { + executeWorkerThread(); + } +} + +async function execute(context: vscode.ExtensionContext) { const rootUri = vscode.workspace.workspaceFolders?.[0]?.uri ?? null; messageBus.setDisposables(context.subscriptions); diff --git a/src/worker.ts b/src/worker.ts new file mode 100644 index 00000000..9a91f784 --- /dev/null +++ b/src/worker.ts @@ -0,0 +1,42 @@ +import { parentPort } from 'node:worker_threads'; + +import { ConsoleKind } from './data/schemata/consoleKind'; +import { WorkerThreadMessage } from './data/schemata/workerThreadMessage'; +import { decodeMainThreadMessage } from './data/schemata/mainThreadMessage'; + +class PathAwareError extends Error { + constructor(public readonly path: string, message?: string | undefined) { + super(message); + } +} + +const sendLog = (consoleKind: ConsoleKind, message: string): void => { + parentPort?.postMessage({ + kind: 'console', + consoleKind, + message, + } satisfies WorkerThreadMessage); +}; + +const messageHandler = async (m: unknown) => { + try { + const message = decodeMainThreadMessage(m); + + if (message.kind === 'exit') { + parentPort?.off('message', messageHandler); + return; + } + + sendLog('log', `Received message: ${JSON.stringify(message)}`); + } catch (error) { + parentPort?.postMessage({ + kind: 'error', + message: error instanceof Error ? error.message : String(error), + path: error instanceof PathAwareError ? error.path : undefined, + } satisfies WorkerThreadMessage); + } +}; + +export const executeWorkerThread = () => { + parentPort?.on('message', messageHandler); +};