专栏文章
冬日绘板脚本
算法·理论参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minmkeuy
- 此快照首次捕获于
- 2025/12/02 04:51 3 个月前
- 此快照最后确认于
- 2025/12/02 04:51 3 个月前
JAVASCRIPT
import WebSocket from 'ws';
import sharp from 'sharp';
import fs from 'fs';
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
// 极致性能配置
const CONFIG = {
uid: 1099138,
accessKey: "edRx7TMg",
token: "f6a1a59f-f195-4720-85ce-6c86ceb7fe73",
imagePath: "./white.png",
targetX: 0,
targetY: 0,
scale: 1,
maxThreads: 4, // 增加线程数
wsUrl: "wss://paintboard.luogu.me/api/paintboard/ws?writeonly=1",
apiUrl: "https://paintboard.luogu.me",
maintenanceMode: true, // 关闭维护模式
sendInterval: 1, // 减少发送间隔到1ms
retryDelay: 20,
repairBatchSize: 30000, // 大幅减少修复批次大小
maxPendingRequests: 500, // 减少待处理请求
aggressiveMode: false,
maxBatchSize: 30000, // 增加每批最大像素数
maxChunkSize: 30000, // 增加最大块大小
pingInterval: 30000, // 30秒发送一次心跳检测
pongTimeout: 10000, // 10秒内必须收到pong响应
maxMissedPongs: 2, // 最多允许2次pong超时
connectionHealthCheck: 30000, // 30秒检查一次连接健康状态
debugMode: true, // 添加调试模式
pixelsPerSecond: 30000, // 每秒发送30000像素
smoothSending: true, // 启用平滑发送
packetsPerThread: 256, // 增加每个线程的包数限制
};
class RepairManager {
constructor(controller) {
this.controller = controller;
this.repairWorker = null;
this.pendingRepairs = new Map();
this.batchInterval = 10; // 0.01秒批量处理
this.maxBatchSize = 30000; // 每批最大修复数量
this.repairStats = {
totalQueued: 0,
totalSent: 0,
lastReportTime: Date.now()
};
this.init();
}
init() {
try {
// 创建专门的修复工作线程
this.repairWorker = new Worker(new URL(import.meta.url), {
workerData: {
workerId: 'repair',
config: CONFIG,
isRepairWorker: true
}
});
this.repairWorker.on('message', (message) => {
this.handleRepairMessage(message);
});
this.repairWorker.on('error', (error) => {
console.error('修复工作线程错误:', error);
});
this.repairWorker.on('exit', (code) => {
if (code !== 0) {
console.log(`修复工作线程异常退出: ${code},5秒后重启`);
setTimeout(() => this.init(), 5000);
}
});
// 定时批量处理修复队列
setInterval(() => this.processRepairBatch(), this.batchInterval);
} catch (error) {
console.error('修复管理器初始化失败:', error);
setTimeout(() => this.init(), 5000);
}
}
// 添加需要修复的像素
addRepairPixel(x, y, currentR, currentG, currentB) {
const pixelKey = `${x},${y}`;
const expected = this.controller.expectedPixels.get(pixelKey);
if (!expected) return;
// 检查颜色是否确实需要修复
if (expected.r === currentR && expected.g === currentG && expected.b === currentB) {
return;
}
// 更新或添加修复任务
this.pendingRepairs.set(pixelKey, {
x, y,
r: expected.r,
g: expected.g,
b: expected.b,
timestamp: Date.now()
});
this.repairStats.totalQueued++;
}
// 批量处理修复任务
processRepairBatch(batch) {
if (!batch || !Array.isArray(batch) || batch.length === 0) return;
console.log(`🔧 修复工作线程处理 ${batch.length} 个修复像素`);
let processed = 0;
for (const pixel of batch) {
// 验证像素数据
if (!pixel || typeof pixel.x !== 'number' || typeof pixel.y !== 'number') {
continue;
}
const paintData = this.createPaintData(pixel);
if (paintData) {
this.appendData(paintData);
processed++;
// 限制单次处理数量,避免堆积
if (processed >= this.batchControl.maxBatchSize) {
break;
}
}
}
if (processed > 0) {
parentPort.postMessage({
type: 'repairCompleted',
count: processed
});
}
}
handleRepairMessage(message) {
switch (message.type) {
case 'repairCompleted':
this.controller.performanceStats.repairedPixels += message.count;
break;
case 'repairStats':
console.log(`🔧 修复线程: 已修复 ${message.totalRepaired} 像素, 队列: ${this.pendingRepairs.size}`);
break;
case 'ready':
console.log('🔧 修复工作线程就绪');
break;
}
}
reportStats() {
const now = Date.now();
const elapsed = (now - this.repairStats.lastReportTime) / 1000;
const rate = elapsed > 0 ? (this.repairStats.totalSent / elapsed).toFixed(1) : 0;
console.log(`🛠️ 修复统计: 队列 ${this.pendingRepairs.size} | 累计发送 ${this.repairStats.totalSent} | 速率 ${rate}/秒`);
this.repairStats.lastReportTime = now;
this.repairStats.totalSent = 0;
}
shutdown() {
if (this.repairWorker) {
this.repairWorker.postMessage({ type: 'shutdown' });
setTimeout(() => this.repairWorker.terminate(), 50);
}
}
}
// 高性能主线程控制器
class PaintBoardController {
constructor() {
this.workers = new Map();
this.imageData = null;
this.workerReadyCount = 0;
this.boardState = new Map();
this.expectedPixels = new Map();
this.repairQueue = [];
this.repairManager = new RepairManager(this);
this.globalRateLimit = {
maxTotalPacketsPerSecond: CONFIG.packetsPerThread * CONFIG.maxThreads, // 全局总限制
currentSecondPackets: 0,
lastResetTime: Date.now()
};
this.performanceStats = {
totalPixels: 0,
paintedPixels: 0,
repairedPixels: 0,
startTime: Date.now(),
totalRequests: 0,
};
setInterval(() => {
this.globalRateLimit.currentSecondPackets = 0;
this.globalRateLimit.lastResetTime = Date.now();
}, 1000);
}
checkGlobalRateLimit() {
const now = Date.now();
if (now - this.globalRateLimit.lastResetTime >= 1000) {
this.globalRateLimit.currentSecondPackets = 0;
this.globalRateLimit.lastResetTime = now;
}
if (this.globalRateLimit.currentSecondPackets >= this.globalRateLimit.maxTotalPacketsPerSecond) {
return false;
}
this.globalRateLimit.currentSecondPackets++;
return true;
}
startGlobalProgressMonitor() {
setInterval(() => {
const now = Date.now();
const elapsed = (now - this.performanceStats.startTime) / 1000;
const totalSent = this.performanceStats.paintedPixels;
const totalPixels = this.performanceStats.totalPixels;
const progress = totalPixels > 0 ? (totalSent / totalPixels * 100).toFixed(2) : 0;
const pixelsPerSecond = totalSent > 0 ? (totalSent / elapsed).toFixed(1) : 0;
console.log(`🎯 全局进度: ${totalSent}/${totalPixels} (${progress}%) | ${pixelsPerSecond} 像素/秒 | 运行: ${Math.floor(elapsed)}秒`);
// 如果进度长时间没有变化,可能是连接问题
if (this.lastProgress === totalSent && elapsed > 30) {
console.log(`⚠️ 进度停滞,检查连接状态`);
}
this.lastProgress = totalSent;
}, 10000); // 每10秒报告一次
}
async loadImage() {
try {
if (!fs.existsSync(CONFIG.imagePath)) {
console.error(`图片文件不存在: ${CONFIG.imagePath}`);
return false;
}
console.log("正在极速加载图片...");
const image = sharp(CONFIG.imagePath);
const metadata = await image.metadata();
const [scaledWidth, scaledHeight] = [
Math.floor(metadata.width * CONFIG.scale),
Math.floor(metadata.height * CONFIG.scale)
];
const resizedImage = image.resize(scaledWidth, scaledHeight).removeAlpha();
const pixels = await resizedImage.raw().toBuffer();
this.imageData = {
width: scaledWidth,
height: scaledHeight,
channels: Math.min(metadata.channels, 3),
pixels: pixels
};
console.log(`调整后尺寸: ${scaledWidth} x ${scaledHeight}`);
return true;
} catch (error) {
console.error(`加载图片时出错: ${error.message}`);
return false;
}
}
// 预处理所有像素数据
prepareAllPixels() {
if (!this.imageData) return;
const { width, height, channels, pixels } = this.imageData;
const allPixels = [];
// 计算实际的起始坐标,确保图片在画板范围内
const boardWidth = 1000;
const boardHeight = 600;
// 如果目标坐标为负数或超出范围,自动调整到合适位置
let startX = CONFIG.targetX;
let startY = CONFIG.targetY;
// 确保图片不会超出画板边界
if (startX < 0) startX = 0;
if (startY < 0) startY = 0;
if (startX + width > boardWidth) startX = boardWidth - width;
if (startY + height > boardHeight) startY = boardHeight - height;
// 如果图片太大,从(0,0)开始绘制
if (width > boardWidth || height > boardHeight) {
startX = 0;
startY = 0;
console.warn(`图片尺寸 ${width}x${height} 超出画板范围 ${boardWidth}x${boardHeight},将从(0,0)开始绘制`);
}
console.log(`实际绘制起始坐标: (${startX}, ${startY}), 图片尺寸: ${width}x${height}`);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = (y * width + x) * channels;
const r = pixels[index];
const g = pixels[index + 1];
const b = pixels[index + 2];
// 跳过白色像素
// if (r === 255 && g === 255 && b === 255) continue;
const targetX = startX + x;
const targetY = startY + y;
// 确保目标坐标在画板范围内
if (targetX >= 0 && targetX < boardWidth && targetY >= 0 && targetY < boardHeight) {
allPixels.push({ x: targetX, y: targetY, r, g, b });
this.expectedPixels.set(`${targetX},${targetY}`, { x: targetX, y: targetY, r, g, b });
}
}
}
this.performanceStats.totalPixels = allPixels.length;
console.log(`预处理完成: ${allPixels.length} 个像素`);
return allPixels;
}
// 将像素平均分配给工作线程
distributePixelsToWorkers(allPixels) {
const workerCount = CONFIG.maxThreads;
const pixelsPerWorker = Math.ceil(allPixels.length / workerCount);
console.log(`每个工作线程处理约 ${pixelsPerWorker} 个像素`);
for (let i = 0; i < workerCount; i++) {
const startIdx = i * pixelsPerWorker;
const endIdx = Math.min(startIdx + pixelsPerWorker, allPixels.length);
const workerPixels = allPixels.slice(startIdx, endIdx);
const worker = this.workers.get(i);
if (worker) {
worker.postMessage({
type: 'assignPixels',
pixels: workerPixels
});
console.log(`分配 ${workerPixels.length} 个像素给工作线程 ${i}`);
}
}
}
async startWorkers() {
const workerCount = CONFIG.maxThreads;
for (let i = 0; i < workerCount; i++) {
const worker = new Worker(new URL(import.meta.url), {
workerData: {
workerId: i,
config: CONFIG
}
});
this.workers.set(i, worker);
worker.on('message', (message) => {
this.handleWorkerMessage(i, message);
});
worker.on('error', (error) => {
console.error(`工作线程 ${i} 错误:`, error);
});
worker.on('exit', (code) => {
console.log(`工作线程 ${i} 退出: ${code}`);
this.workers.delete(i);
setTimeout(() => this.startWorker(i), 100);
});
}
console.log(`启动了 ${workerCount} 个工作线程`);
}
async startWorker(workerId) {
const worker = new Worker(new URL(import.meta.url), {
workerData: {
workerId: workerId,
config: CONFIG
}
});
this.workers.set(workerId, worker);
console.log(`工作线程 ${workerId} 已重启`);
}
handleWorkerMessage(workerId, message) {
switch (message.type) {
case 'ready':
this.workerReadyCount++;
console.log(`工作线程 ${workerId} 就绪`);
if (this.workerReadyCount === this.workers.size) {
const allPixels = this.prepareAllPixels();
this.distributePixelsToWorkers(allPixels);
this.startPerformanceMonitor();
}
break;
case 'pixelPainted':
this.performanceStats.paintedPixels++;
break;
case 'pixelRepaired':
this.performanceStats.repairedPixels++;
break;
case 'needRepair':
this.addToRepairQueue(message.x, message.y, message.r, message.g, message.b);
break;
case 'requestSent':
this.performanceStats.totalRequests++;
break;
case 'needRepair':
this.repairManager.addRepairQueue(message.x, message.y, message.r, message.g, message.b);
break;
case 'pixelOverwritten':
this.repairManager.addRepairPixel(message.x, message.y, message.r, message.g, message.b);
break;
}
}
addToRepairQueue(x, y, currentR, currentG, currentB) {
const pixelKey = `${x},${y}`;
const expected = this.expectedPixels.get(pixelKey);
if (expected && (expected.r !== currentR || expected.g !== currentG || expected.b !== currentB)) {
const existingIndex = this.repairQueue.findIndex(p => p.x === x && p.y === y);
if (existingIndex === -1) {
this.repairQueue.push({
x, y,
r: expected.r,
g: expected.g,
b: expected.b,
timestamp: Date.now()
});
// 如果修复队列较大,分配给工作线程处理
if (this.repairQueue.length >= CONFIG.repairBatchSize) {
this.distributeRepairTasks();
}
}
}
}
distributeRepairTasks() {
if (this.repairQueue.length === 0) return;
const repairBatch = this.repairQueue.splice(0, CONFIG.repairBatchSize);
// 平均分配给所有工作线程
const workerCount = this.workers.size;
const repairPerWorker = Math.ceil(repairBatch.length / workerCount);
for (let i = 0; i < workerCount; i++) {
const startIdx = i * repairPerWorker;
const endIdx = Math.min(startIdx + repairPerWorker, repairBatch.length);
const workerRepair = repairBatch.slice(startIdx, endIdx);
if (workerRepair.length > 0) {
const worker = this.workers.get(i);
if (worker) {
worker.postMessage({
type: 'repairPixels',
pixels: workerRepair
});
}
}
}
console.log(`分配了 ${repairBatch.length} 个修复任务给工作线程`);
}
startPerformanceMonitor() {
// setInterval(() => {
// const now = Date.now();
// const elapsed = (now - this.performanceStats.startTime) / 1000;
// const pixelsPerSecond = this.performanceStats.paintedPixels / elapsed;
// const progress = this.performanceStats.totalPixels > 0
// ? (this.performanceStats.paintedPixels / this.performanceStats.totalPixels * 100).toFixed(2)
// : 0;
// console.log(`🚀 性能统计: ${this.performanceStats.paintedPixels}/${this.performanceStats.totalPixels} 像素 (${progress}%) | ${pixelsPerSecond.toFixed(1)} 像素/秒 | 修复: ${this.performanceStats.repairedPixels} | 修复队列: ${this.repairQueue.length}`);
// }, 3000);
}
async main() {
console.log("🚀 LGS Paintboard 极致性能多线程客户端启动");
const imageLoaded = await this.loadImage();
if (!imageLoaded) return;
await this.startWorkers();
}
shutdown() {
for (const [workerId, worker] of this.workers) {
worker.postMessage({ type: 'shutdown' });
setTimeout(() => worker.terminate(), 50);
}
if (this.repairManager) {
this.repairManager.shutdown();
}
}
}
// 极致性能工作线程
class PaintBoardWorker {
constructor() {
this.workerId = workerData.workerId;
this.config = workerData.config;
this.ws = null;
this.isConnected = false;
this.paintId = 0;
this.pendingCallbacks = new Map();
this.cooldownPixels = new Map();
this.assignedPixels = [];
this.sendInterval = null;
this.chunks = [];
this.totalSize = 0;
this.performance = {
pixelsPainted: 0,
pixelsRepaired: 0,
requestsSent: 0,
};
// 添加连接状态监控
this.connectionStats = {
totalConnections: 0,
failedConnections: 0,
lastConnectionTime: 0
};
// 添加批次管理
this.batchManager = {
batches: [],
currentBatchIndex: 0,
maxPixelsPerBatch: this.config.maxBatchSize || 30000,
isProcessing: false
};
this.performanceStats = {
totalPixels: this.assignedPixels.length, // 实际分配的像素数
pixelsSent: 0, // 实际发送的像素数(去重)
uniquePixelsSent: new Set(), // 用于追踪已发送的像素
batchesCompleted: 0,
startTime: Date.now(),
lastReportTime: Date.now()
};
// 添加发送速率限制
this.rateLimiter = {
maxPacketsPerSecond: this.config.packetsPerThread || 256,
packetsSentInCurrentSecond: 0,
currentSecondStart: Date.now(),
packetQueue: [],
isRateLimited: false,
resetInterval: null
};
// 添加性能监控变量
this.performanceMonitor = {
lastLogTime: Date.now(),
packetsSinceLastLog: 0,
totalPacketsSent: 0
};
// 启动速率限制重置定时器
this.rateLimiter.resetInterval = setInterval(() => {
this.rateLimiter.packetsSentInCurrentSecond = 0;
this.rateLimiter.currentSecondStart = Date.now();
this.rateLimiter.isRateLimited = false;
}, 1000);
this.initialize();
}
async initialize() {
// 延迟启动,避免所有线程同时连接
const delay = this.workerId * 1000; // 每个线程间隔1秒启动
await new Promise(resolve => setTimeout(resolve, delay));
await this.connect();
}
async connect() {
if (!this.config.token) {
await this.getToken();
}
return new Promise((resolve) => {
try {
console.log(`工作线程 ${this.workerId} 正在连接...`);
this.ws = new WebSocket(this.config.wsUrl, {
perMessageDeflate: false, // 禁用压缩避免协议问题
maxPayload: 30000, // 增加最大载荷大小
skipUTF8Validation: false // 确保UTF8验证
});
this.ws.binaryType = "arraybuffer";
this.ws.onopen = () => {
console.log(`工作线程 ${this.workerId} 连接成功`);
this.isConnected = true;
this.connectionStats.totalConnections++;
this.connectionStats.lastConnectionTime = Date.now();
this.startSending();
parentPort.postMessage({ type: 'ready', workerId: this.workerId });
resolve(true);
};
this.ws.onmessage = (event) => {
this.handleMessage(event.data);
};
this.ws.onerror = (err) => {
console.error(`工作线程 ${this.workerId} 连接错误:`, err.message);
this.connectionStats.failedConnections++;
};
this.ws.onclose = (event) => {
this.isConnected = false;
const reason = event.reason || "Unknown";
const code = event.code || "Unknown";
console.log(`工作线程 ${this.workerId} 连接关闭 (${code}: ${reason}),0.1秒后重连`);
if (this.sendInterval) {
clearInterval(this.sendInterval);
this.sendInterval = null;
}
// 根据错误代码调整重连延迟
let reconnectDelay = 1000;
if (code === 1008) { // 策略限制
reconnectDelay = 5000;
} else if (code === 1011) { // 服务器错误
reconnectDelay = 3000;
}
setTimeout(() => {
this.connect().catch(console.error);
}, reconnectDelay);
};
} catch (error) {
console.error(`工作线程 ${this.workerId} 连接异常:`, error.message);
setTimeout(() => {
this.connect().catch(console.error);
}, 2000);
}
});
}
async getToken() {
try {
const response = await fetch(`${this.config.apiUrl}/api/auth/gettoken`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uid: this.config.uid,
access_key: this.config.accessKey
})
});
const data = await response.json();
if (data.token) {
this.config.token = data.token;
return true;
}
} catch (error) {
console.error(`工作线程 ${this.workerId} 获取Token失败:`, error.message);
}
return false;
}
handleMessage(buffer) {
const dataView = new DataView(buffer);
let offset = 0;
while (offset < buffer.byteLength) {
const type = dataView.getUint8(offset);
offset += 1;
switch (type) {
case 0xfa:
const x = dataView.getUint16(offset, true);
const y = dataView.getUint16(offset + 2, true);
const colorR = dataView.getUint8(offset + 4);
const colorG = dataView.getUint8(offset + 5);
const colorB = dataView.getUint8(offset + 6);
offset += 7;
// 实时检测覆盖并通知修复管理器
parentPort.postMessage({
type: 'pixelOverwritten',
x, y, r: colorR, g: colorG, b: colorB
});
break;
case 0xfc: // 服务端 ping
// 立即响应 pong,不要延迟
if (this.isConnected && this.ws && this.ws.readyState === this.ws.OPEN) {
try {
// 直接发送单个字节的 pong 响应
const pongData = new Uint8Array([0xfb]);
this.ws.send(pongData);
} catch (error) {
console.error(`工作线程 ${this.workerId} 发送 pong 失败:`, error.message);
}
}
break;
case 0xff:
const id = dataView.getUint32(offset, true);
const code = dataView.getUint8(offset + 4);
offset += 5;
this.handlePaintResult(id, code);
break;
default:
console.log(`工作线程 ${this.workerId} 未知消息类型: 0x${type.toString(16)}`);
break;
}
}
}
sendPong() {
if (this.isConnected) {
this.ws.send(new Uint8Array([0xfb]));
}
}
handlePaintResult(id, code) {
const callback = this.pendingCallbacks.get(id);
if (callback) {
this.pendingCallbacks.delete(id);
callback(code);
}
if (code === 0xef) {
// 绘画成功
this.performanceStats.pixelsAccepted++;
parentPort.postMessage({
type: 'pixelAccepted',
workerId: this.workerId
});
} else if (code === 0xee) {
// 冷却中,不需要修复
console.log(`⏸️ 工作线程 ${this.workerId} 像素冷却中`);
} else {
// 其他错误
console.log(`❌ 工作线程 ${this.workerId} 绘画失败: 0x${code.toString(16)}`);
parentPort.postMessage({
type: 'pixelFailed',
workerId: this.workerId,
code: code
});
}
}
uintToUint8Array(uint, bytes) {
const array = new Uint8Array(bytes);
for (let i = 0; i < bytes; i++) {
array[i] = uint & 0xff;
uint = uint >> 8;
}
return array;
}
// 预处理像素数据包
createPaintData(pixel) {
const id = (this.paintId++) % 4294967296;
const tokenBytes = new Uint8Array(16);
const hexToken = this.config.token.replace(/-/g, '');
for (let j = 0; j < 32; j += 2) {
tokenBytes[j / 2] = parseInt(hexToken.substr(j, 2), 16);
}
const paintData = new Uint8Array([
0xfe,
...this.uintToUint8Array(pixel.x, 2),
...this.uintToUint8Array(pixel.y, 2),
pixel.r, pixel.g, pixel.b,
...this.uintToUint8Array(this.config.uid, 3),
...tokenBytes,
...this.uintToUint8Array(id, 4)
]);
this.pendingCallbacks.set(id, (code) => {
this.handlePaintResult(id, code);
});
return paintData;
}
// 预处理所有分配像素的数据包,支持拆分成多个批次
preprocessAllPixels(pixels) {
console.log(`工作线程 ${this.workerId} 预处理 ${pixels.length} 个像素数据包`);
this.batchManager.batches = [];
this.batchManager.currentBatchIndex = 0;
// 增加批次大小,减少批次数量
const batchSize = Math.min(30000, this.batchManager.maxPixelsPerBatch); // 增加到30000
const batchCount = Math.ceil(pixels.length / batchSize);
console.log(`工作线程 ${this.workerId} 将拆分为 ${batchCount} 个批次,每批 ${batchSize} 像素`);
for (let i = 0; i < batchCount; i++) {
const startIdx = i * batchSize;
const endIdx = Math.min(startIdx + batchSize, pixels.length);
const batchPixels = pixels.slice(startIdx, endIdx);
const batchData = [];
for (const pixel of batchPixels) {
const paintData = this.createPaintData(pixel);
batchData.push(paintData);
}
this.batchManager.batches.push(batchData);
}
console.log(`工作线程 ${this.workerId} 预处理完成,共 ${this.batchManager.batches.length} 个批次`);
return this.batchManager.batches;
}
appendData(data) {
// 检查添加数据后是否会超过32KB限制
if (this.totalSize + data.length > 30000) {
// 如果超过限制,先发送当前数据
this.sendCurrentChunks();
}
this.chunks.push(data);
this.totalSize += data.length;
}
// 检查是否被速率限制
isRateLimited() {
const now = Date.now();
const timeSinceSecondStart = now - this.rateLimiter.currentSecondStart;
// 如果已经过了1秒,重置计数器
if (timeSinceSecondStart >= 1000) {
this.rateLimiter.currentSecondStart = now;
this.rateLimiter.packetsSentInCurrentSecond = 0;
this.rateLimiter.isRateLimited = false;
return false;
}
// 只在真正达到限制时才限制
if (this.rateLimiter.packetsSentInCurrentSecond >= this.rateLimiter.maxPacketsPerSecond) {
this.rateLimiter.isRateLimited = true;
return true;
}
return false;
}
// 更新速率限制计数
updateRateLimit() {
const now = Date.now();
// 性能监控
this.performanceMonitor.packetsSinceLastLog++;
this.performanceMonitor.totalPacketsSent++;
// 每5秒输出一次实际发送速率
if (now - this.performanceMonitor.lastLogTime > 5000) {
const rate = (this.performanceMonitor.packetsSinceLastLog / 5).toFixed(1);
console.log(`工作线程 ${this.workerId} 实际发送速率: ${rate} 包/秒, 累计: ${this.performanceMonitor.totalPacketsSent}`);
this.performanceMonitor.lastLogTime = now;
this.performanceMonitor.packetsSinceLastLog = 0;
}
const timeSinceSecondStart = now - this.rateLimiter.currentSecondStart;
// 如果已经过了1秒,重置计数器
if (timeSinceSecondStart >= 1000) {
this.rateLimiter.currentSecondStart = now;
this.rateLimiter.packetsSentInCurrentSecond = 1;
this.rateLimiter.isRateLimited = false;
} else {
this.rateLimiter.packetsSentInCurrentSecond++;
// 只在真正达到限制时才标记
if (this.rateLimiter.packetsSentInCurrentSecond >= this.rateLimiter.maxPacketsPerSecond) {
this.rateLimiter.isRateLimited = true;
}
}
}
// 处理队列中的包
processQueuedPackets() {
while (this.rateLimiter.packetQueue.length > 0 && !this.isRateLimited()) {
const data = this.rateLimiter.packetQueue.shift();
this.chunks.push(data);
this.totalSize += data.length;
}
}
getMergedData() {
if (this.chunks.length === 0) return null;
const result = new Uint8Array(this.totalSize);
let offset = 0;
for (const chunk of this.chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
this.chunks = [];
this.totalSize = 0;
return result;
}
// 发送当前累积的数据块
sendCurrentChunks() {
if (this.chunks.length === 0) return;
// 严格控制数据包大小不超过30KB(留出安全余量)
const MAX_PAYLOAD_SIZE = 30000;
if (this.totalSize > MAX_PAYLOAD_SIZE) {
// 如果数据太大,分批发送
const batches = [];
let currentBatch = [];
let currentSize = 0;
for (const chunk of this.chunks) {
if (currentSize + chunk.length > MAX_PAYLOAD_SIZE && currentBatch.length > 0) {
batches.push(currentBatch);
currentBatch = [];
currentSize = 0;
}
currentBatch.push(chunk);
currentSize += chunk.length;
}
if (currentBatch.length > 0) {
batches.push(currentBatch);
}
// 发送所有批次
for (const batch of batches) {
const data = this.mergeChunks(batch);
if (data && data.length > 0) {
try {
this.ws.send(data);
console.log(`📤 工作线程 ${this.workerId} 发送 ${batch.length} 个数据包 (${data.length} 字节)`);
} catch (error) {
console.error(`发送失败: ${error.message}`);
}
}
}
this.chunks = [];
this.totalSize = 0;
} else {
// 正常发送
const data = this.getMergedData();
if (data && data.length > 0) {
try {
this.ws.send(data);
console.log(`📤 工作线程 ${this.workerId} 发送 ${this.chunks.length} 个数据包 (${data.length} 字节)`);
} catch (error) {
console.error(`发送失败: ${error.message}`);
}
}
}
}
mergeChunks(chunks) {
const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const result = new Uint8Array(totalSize);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
startSending() {
this.sendInterval = setInterval(() => {
this.processQueuedPackets();
// 检查数据大小,如果接近32KB就发送
if (this.totalSize >= 30000) { // 在接近32KB时提前发送
this.sendCurrentChunks();
}
// 常规发送逻辑
if (this.isConnected && this.chunks.length > 0) {
this.sendCurrentChunks();
}
}, this.config.sendInterval);
}
// 持续发送预处理的像素数据,支持多批次依次发送
startContinuousPainting(preprocessedBatches) {
if (!preprocessedBatches || preprocessedBatches.length === 0) return;
console.log(`🚀 工作线程 ${this.workerId} 启动: ${this.performanceStats.totalPixels}像素`);
this.batchManager.isProcessing = true;
const sendBatch = () => {
if (!this.isConnected || !this.batchManager.isProcessing) return;
const currentBatch = this.batchManager.batches[this.batchManager.currentBatchIndex];
if (!currentBatch || currentBatch.length === 0) {
this.switchToNextBatch();
return;
}
let sentCount = 0;
const maxToSend = Math.min(100, currentBatch.length); // 减少单次发送量
for (let i = 0; i < maxToSend; i++) {
if (this.isRateLimited()) {
break;
}
const pixelData = currentBatch[i];
const pixelKey = `${pixelData[1]},${pixelData[2]}`; // x,y 作为唯一标识
// 避免重复发送同一像素
if (!this.performanceStats.uniquePixelsSent.has(pixelKey)) {
this.appendData(pixelData);
this.performanceStats.uniquePixelsSent.add(pixelKey);
this.performanceStats.pixelsSent++;
sentCount++;
}
}
currentBatch.splice(0, maxToSend); // 移除已处理的像素
if (currentBatch.length === 0) {
this.switchToNextBatch();
}
// 实时进度显示
if (sentCount > 0) {
const progress = (this.performanceStats.pixelsSent / this.performanceStats.totalPixels * 100).toFixed(1);
console.log(`📦 工作线程 ${this.workerId}: ${this.performanceStats.pixelsSent}/${this.performanceStats.totalPixels} (${progress}%)`);
}
};
this.batchSwitchInterval = setInterval(() => {
if (!this.batchManager.isProcessing || !this.isConnected) {
if (this.batchSwitchInterval) {
clearInterval(this.batchSwitchInterval);
this.batchSwitchInterval = null;
}
return;
}
sendBatch();
this.processQueuedPackets();
}, 10); // 调整间隔
}
startPerformanceLogging() {
setInterval(() => {
const rate = (this.performanceMonitor.packetsSinceLastLog / 5).toFixed(1);
console.log(`📊 工作线程 ${this.workerId} 发送速率: ${rate} 包/秒, 累计: ${this.performanceMonitor.totalPacketsSent}`);
this.performanceMonitor.lastLogTime = Date.now();
this.performanceMonitor.packetsSinceLastLog = 0;
}, 5000);
}
// 切换到下一个批次
switchToNextBatch() {
if (this.batchManager.batches.length === 0) return;
const oldIndex = this.batchManager.currentBatchIndex;
this.batchManager.currentBatchIndex = (this.batchManager.currentBatchIndex + 1) % this.batchManager.batches.length;
this.performanceStats.batchesCompleted++;
// 只有当所有批次都完成时才记录
if (this.batchManager.currentBatchIndex === 0) {
const elapsed = (Date.now() - this.performanceStats.startTime) / 1000;
const pixelsPerSecond = (this.performanceStats.pixelsSent / elapsed).toFixed(1);
const progress = (this.performanceStats.pixelsSent / this.performanceStats.totalPixels * 100).toFixed(1);
console.log(`🔄 工作线程 ${this.workerId} 完成一轮: ${this.performanceStats.pixelsSent}/${this.performanceStats.totalPixels} (${progress}%) | ${pixelsPerSecond} 像素/秒`);
// 如果已经发送完所有像素,停止发送
if (this.performanceStats.pixelsSent >= this.performanceStats.totalPixels) {
console.log(`✅ 工作线程 ${this.workerId} 完成所有像素发送`);
this.batchManager.isProcessing = false;
if (this.batchSwitchInterval) {
clearInterval(this.batchSwitchInterval);
this.batchSwitchInterval = null;
}
}
}
}
// 处理修复任务
repairPixels(pixels) {
console.log(`工作线程 ${this.workerId} 修复 ${pixels.length} 个像素`);
for (const pixel of pixels) {
const paintData = this.createPaintData(pixel);
this.appendData(paintData);
this.performance.pixelsRepaired++;
parentPort.postMessage({ type: 'pixelRepaired' });
}
}
}
// 主入口
if (isMainThread) {
const controller = new PaintBoardController();
process.on('SIGINT', () => {
console.log('正在关闭...');
controller.shutdown();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('正在关闭...');
controller.shutdown();
process.exit(0);
});
controller.main().catch(console.error);
} else if (workerData.isRepairWorker) {
// 专门的修复工作线程
class RepairWorker {
constructor() {
this.workerId = workerData.workerId;
this.config = workerData.config;
this.ws = null;
this.isConnected = false;
this.pendingBatch = [];
this.sendInterval = null;
this.chunks = [];
this.totalSize = 0;
this.paintId = Math.floor(Math.random() * 40000);
this.pendingCallbacks = new Map();
this.performance = {
totalRepaired: 0,
batchesSent: 0,
failedRepairs: 0,
lastReportTime: Date.now()
};
// 修复线程的特殊设置
this.batchControl = {
maxBatchSize: 30000, // 更小的批次
maxChunkSize: 30000, // 32KB限制
minSendInterval: 10 // 10ms间隔
};
this.initialize();
}
async initialize() {
console.log('🔧 修复工作线程启动');
// 修复线程延迟启动,避免冲突
await new Promise(resolve => setTimeout(resolve, 5000));
await this.connectWebSocket();
this.startProcessing();
parentPort.postMessage({ type: 'ready', workerId: this.workerId });
}
async connectWebSocket() {
return new Promise((resolve, reject) => {
try {
console.log('🔧 修复工作线程正在连接...');
// 简化连接选项,避免兼容性问题
this.ws = new WebSocket(this.config.wsUrl);
this.ws.binaryType = "arraybuffer";
const connectionTimeout = setTimeout(() => {
console.log('🔧 修复工作线程连接超时');
this.ws.close();
reject(new Error('Connection timeout'));
}, 10000);
this.ws.onopen = () => {
clearTimeout(connectionTimeout);
console.log('🔧 修复工作线程WebSocket连接成功');
this.isConnected = true;
resolve(true);
};
this.ws.onmessage = (event) => {
this.handleMessage(event.data);
};
this.ws.onerror = (err) => {
clearTimeout(connectionTimeout);
console.error('🔧 修复工作线程连接错误:', err.message);
reject(err);
};
this.ws.onclose = (event) => {
clearTimeout(connectionTimeout);
this.isConnected = false;
console.log(`🔧 修复工作线程连接关闭 (${event.code}: ${event.reason || 'Unknown'})`);
// 使用指数退避重连
setTimeout(() => {
this.connectWebSocket().catch(console.error);
}, 5000);
};
} catch (error) {
console.error('🔧 修复工作线程连接异常:', error.message);
reject(error);
}
});
}
// 更安全的消息处理
handleMessage(buffer) {
try {
if (!buffer || buffer.byteLength === 0) {
return;
}
const dataView = new DataView(buffer);
let offset = 0;
while (offset < buffer.byteLength) {
// 确保有足够的数据读取类型
if (offset >= buffer.byteLength) break;
const type = dataView.getUint8(offset);
offset += 1;
switch (type) {
case 0xfc: // Ping
this.sendPong();
break;
case 0xff: // 绘画结果
if (offset + 5 <= buffer.byteLength) {
const id = dataView.getUint32(offset, true);
const code = dataView.getUint8(offset + 4);
offset += 5;
this.handlePaintResult(id, code);
} else {
// 数据不完整,直接返回
return;
}
break;
case 0xfa: // 像素更新消息 - 修复线程可以忽略
if (offset + 7 <= buffer.byteLength) {
offset += 7; // 跳过像素数据
} else {
return;
}
break;
default:
// 对于未知类型,跳过但不报错
break;
}
}
} catch (error) {
// 静默处理错误,不输出到控制台
console.error('🔧 处理消息时出错:', error.message);
}
}
sendPong() {
if (this.isConnected && this.ws && this.ws.readyState === WebSocket.OPEN) {
try {
this.ws.send(new Uint8Array([0xfb]));
} catch (error) {
console.error('🔧 发送Pong失败:', error.message);
}
}
}
handlePaintResult(id, code) {
const callback = this.pendingCallbacks.get(id);
if (callback) {
this.pendingCallbacks.delete(id);
callback(code);
}
if (code === 0xef) {
this.performance.totalRepaired++;
} else if (code !== 0xee) {
this.performance.failedRepairs++;
console.log(`🔧 修复失败: 状态码 0x${code.toString(16)}`);
}
}
// 更安全的 uint 转换
uintToUint8Array(uint, bytes) {
const array = new Uint8Array(bytes);
for (let i = 0; i < bytes; i++) {
array[i] = uint & 0xff;
uint = uint >>> 8; // 使用无符号右移
}
return array;
}
// 验证并创建绘图数据
createPaintData(pixel) {
try {
const id = (this.paintId++) % 4294967296;
const tokenBytes = new Uint8Array(16);
const hexToken = this.config.token.replace(/-/g, '');
// 严格验证 Token
if (!hexToken || hexToken.length !== 32) {
console.error('🔧 Token 格式错误');
return null;
}
// 严格验证 Token 内容
for (let j = 0; j < 32; j += 2) {
const byteStr = hexToken.substr(j, 2);
const byteVal = parseInt(byteStr, 16);
if (isNaN(byteVal)) {
console.error('🔧 Token 包含非法字符');
return null;
}
tokenBytes[j / 2] = byteVal;
}
// 验证坐标和颜色
if (pixel.x < 0 || pixel.x >= 1000 || pixel.y < 0 || pixel.y >= 600) {
return null;
}
if (pixel.r < 0 || pixel.r > 255 || pixel.g < 0 || pixel.g > 255 || pixel.b < 0 || pixel.b > 255) {
return null;
}
// 创建数据包
const paintData = new Uint8Array([
0xfe,
...this.uintToUint8Array(pixel.x, 2),
...this.uintToUint8Array(pixel.y, 2),
pixel.r, pixel.g, pixel.b,
...this.uintToUint8Array(this.config.uid, 3),
...tokenBytes,
...this.uintToUint8Array(id, 4)
]);
// 验证数据包大小
if (paintData.length !== 31) { // 固定应该是31字节
console.error(`🔧 数据包大小错误: ${paintData.length} 字节`);
return null;
}
this.pendingCallbacks.set(id, (code) => {
if (code === 0xee) {
// 冷却中,使用更长的延迟重试
setTimeout(() => {
const retryData = this.createPaintData(pixel);
if (retryData) {
this.appendData(retryData);
}
}, 2000);
}
});
return paintData;
} catch (error) {
console.error('🔧 创建绘图数据失败:', error.message);
return null;
}
}
// 严格控制数据添加,确保不超过32KB
appendData(data) {
if (!data || !(data instanceof Uint8Array)) {
return;
}
// 严格的大小控制
if (data.length > 1024) {
return;
}
// 检查添加后是否会超过32KB限制
if (this.totalSize + data.length > this.batchControl.maxChunkSize) {
// 如果超过限制,先发送当前数据
this.sendCurrentChunks();
}
if (this.chunks.length >= this.batchControl.maxBatchSize) {
return;
}
this.chunks.push(data);
this.totalSize += data.length;
}
getMergedData() {
if (this.chunks.length === 0) return null;
try {
const result = new Uint8Array(this.totalSize);
let offset = 0;
for (const chunk of this.chunks) {
// 确保每个块都是 Uint8Array
const safeChunk = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
result.set(safeChunk, offset);
offset += safeChunk.length;
}
console.log(`🔧 修复线程发送批次: ${this.chunks.length} 像素, ${this.totalSize} 字节`);
this.chunks = [];
this.totalSize = 0;
return result;
} catch (error) {
console.error('🔧 合并数据失败:', error.message);
this.chunks = [];
this.totalSize = 0;
return null;
}
}
// 发送当前累积的数据块
sendCurrentChunks() {
if (this.chunks.length === 0) return;
const data = this.getMergedData();
if (data && data.length > 0 && data.length <= this.batchControl.maxChunkSize) {
try {
this.ws.send(data);
this.performance.batchesSent++;
} catch (error) {
console.error('🔧 发送修复数据失败:', error.message);
// 发送失败时关闭连接重连
if (this.ws) {
this.ws.close();
}
}
}
}
startProcessing() {
// 修复线程使用更低的频率
this.sendInterval = setInterval(() => {
if (this.isConnected && this.ws && this.ws.readyState === this.ws.OPEN) {
// 检查数据大小,如果接近32KB就发送
if (this.totalSize >= 30000) { // 在接近32KB时提前发送
this.sendCurrentChunks();
}
// 常规发送逻辑
if (this.chunks.length > 0) {
this.sendCurrentChunks();
}
}
// 定期报告统计
// const now = Date.now();
// if (now - this.performance.lastReportTime > 4000) {
// parentPort.postMessage({
// type: 'repairStats',
// totalRepaired: this.performance.totalRepaired,
// batchesSent: this.performance.batchesSent,
// failedRepairs: this.performance.failedRepairs
// });
// this.performance.lastReportTime = now;
// }
}, this.batchControl.minSendInterval);
}
processRepairBatch(batch) {
if (!batch || batch.length === 0) return;
// 限制修复批次大小
const limitedBatch = batch;
console.log(`🔧 修复工作线程处理 ${limitedBatch.length} 个修复像素`);
let processed = 0;
for (const pixel of limitedBatch) {
const paintData = this.createPaintData(pixel);
if (paintData) {
this.appendData(paintData);
processed++;
}
}
parentPort.postMessage({
type: 'repairCompleted',
count: processed
});
}
}
const repairWorker = new RepairWorker();
parentPort.on('message', (message) => {
if (!message || !message.type) return;
switch (message.type) {
case 'repairBatch':
if (message.pixels && Array.isArray(message.pixels)) {
repairWorker.processRepairBatch(message.pixels);
}
break;
case 'shutdown':
console.log('🔧 修复工作线程收到关闭信号');
if (repairWorker.ws) {
repairWorker.ws.close();
}
if (repairWorker.sendInterval) {
clearInterval(repairWorker.sendInterval);
}
process.exit(0);
break;
}
});
} else {
const worker = new PaintBoardWorker();
parentPort.on('message', (message) => {
switch (message.type) {
case 'assignPixels':
const preprocessedData = worker.preprocessAllPixels(message.pixels);
worker.startContinuousPainting(preprocessedData);
break;
case 'repairPixels':
worker.repairPixels(message.pixels);
break;
case 'shutdown':
if (worker.ws) worker.ws.close();
if (worker.sendInterval) clearInterval(worker.sendInterval);
process.exit(0);
break;
}
});
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...