public static byte[] GetRectangle(ChunkCache2D cache, short left, short top, short right, short bottom) { int rightExclusive = right + 1; int bottomExclusive = bottom + 1; int width = rightExclusive - left; int height = bottomExclusive - top; byte[] bytes = new byte[height * width]; unsafe { fixed(byte *byteArrPtr = &bytes[0]) { byte *byteArrStartPosition = byteArrPtr; Parallel.ForEach(cache.CachedChunks, chunkEntry => { fixed(byte *chunkPtr = &chunkEntry.Value[0, 0]) { (byte chunkX, byte chunkY) = chunkEntry.Key; short xChunkStart = PixelMap.RelativeToAbsolute(chunkX, 0); short yChunkStart = PixelMap.RelativeToAbsolute(chunkY, 0); short xChunkEndExclusive = PixelMap.RelativeToAbsolute(chunkX + 1, 0); short yChunkEndExclusive = PixelMap.RelativeToAbsolute(chunkY + 1, 0); short xBlockStart = Math.Max(xChunkStart, left); short yBlockStart = Math.Max(yChunkStart, top); int xBlockEndExclusive = Math.Min(xChunkEndExclusive, rightExclusive); int yBlockEndExclusive = Math.Min(yChunkEndExclusive, bottomExclusive); if (xBlockStart >= xBlockEndExclusive || yBlockStart >= yBlockEndExclusive) { return; } int xBlockStartChunkOffset = xBlockStart - xChunkStart; int yBlockStartChunkOffset = yBlockStart - yChunkStart; int yBlockEndExclusiveChunkOffset = yBlockEndExclusive - yChunkStart; int blockWidth = xBlockEndExclusive - xBlockStart; byte *outputCurrentPosition = byteArrStartPosition + xBlockStart - left + (yBlockStart - top) * width; byte *inputCurrentPosition = chunkPtr + xBlockStartChunkOffset + PixelMap.ChunkSize * yBlockStartChunkOffset; for (int chunkLine = yBlockStartChunkOffset; chunkLine < yBlockEndExclusiveChunkOffset; chunkLine++) { Buffer.MemoryCopy(inputCurrentPosition, outputCurrentPosition, blockWidth, blockWidth); inputCurrentPosition += PixelMap.ChunkSize; outputCurrentPosition += width; } } }); } } return(bytes); }
private static async Task Main(string[] args) { try { if (!ParseArguments(args)) { return; } logger = new Logger(options?.LogFilePath, finishCTS.Token) { ShowDebugLogs = options?.ShowDebugLogs ?? false }; logger.LogDebug("Command line: " + Environment.CommandLine); logger.LogTechState("Connecting to API..."); PixelPlanetHttpApi api = new PixelPlanetHttpApi { ProxySettings = proxySettings }; user = await api.GetMeAsync(); logger.LogTechInfo("Successfully connected"); CanvasModel canvas = user.Canvases[options.Canvas]; if (canvas.Is3D) { throw new Exception("3D canvas is not supported"); } LoggerExtensions.MaxCoordXYLength = 1 + (int)Math.Log10(canvas.Size / 2); PixelMap.MapSize = canvas.Size; try { if (options.LeftX < -(canvas.Size / 2) || options.RightX >= canvas.Size / 2) { throw new Exception("X"); } if (options.TopY < -(canvas.Size / 2) || options.BottomY >= canvas.Size / 2) { throw new Exception("Y"); } } catch (Exception ex) { throw new Exception($"Entire rectangle should be inside the map (failed by {ex.Message})"); } colorNameResolver = new ColorNameResolver(options.Canvas); if (checkUpdates || !options.DisableUpdates) { if (UpdateChecker.IsStartingUpdate(logger, checkUpdates) || checkUpdates) { return; } } cache = new ChunkCache2D(options.LeftX, options.TopY, options.RightX, options.BottomY, logger, options.Canvas); bool initialMapSavingStarted = false; saveThread = new Thread(SaveChangesThreadBody); saveThread.Start(); if (string.IsNullOrWhiteSpace(options.FileName)) { options.FileName = string.Format("pixels_({0};{1})-({2};{3})_{4:yyyy.MM.dd_HH-mm}.bin", options.LeftX, options.TopY, options.RightX, options.BottomY, DateTime.Now); } Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(options.FileName))); do { try { using (WebsocketWrapper wrapper = new WebsocketWrapper(logger, true, proxySettings, null, options.Canvas)) { cache.Wrapper = wrapper; if (!initialMapSavingStarted) { logger.LogDebug("Main(): initiating map saving"); initialMapSavingStarted = true; lockingStreamTask = Task.Run(SaveInitialMapState); } wrapper.OnMapChanged += Wrapper_OnMapChanged; stopListening = wrapper.StopListening; Console.CancelKeyPress += (o, e) => { logger.LogDebug("Console.CancelKeyPress received"); e.Cancel = true; wrapper.StopListening(); }; logger.LogInfo("Press Ctrl+C to stop"); wrapper.StartListening(); break; } } catch (Exception ex) { logger.LogError($"Unhandled exception: {ex.Message}"); Thread.Sleep(1000); } } while (true); } catch (Exception ex) { logger?.LogError($"Unhandled app level exception: {ex.Message}"); logger?.LogDebug(ex.ToString()); } finally { if (logger != null) { logger.LogInfo("Exiting when everything is saved..."); logger.LogInfo($"Logs were saved to {logger.LogFilePath}"); } finishCTS.Cancel(); if (logger != null) { Thread.Sleep(500); } finishCTS.Dispose(); logger?.Dispose(); Console.ForegroundColor = ConsoleColor.White; if (saveThread != null && !saveThread.Join(TimeSpan.FromMinutes(1))) { Console.WriteLine("Save thread doesn't finish, aborting"); Environment.Exit(0); } } }