专栏文章

冬日绘板脚本

算法·理论参与者 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 条评论,欢迎与作者交流。

正在加载评论...