qnce-engine
Version:
Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization
348 lines (336 loc) • 11.5 kB
JavaScript
"use strict";
// S2-T2: Multithreaded Job Scheduler Integration
// QnceThreadPool for cache loads and telemetry writes off main thread
Object.defineProperty(exports, "__esModule", { value: true });
exports.QnceThreadPool = void 0;
exports.getThreadPool = getThreadPool;
exports.shutdownThreadPool = shutdownThreadPool;
/**
* QnceThreadPool - Background job processing for QNCE engine
* Handles cache operations, telemetry, and other non-blocking tasks
*/
class QnceThreadPool {
workers = [];
jobQueue = [];
activeJobs = new Map();
config;
stats;
isShuttingDown = false;
constructor(config = {}) {
this.config = {
maxWorkers: config.maxWorkers || Math.max(1, Math.floor(navigator?.hardwareConcurrency || 4) / 2),
queueLimit: config.queueLimit || 100,
idleTimeout: config.idleTimeout || 30000,
enableProfiling: config.enableProfiling || false
};
this.stats = {
activeWorkers: 0,
queuedJobs: 0,
completedJobs: 0,
failedJobs: 0,
avgExecutionTime: 0,
workerUtilization: 0
};
this.initializeWorkers();
}
/**
* Submit job for background processing
*/
async submitJob(type, payload, priority = 'normal') {
return new Promise((resolve, reject) => {
if (this.isShuttingDown) {
reject(new Error('ThreadPool is shutting down'));
return;
}
if (this.jobQueue.length >= this.config.queueLimit) {
reject(new Error('Job queue limit exceeded'));
return;
}
const job = {
id: `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
type,
priority,
payload,
timestamp: performance.now(),
resolve: resolve,
reject
};
// Insert based on priority (high -> normal -> low)
const insertIndex = this.findInsertionIndex(priority);
this.jobQueue.splice(insertIndex, 0, job);
this.stats.queuedJobs = this.jobQueue.length;
this.processQueue();
});
}
/**
* Cache load operation (S2-T2 primary use case)
*/
async loadFromCache(cacheKey, loader) {
return this.submitJob('cache-load', { cacheKey, loader: loader.toString() }, 'normal');
}
/**
* Telemetry write operation (S2-T2 primary use case)
*/
async writeTelemetry(eventData) {
return this.submitJob('telemetry-write', eventData, 'low');
}
/**
* Hot-reload preparation (integration with S2-T3)
*/
async prepareHotReload(deltaData) {
return this.submitJob('hot-reload-prep', deltaData, 'high');
}
/**
* Get current thread pool statistics
*/
getStats() {
return { ...this.stats };
}
/**
* Graceful shutdown of thread pool
*/
async shutdown(timeoutMs = 5000) {
this.isShuttingDown = true;
const startTime = Date.now();
// Wait for active jobs to complete or timeout
while (this.activeJobs.size > 0 && (Date.now() - startTime) < timeoutMs) {
await new Promise(resolve => setTimeout(resolve, 100));
}
// Terminate all workers
for (const worker of this.workers) {
worker.terminate();
}
this.workers.length = 0;
this.stats.activeWorkers = 0;
}
/**
* Initialize worker threads based on environment
*/
initializeWorkers() {
// Browser environment: Use Web Workers
if (typeof Worker !== 'undefined' && typeof window !== 'undefined') {
this.initializeWebWorkers();
}
// Node.js environment: Use worker_threads
else if (typeof require !== 'undefined') {
this.initializeNodeWorkers();
}
// Fallback: Simulate workers with setTimeout (for testing)
else {
this.initializeFallbackWorkers();
}
}
/**
* Web Workers for browser environment
*/
initializeWebWorkers() {
const workerCode = this.generateWebWorkerCode();
const workerBlob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(workerBlob);
for (let i = 0; i < this.config.maxWorkers; i++) {
try {
const worker = new Worker(workerUrl);
this.setupWorkerHandlers(worker, i);
this.workers.push(worker);
this.stats.activeWorkers++;
}
catch (error) {
console.warn(`Failed to create worker ${i}:`, error);
}
}
}
/**
* Node.js worker_threads (placeholder - would need actual implementation)
*/
initializeNodeWorkers() {
// TODO: Implement worker_threads for Node.js environment
// For now, fall back to simulation
this.initializeFallbackWorkers();
}
/**
* Fallback simulation for testing/development
*/
initializeFallbackWorkers() {
// Simulate workers with async processing
for (let i = 0; i < this.config.maxWorkers; i++) {
const mockWorker = {
postMessage: (data) => {
// Simulate async processing
setTimeout(() => {
this.handleWorkerMessage({
data: {
jobId: data.jobId,
result: `Processed: ${JSON.stringify(data.payload)}`,
success: true
}
});
}, Math.random() * 100 + 50); // 50-150ms simulation
},
terminate: () => { },
addEventListener: () => { },
removeEventListener: () => { }
};
this.workers.push(mockWorker);
this.stats.activeWorkers++;
}
}
/**
* Generate Web Worker code for browser execution
*/
generateWebWorkerCode() {
return `
// QNCE Thread Pool Worker
self.addEventListener('message', function(e) {
const { jobId, type, payload } = e.data;
try {
let result;
switch (type) {
case 'cache-load':
// Simulate cache loading
result = processCache(payload);
break;
case 'telemetry-write':
// Simulate telemetry writing
result = writeTelemetryData(payload);
break;
case 'hot-reload-prep':
// Simulate hot-reload preparation
result = prepareReload(payload);
break;
case 'asset-process':
// Simulate asset processing
result = processAsset(payload);
break;
default:
throw new Error('Unknown job type: ' + type);
}
self.postMessage({
jobId,
result,
success: true
});
} catch (error) {
self.postMessage({
jobId,
error: error.message,
success: false
});
}
});
function processCache(payload) {
// Simulate cache processing work
const data = JSON.parse(JSON.stringify(payload));
return { cached: true, data, timestamp: Date.now() };
}
function writeTelemetryData(payload) {
// Simulate telemetry write
return { written: true, bytes: JSON.stringify(payload).length };
}
function prepareReload(payload) {
// Simulate hot-reload preparation
return { prepared: true, deltaSize: JSON.stringify(payload).length };
}
function processAsset(payload) {
// Simulate asset processing
return { processed: true, asset: payload };
}
`;
}
/**
* Setup worker message handlers
*/
setupWorkerHandlers(worker, workerId) {
worker.addEventListener('message', (e) => this.handleWorkerMessage(e));
worker.addEventListener('error', (e) => this.handleWorkerError(e, workerId));
}
/**
* Handle worker completion messages
*/
handleWorkerMessage(event) {
const { jobId, result, error, success } = event.data;
const job = this.activeJobs.get(jobId);
if (!job)
return;
this.activeJobs.delete(jobId);
if (success) {
job.resolve(result);
this.stats.completedJobs++;
}
else {
job.reject(new Error(error));
this.stats.failedJobs++;
}
// Update execution time stats
const executionTime = performance.now() - job.timestamp;
this.updateExecutionTimeStats(executionTime);
// Process next job in queue
this.processQueue();
}
/**
* Handle worker errors
*/
handleWorkerError(error, workerId) {
console.error(`Worker ${workerId} error:`, error);
// TODO: Implement worker recovery/restart logic
}
/**
* Process job queue by assigning jobs to available workers
*/
processQueue() {
if (this.jobQueue.length === 0)
return;
const availableWorkers = this.config.maxWorkers - this.activeJobs.size;
if (availableWorkers <= 0)
return;
const job = this.jobQueue.shift();
this.stats.queuedJobs = this.jobQueue.length;
this.activeJobs.set(job.id, job);
// Find least busy worker (for now, just use round-robin)
const workerIndex = this.stats.completedJobs % this.workers.length;
const worker = this.workers[workerIndex];
worker.postMessage({
jobId: job.id,
type: job.type,
payload: job.payload
});
}
/**
* Find insertion index for job based on priority
*/
findInsertionIndex(priority) {
const priorityValues = { high: 3, normal: 2, low: 1 };
const jobPriority = priorityValues[priority];
for (let i = 0; i < this.jobQueue.length; i++) {
if (priorityValues[this.jobQueue[i].priority] < jobPriority) {
return i;
}
}
return this.jobQueue.length;
}
/**
* Update execution time statistics
*/
updateExecutionTimeStats(executionTime) {
const totalJobs = this.stats.completedJobs + this.stats.failedJobs;
this.stats.avgExecutionTime = ((this.stats.avgExecutionTime * (totalJobs - 1)) + executionTime) / totalJobs;
this.stats.workerUtilization = (this.activeJobs.size / this.config.maxWorkers) * 100;
}
}
exports.QnceThreadPool = QnceThreadPool;
// Singleton instance for global access
let globalThreadPool = null;
function getThreadPool(config) {
if (!globalThreadPool) {
globalThreadPool = new QnceThreadPool(config);
}
return globalThreadPool;
}
function shutdownThreadPool() {
if (globalThreadPool) {
const shutdown = globalThreadPool.shutdown();
globalThreadPool = null;
return shutdown;
}
return Promise.resolve();
}
//# sourceMappingURL=ThreadPool.js.map