Hey everyone!
As I mentioned in this post,
Obsidian currently only uses a single core for processing, and GPU acceleration provides little to no improvement in its rendering performance, and considering that the official team hasn’t made any clear statements about turning it into a multi-core application or using GPUs to accelerate rendering, I’ve been wondering if it’s possible to use a plugin to enable Obsidian to utilize multiple cores for rendering lists, canvas cards and notes, as well as windows. So, I asked AI to help me sketch out a simple solution. Here’s the idea:
The Problem:
- Single-core bottleneck: Obsidian currently handles rendering tasks (such as file rendering, canvas updates, and list processing) on a single CPU core, which causes lag and slowdowns, especially with larger datasets.
- Multiple open windows/canvases: When working with multiple windows, complex canvases, or large lists, performance degrades since Obsidian can’t utilize the full potential of multi-core CPUs.
The Solution: Multi-Core Rendering Plugin
The idea is to create a plugin that enables Obsidian to process content in parallel using multiple CPU cores. By doing so, it would improve the performance of tasks like:
- File Rendering: Assigning different files to different cores for quicker rendering.
- Canvas Rendering: Splitting the canvas into smaller tasks, with each card or note being handled by a different core.
- List Rendering: Breaking long lists into chunks and processing each chunk on a separate core to load the content faster.
How It Would Work:
The plugin would leverage worker threads to offload tasks and run them on separate CPU cores. Here’s how it could work for different components:
- File Rendering: Each file opened in Obsidian would be assigned its own worker thread. This would allow files to be rendered independently, so if you’re working with multiple files, the CPU can handle them simultaneously.
- Canvas Rendering: For canvases with many interconnected notes or cards, the plugin would assign each card or note to a separate worker thread. This would ensure smooth interaction with the canvas, even when it’s large or complex.
- List Rendering: Long lists in Obsidian (especially when you’re dealing with hundreds or thousands of items) could be split into smaller chunks. These chunks would be processed by different worker threads, enabling faster list rendering without blocking the main thread.
Example Code for the Plugin:
obsidian-multicore-render
│
├── main.ts # The main entry file for the plugin, listens to windows, files, canvases, and lists
├── workers
│ ├── renderFileWorker.js # Worker thread for rendering files
│ ├── renderCanvasWorker.js # Worker thread for rendering canvas cards/notes
│ └── renderListWorker.js # Worker thread for rendering lists
└── package.json # Plugin metadata file
Here’s a basic breakdown of how the plugin might look:
Plugin Entry (main.ts):
import { Plugin } from 'obsidian';
import { Worker } from 'worker_threads';
export default class MultiCoreRenderer extends Plugin {
private fileWorkers: Map<string, Worker> = new Map();
private canvasWorkers: Map<string, Worker> = new Map();
private listWorkers: Worker[] = [];
async onload() {
console.log('Loading multi-core rendering plugin...');
// Listen for file changes
this.registerEvent(this.app.workspace.on('file-open', (file) => {
if (file) this.assignFileWorker(file.path);
}));
// Listen for canvas changes
this.registerEvent(this.app.workspace.on('canvas-changed', (canvas) => {
if (canvas) this.assignCanvasWorker(canvas.id);
}));
// Listen for list changes in the editor
this.registerEvent(this.app.workspace.on('editor-change', (editor) => {
const content = editor.getValue();
const listItems = content.match(/^- .+/gm) || [];
this.splitListForMultiCoreRendering(listItems);
}));
}
// File rendering: Assign a separate worker for each file
assignFileWorker(filePath: string) {
if (this.fileWorkers.has(filePath)) return;
const worker = new Worker('./workers/renderFileWorker.js', { workerData: { filePath } });
this.fileWorkers.set(filePath, worker);
worker.on('message', (result) => {
console.log(`File ${filePath} rendering complete`, result);
});
}
// Canvas rendering: Assign a separate worker for each card/note
assignCanvasWorker(canvasId: string) {
if (this.canvasWorkers.has(canvasId)) return;
const worker = new Worker('./workers/renderCanvasWorker.js', { workerData: { canvasId } });
this.canvasWorkers.set(canvasId, worker);
worker.on('message', (result) => {
console.log(`Canvas ${canvasId} rendering complete`, result);
});
}
// List rendering: Split the list into multiple tasks and use multiple workers for processing
splitListForMultiCoreRendering(listItems: string[]) {
const chunkSize = Math.ceil(listItems.length / 4); // Assuming 4 cores are used
const chunks = [];
for (let i = 0; i < listItems.length; i += chunkSize) {
chunks.push(listItems.slice(i, i + chunkSize));
}
chunks.forEach((chunk, index) => {
const worker = new Worker('./workers/renderListWorker.js', { workerData: { chunk, index } });
this.listWorkers.push(worker);
worker.on('message', (result) => {
console.log(`List rendering task ${index} complete`, result);
});
});
}
}
renderFileWorker.js
const { parentPort, workerData } = require('worker_threads');
const filePath = workerData.filePath;
function renderFile(filePath) {
// Simulate file rendering
console.log(`Rendering file: ${filePath}`);
// Simulate time-consuming task
for (let i = 0; i < 1e8; i++) {}
return `Rendering complete: ${filePath}`;
}
const result = renderFile(filePath);
parentPort.postMessage(result);
renderCanvasWorker.js
const { parentPort, workerData } = require('worker_threads');
const canvasId = workerData.canvasId;
function renderCanvas(canvasId) {
console.log(`Rendering Canvas: ${canvasId}`);
// Simulate time-consuming task
for (let i = 0; i < 1e8; i++) {}
return `Rendering complete: ${canvasId}`;
}
const result = renderCanvas(canvasId);
parentPort.postMessage(result);
renderListWorker.js
const { parentPort, workerData } = require('worker_threads');
const { chunk, index } = workerData;
function renderListChunk(chunk) {
console.log(`Rendering list chunk: ${index}`);
// Simulate time-consuming task
for (let i = 0; i < 1e7; i++) {}
return `List chunk ${index} rendered, item count: ${chunk.length}`;
}
const result = renderListChunk(chunk);
parentPort.postMessage(result);
Why This Would Be Awesome:
- Faster Rendering : Tasks like file loading, canvas rendering, and list processing would be much faster since they’re being processed in parallel.
- Better Multi-Tasking : You could have multiple windows, canvases, and lists open and rendered simultaneously without lag.
- Optimized for Power Users : If you’re someone who works with large note collections, multiple windows, or complex visualizations, this plugin would drastically improve your workflow.