mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Merge pull request #1496 from RoboSats/fix-robohash-generator
Fix Robohash generation on web
This commit is contained in:
commit
98a16e633a
@ -1,7 +1,11 @@
|
|||||||
interface Task {
|
interface Task {
|
||||||
robohash: Robohash;
|
robohash: Robohash;
|
||||||
resolves: Array<(result: string) => void>;
|
}
|
||||||
rejects: Array<(reason?: Error) => void>;
|
|
||||||
|
interface RoboWorker {
|
||||||
|
id: number;
|
||||||
|
worker: Worker;
|
||||||
|
busy: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Robohash {
|
interface Robohash {
|
||||||
@ -10,71 +14,25 @@ interface Robohash {
|
|||||||
cacheKey: string;
|
cacheKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RoboWorker {
|
|
||||||
worker: Worker;
|
|
||||||
busy: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RoboGenerator {
|
class RoboGenerator {
|
||||||
private assetsCache: Record<string, string> = {};
|
private assetsCache: Record<string, string> = {};
|
||||||
|
|
||||||
private readonly workers: RoboWorker[] = [];
|
private readonly workers: RoboWorker[] = [];
|
||||||
private readonly queue: Task[] = [];
|
private readonly taskQueue: Task[] = [];
|
||||||
|
private numberOfWorkers: number = 8;
|
||||||
|
private waitingForLibrary: boolean = true;
|
||||||
|
|
||||||
|
private resolves: Record<string, ((result: string) => void)[]> = {};
|
||||||
|
private rejects: Record<string, ((reason?: Error) => void)[]> = {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// limit to 8 workers
|
for (let i = 0; i < this.numberOfWorkers; i++) {
|
||||||
const numCores = 8;
|
this.workers.push(this.createWorker(i));
|
||||||
|
|
||||||
for (let i = 0; i < numCores; i++) {
|
|
||||||
const worker = new Worker(new URL('./robohash.worker.ts', import.meta.url));
|
|
||||||
worker.onmessage = this.assignTasksToWorkers.bind(this);
|
|
||||||
this.workers.push({ worker, busy: false });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private assignTasksToWorkers(): void {
|
setTimeout(() => {
|
||||||
const availableWorker = this.workers.find((w) => !w.busy);
|
this.waitingForLibrary = false;
|
||||||
|
}, 1000);
|
||||||
if (availableWorker) {
|
|
||||||
const task = this.queue.shift();
|
|
||||||
if (task) {
|
|
||||||
availableWorker.busy = true;
|
|
||||||
availableWorker.worker.postMessage(task.robohash);
|
|
||||||
|
|
||||||
// Clean up the event listener and free the worker after receiving the result
|
|
||||||
const cleanup = (): void => {
|
|
||||||
availableWorker.worker.removeEventListener('message', completionCallback);
|
|
||||||
availableWorker.busy = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resolve the promise when the task is completed
|
|
||||||
const completionCallback = (event: MessageEvent): void => {
|
|
||||||
if (event.data.cacheKey === task.robohash.cacheKey) {
|
|
||||||
const { cacheKey, imageUrl } = event.data;
|
|
||||||
|
|
||||||
// Update the cache and resolve the promise
|
|
||||||
this.assetsCache[cacheKey] = imageUrl;
|
|
||||||
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
task.resolves.forEach((f) => {
|
|
||||||
f(imageUrl);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
availableWorker.worker.addEventListener('message', completionCallback);
|
|
||||||
|
|
||||||
// Reject the promise if an error occurs
|
|
||||||
availableWorker.worker.addEventListener('error', (error) => {
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
task.rejects.forEach((f) => {
|
|
||||||
f(new Error(error.message));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public generate: (hash: string, size: 'small' | 'large') => Promise<string> = async (
|
public generate: (hash: string, size: 'small' | 'large') => Promise<string> = async (
|
||||||
@ -86,8 +44,7 @@ class RoboGenerator {
|
|||||||
return this.assetsCache[cacheKey];
|
return this.assetsCache[cacheKey];
|
||||||
} else {
|
} else {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
let task = this.queue.find((t) => t.robohash.cacheKey === cacheKey);
|
let task = this.taskQueue.find((task) => task.robohash.cacheKey === cacheKey);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
task = {
|
task = {
|
||||||
robohash: {
|
robohash: {
|
||||||
@ -95,19 +52,48 @@ class RoboGenerator {
|
|||||||
size,
|
size,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
},
|
},
|
||||||
resolves: [],
|
|
||||||
rejects: [],
|
|
||||||
};
|
};
|
||||||
this.queue.push(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task.resolves.push(resolve);
|
this.resolves[cacheKey] = [...(this.resolves[cacheKey] ?? []), resolve];
|
||||||
task.rejects.push(reject);
|
this.rejects[cacheKey] = [...(this.rejects[cacheKey] ?? []), reject];
|
||||||
|
|
||||||
this.assignTasksToWorkers();
|
this.addTask(task);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
createWorker = (id: number): RoboWorker => {
|
||||||
|
const worker = new Worker(new URL('./robohash.worker.ts', import.meta.url));
|
||||||
|
|
||||||
|
worker.onmessage = (event) => {
|
||||||
|
const { cacheKey, imageUrl } = event.data;
|
||||||
|
// Update the cache and resolve the promise
|
||||||
|
this.assetsCache[cacheKey] = imageUrl;
|
||||||
|
this.resolves[cacheKey].forEach((f) => {
|
||||||
|
f(imageUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.taskQueue.length > 0) {
|
||||||
|
const nextTask = this.taskQueue.shift();
|
||||||
|
this.workers[id].busy = true;
|
||||||
|
this.workers[id].worker.postMessage(nextTask);
|
||||||
|
} else {
|
||||||
|
this.workers[id].busy = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { id, worker, busy: false };
|
||||||
|
};
|
||||||
|
|
||||||
|
addTask = (task: any) => {
|
||||||
|
const availableWorker = this.workers.find((w) => !w.busy);
|
||||||
|
if (availableWorker && !this.waitingForLibrary) {
|
||||||
|
availableWorker.worker.postMessage(task);
|
||||||
|
} else {
|
||||||
|
this.taskQueue.push(task);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const robohash = new RoboGenerator();
|
export const robohash = new RoboGenerator();
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { async_generate_robohash } from 'robo-identities-wasm';
|
import { async_generate_robohash } from 'robo-identities-wasm';
|
||||||
|
|
||||||
// Listen for messages from the main thread
|
self.onmessage = async (event) => {
|
||||||
self.addEventListener('message', (event) => {
|
if (!event.data) return;
|
||||||
void (async () => {
|
try {
|
||||||
const { hash, size, cacheKey } = event.data;
|
const { hash, size, cacheKey } = event.data.robohash;
|
||||||
|
|
||||||
// Generate the image using async_image_base
|
// Generate the image using async_image_base
|
||||||
const t0 = performance.now();
|
|
||||||
const avatarB64: string = await async_generate_robohash(hash, size === 'small' ? 80 : 256);
|
const avatarB64: string = await async_generate_robohash(hash, size === 'small' ? 80 : 256);
|
||||||
const imageUrl = `data:image/png;base64,${avatarB64}`;
|
const imageUrl = `data:image/png;base64,${avatarB64}`;
|
||||||
const t1 = performance.now();
|
|
||||||
console.log(`Avatar generated in: ${t1 - t0} ms`);
|
|
||||||
// Send the result back to the main thread
|
// Send the result back to the main thread
|
||||||
self.postMessage({ cacheKey, imageUrl });
|
self.postMessage({ cacheKey, imageUrl });
|
||||||
})();
|
} catch (error) {
|
||||||
});
|
console.error('Wasm error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user