source: binary-improvements2/MapRendering/src/MapRenderer.cs@ 407

Last change on this file since 407 was 402, checked in by alloc, 22 months ago
  • Major refactoring
  • Using Utf8Json for (de)serialization
  • Moving APIs to REST
  • Removing dependencies from WebServer and MapRenderer to ServerFixes
File size: 12.5 KB
RevLine 
[224]1using System;
[325]2using System.Collections;
[224]3using System.Collections.Generic;
4using System.IO;
5using System.Text;
6using System.Threading;
7using UnityEngine;
[329]8using UnityEngine.Profiling;
[402]9using Utf8Json;
10using Webserver.FileCache;
[325]11using Object = UnityEngine.Object;
[224]12
[391]13namespace MapRendering {
14 public class MapRenderer {
15 private static MapRenderer instance;
[224]16
[325]17 private static readonly object lockObject = new object ();
[224]18 public static bool renderingEnabled = true;
[391]19 private readonly MapTileCache cache = new MapTileCache (Constants.MapBlockSize);
[325]20 private readonly Dictionary<Vector2i, Color32[]> dirtyChunks = new Dictionary<Vector2i, Color32[]> ();
21 private readonly MicroStopwatch msw = new MicroStopwatch ();
22 private readonly MapRenderBlockBuffer[] zoomLevelBuffers;
23 private Coroutine renderCoroutineRef;
24 private bool renderingFullMap;
[299]25 private float renderTimeout = float.MaxValue;
[383]26 private bool shutdown;
[224]27
[391]28 private MapRenderer () {
[402]29 Constants.MapDirectory = $"{GameIO.GetSaveGameDir ()}/map";
[224]30
[402]31 if (!LoadMapInfo ()) {
32 WriteMapInfo ();
[224]33 }
34
[391]35 cache.SetZoomCount (Constants.Zoomlevels);
[224]36
[391]37 zoomLevelBuffers = new MapRenderBlockBuffer[Constants.Zoomlevels];
38 for (int i = 0; i < Constants.Zoomlevels; i++) {
[224]39 zoomLevelBuffers [i] = new MapRenderBlockBuffer (i, cache);
40 }
41
[299]42 renderCoroutineRef = ThreadManager.StartCoroutine (renderCoroutine ());
[224]43 }
44
[391]45 public static MapRenderer Instance => instance ??= new MapRenderer ();
[325]46
[402]47 public static AbstractCache GetTileCache () {
[325]48 return Instance.cache;
49 }
50
51 public static void Shutdown () {
[383]52 if (Instance == null) {
53 return;
54 }
55
56 Instance.shutdown = true;
57
58 if (Instance.renderCoroutineRef != null) {
[299]59 ThreadManager.StopCoroutine (Instance.renderCoroutineRef);
60 Instance.renderCoroutineRef = null;
61 }
62 }
63
[351]64 public static void RenderSingleChunk (Chunk _chunk) {
[369]65 if (renderingEnabled && Instance != null) {
[329]66 // TODO: Replace with regular thread and a blocking queue / set
[351]67 ThreadPool.UnsafeQueueUserWorkItem (_o => {
[224]68 try {
[391]69 if (Instance.renderingFullMap) {
70 return;
71 }
[224]72
[391]73 lock (lockObject) {
74 Chunk c = (Chunk) _o;
75 Vector3i cPos = c.GetWorldPos ();
76 Vector2i cPos2 = new Vector2i (cPos.x / Constants.MapChunkSize,
77 cPos.z / Constants.MapChunkSize);
[325]78
[391]79 ushort[] mapColors = c.GetMapColors ();
80 if (mapColors == null) {
81 return;
82 }
[325]83
[391]84 Color32[] realColors =
85 new Color32[Constants.MapChunkSize * Constants.MapChunkSize];
86 for (int iColors = 0; iColors < mapColors.Length; iColors++) {
87 realColors [iColors] = shortColorToColor32 (mapColors [iColors]);
[224]88 }
[391]89
90 Instance.dirtyChunks [cPos2] = realColors;
91
92 //Log.Out ("Add Dirty: " + cPos2);
[224]93 }
94 } catch (Exception e) {
[402]95 Log.Out ($"Exception in MapRendering.RenderSingleChunk(): {e}");
[224]96 }
[351]97 }, _chunk);
[224]98 }
99 }
100
[325]101 public void RenderFullMap () {
[224]102 MicroStopwatch microStopwatch = new MicroStopwatch ();
103
[369]104 string regionSaveDir = GameIO.GetSaveGameRegionDir ();
[224]105 RegionFileManager rfm = new RegionFileManager (regionSaveDir, regionSaveDir, 0, false);
106 Texture2D fullMapTexture = null;
107
[391]108 getWorldExtent (rfm, out Vector2i minChunk, out Vector2i maxChunk, out Vector2i minPos, out Vector2i maxPos, out int widthChunks, out int heightChunks,
109 out int widthPix, out int heightPix);
[224]110
[391]111 Log.Out (
112 $"RenderMap: min: {minChunk.ToString ()}, max: {maxChunk.ToString ()}, minPos: {minPos.ToString ()}, maxPos: {maxPos.ToString ()}, w/h: {widthChunks}/{heightChunks}, wP/hP: {widthPix}/{heightPix}"
[224]113 );
114
115 lock (lockObject) {
[391]116 for (int i = 0; i < Constants.Zoomlevels; i++) {
[224]117 zoomLevelBuffers [i].ResetBlock ();
118 }
119
[391]120 if (Directory.Exists (Constants.MapDirectory)) {
121 Directory.Delete (Constants.MapDirectory, true);
[224]122 }
[325]123
[224]124 WriteMapInfo ();
125
126 renderingFullMap = true;
127
[325]128 if (widthPix <= 8192 && heightPix <= 8192) {
[224]129 fullMapTexture = new Texture2D (widthPix, heightPix);
[325]130 }
[224]131
[391]132 Vector2i curFullMapPos = default;
133 Vector2i curChunkPos = default;
134 for (curFullMapPos.x = 0; curFullMapPos.x < widthPix; curFullMapPos.x += Constants.MapChunkSize) {
[325]135 for (curFullMapPos.y = 0;
136 curFullMapPos.y < heightPix;
[391]137 curFullMapPos.y += Constants.MapChunkSize) {
138 curChunkPos.x = curFullMapPos.x / Constants.MapChunkSize + minChunk.x;
139 curChunkPos.y = curFullMapPos.y / Constants.MapChunkSize + minChunk.y;
[224]140
141 try {
142 long chunkKey = WorldChunkCache.MakeChunkKey (curChunkPos.x, curChunkPos.y);
143 if (rfm.ContainsChunkSync (chunkKey)) {
144 Chunk c = rfm.GetChunkSync (chunkKey);
145 ushort[] mapColors = c.GetMapColors ();
146 if (mapColors != null) {
[325]147 Color32[] realColors =
[391]148 new Color32[Constants.MapChunkSize * Constants.MapChunkSize];
149 for (int iColors = 0; iColors < mapColors.Length; iColors++) {
150 realColors [iColors] = shortColorToColor32 (mapColors [iColors]);
[224]151 }
[325]152
[224]153 dirtyChunks [curChunkPos] = realColors;
[325]154 if (fullMapTexture != null) {
155 fullMapTexture.SetPixels32 (curFullMapPos.x, curFullMapPos.y,
[391]156 Constants.MapChunkSize, Constants.MapChunkSize, realColors);
[325]157 }
[224]158 }
159 }
160 } catch (Exception e) {
[402]161 Log.Out ($"Exception: {e}");
[224]162 }
163 }
164
165 while (dirtyChunks.Count > 0) {
166 RenderDirtyChunks ();
167 }
168
[391]169 Log.Out ($"RenderMap: {curFullMapPos.x}/{widthPix} ({(int)((float)curFullMapPos.x / widthPix * 100)}%)");
[224]170 }
171 }
[346]172
173 rfm.Cleanup ();
[224]174
175 if (fullMapTexture != null) {
176 byte[] array = fullMapTexture.EncodeToPNG ();
[402]177 File.WriteAllBytes ($"{Constants.MapDirectory}/map.png", array);
[325]178 Object.Destroy (fullMapTexture);
[224]179 }
180
181 renderingFullMap = false;
182
[402]183 Log.Out ($"Generating map took: {microStopwatch.ElapsedMilliseconds} ms");
184 Log.Out ($"World extent: {minPos} - {maxPos}");
[224]185 }
186
[326]187 private void SaveAllBlockMaps () {
[391]188 for (int i = 0; i < Constants.Zoomlevels; i++) {
[224]189 zoomLevelBuffers [i].SaveBlock ();
190 }
191 }
[329]192
193 private readonly WaitForSeconds coroutineDelay = new WaitForSeconds (0.2f);
[224]194
[325]195 private IEnumerator renderCoroutine () {
[383]196 while (!shutdown) {
[299]197 lock (lockObject) {
[383]198 if (dirtyChunks.Count > 0 && renderTimeout >= float.MaxValue / 2) {
[299]199 renderTimeout = Time.time + 0.5f;
200 }
[325]201
[299]202 if (Time.time > renderTimeout || dirtyChunks.Count > 200) {
[329]203 Profiler.BeginSample ("RenderDirtyChunks");
[299]204 RenderDirtyChunks ();
[329]205 Profiler.EndSample ();
[299]206 }
207 }
[325]208
[329]209 yield return coroutineDelay;
[224]210 }
211 }
212
[329]213 private readonly List<Vector2i> chunksToRender = new List<Vector2i> ();
214 private readonly List<Vector2i> chunksRendered = new List<Vector2i> ();
215
[325]216 private void RenderDirtyChunks () {
[224]217 msw.ResetAndRestart ();
218
[326]219 if (dirtyChunks.Count <= 0) {
220 return;
221 }
[224]222
[329]223 Profiler.BeginSample ("RenderDirtyChunks.Prepare");
224 chunksToRender.Clear ();
225 chunksRendered.Clear ();
[224]226
[329]227 dirtyChunks.CopyKeysTo (chunksToRender);
[224]228
[329]229 Vector2i chunkPos = chunksToRender [0];
230 chunksRendered.Add (chunkPos);
231
[326]232 //Log.Out ("Start Dirty: " + chunkPos);
[224]233
[391]234 getBlockNumber (chunkPos, out Vector2i block, out _, Constants.MAP_BLOCK_TO_CHUNK_DIV, Constants.MapChunkSize);
[224]235
[391]236 zoomLevelBuffers [Constants.Zoomlevels - 1].LoadBlock (block);
[329]237 Profiler.EndSample ();
[325]238
[329]239 Profiler.BeginSample ("RenderDirtyChunks.Work");
[331]240 // Write all chunks that are in the same image tile of the highest zoom level
[329]241 foreach (Vector2i v in chunksToRender) {
[391]242 getBlockNumber (v, out Vector2i vBlock, out Vector2i vBlockOffset, Constants.MAP_BLOCK_TO_CHUNK_DIV,
243 Constants.MapChunkSize);
244 if (!vBlock.Equals (block)) {
245 continue;
246 }
[224]247
[391]248 //Log.Out ("Dirty: " + v + " render: true");
249 chunksRendered.Add (v);
250 if (dirtyChunks [v].Length != Constants.MapChunkSize * Constants.MapChunkSize) {
251 Log.Error (
252 $"Rendering chunk has incorrect data size of {dirtyChunks [v].Length} instead of {Constants.MapChunkSize * Constants.MapChunkSize}");
[325]253 }
[391]254
255 zoomLevelBuffers [Constants.Zoomlevels - 1]
256 .SetPart (vBlockOffset, Constants.MapChunkSize, dirtyChunks [v]);
[326]257 }
[329]258 Profiler.EndSample ();
[224]259
[329]260 foreach (Vector2i v in chunksRendered) {
[326]261 dirtyChunks.Remove (v);
262 }
[224]263
[331]264 // Update lower zoom levels affected by the change of the highest one
[326]265 RenderZoomLevel (block);
266
[329]267 Profiler.BeginSample ("RenderDirtyChunks.SaveAll");
[326]268 SaveAllBlockMaps ();
[329]269 Profiler.EndSample ();
[224]270 }
271
[351]272 private void RenderZoomLevel (Vector2i _innerBlock) {
[329]273 Profiler.BeginSample ("RenderZoomLevel");
[391]274 int level = Constants.Zoomlevels - 1;
[326]275 while (level > 0) {
[391]276 getBlockNumber (_innerBlock, out Vector2i block, out Vector2i blockOffset, 2, Constants.MapBlockSize / 2);
[224]277
278 zoomLevelBuffers [level - 1].LoadBlock (block);
279
[329]280 Profiler.BeginSample ("RenderZoomLevel.Transfer");
[331]281 if ((zoomLevelBuffers [level].FormatSelf == TextureFormat.ARGB32 ||
282 zoomLevelBuffers [level].FormatSelf == TextureFormat.RGBA32) &&
283 zoomLevelBuffers [level].FormatSelf == zoomLevelBuffers [level - 1].FormatSelf) {
[391]284 zoomLevelBuffers [level - 1].SetPartNative (blockOffset, Constants.MapBlockSize / 2, zoomLevelBuffers [level].GetHalfScaledNative ());
[329]285 } else {
[391]286 zoomLevelBuffers [level - 1].SetPart (blockOffset, Constants.MapBlockSize / 2, zoomLevelBuffers [level].GetHalfScaled ());
[329]287 }
288 Profiler.EndSample ();
289
290 level--;
[351]291 _innerBlock = block;
[224]292 }
[329]293 Profiler.EndSample ();
[224]294 }
295
[351]296 private void getBlockNumber (Vector2i _innerPos, out Vector2i _block, out Vector2i _blockOffset, int _scaleFactor,
297 int _offsetSize) {
[391]298 _block = default;
299 _blockOffset = default;
[351]300 _block.x = (_innerPos.x + 16777216) / _scaleFactor - 16777216 / _scaleFactor;
301 _block.y = (_innerPos.y + 16777216) / _scaleFactor - 16777216 / _scaleFactor;
302 _blockOffset.x = (_innerPos.x + 16777216) % _scaleFactor * _offsetSize;
303 _blockOffset.y = (_innerPos.y + 16777216) % _scaleFactor * _offsetSize;
[224]304 }
305
[325]306 private void WriteMapInfo () {
[402]307 JsonWriter writer = new JsonWriter ();
308 writer.WriteBeginObject ();
309
310 writer.WriteString ("blockSize");
311 writer.WriteNameSeparator ();
312 writer.WriteInt32 (Constants.MapBlockSize);
313
314 writer.WriteValueSeparator ();
315 writer.WriteString ("maxZoom");
316 writer.WriteNameSeparator ();
317 writer.WriteInt32 (Constants.Zoomlevels - 1);
318
319 writer.WriteEndObject ();
[224]320
[391]321 Directory.CreateDirectory (Constants.MapDirectory);
[402]322 File.WriteAllBytes ($"{Constants.MapDirectory}/mapinfo.json", writer.ToUtf8ByteArray ());
[224]323 }
324
[325]325 private bool LoadMapInfo () {
[402]326 if (!File.Exists ($"{Constants.MapDirectory}/mapinfo.json")) {
[326]327 return false;
328 }
[325]329
[402]330 string json = File.ReadAllText ($"{Constants.MapDirectory}/mapinfo.json", Encoding.UTF8);
[326]331 try {
[402]332 IDictionary<string,object> inputJson = JsonSerializer.Deserialize<IDictionary<string, object>> (json);
[325]333
[402]334 if (inputJson.TryGetValue ("blockSize", out object fieldNode) && fieldNode is double value) {
335 Constants.MapBlockSize = (int)value;
336 }
[326]337
[402]338 if (inputJson.TryGetValue ("maxZoom", out fieldNode) && fieldNode is double value2) {
339 Constants.Zoomlevels = (int)value2 + 1;
[224]340 }
[402]341
342 return true;
343 } catch (Exception e) {
344 Log.Out ($"Exception in LoadMapInfo: {e}");
[224]345 }
[325]346
[224]347 return false;
348 }
349
[351]350 private void getWorldExtent (RegionFileManager _rfm, out Vector2i _minChunk, out Vector2i _maxChunk,
351 out Vector2i _minPos, out Vector2i _maxPos,
352 out int _widthChunks, out int _heightChunks,
353 out int _widthPix, out int _heightPix) {
[391]354 _minChunk = default;
355 _maxChunk = default;
356 _minPos = default;
357 _maxPos = default;
[224]358
[351]359 long[] keys = _rfm.GetAllChunkKeys ();
[325]360 int minX = int.MaxValue;
361 int minY = int.MaxValue;
362 int maxX = int.MinValue;
363 int maxY = int.MinValue;
[224]364 foreach (long key in keys) {
365 int x = WorldChunkCache.extractX (key);
366 int y = WorldChunkCache.extractZ (key);
367
[325]368 if (x < minX) {
[224]369 minX = x;
[325]370 }
371
372 if (x > maxX) {
[224]373 maxX = x;
[325]374 }
375
376 if (y < minY) {
[224]377 minY = y;
[325]378 }
379
380 if (y > maxY) {
[224]381 maxY = y;
[325]382 }
[224]383 }
384
[351]385 _minChunk.x = minX;
386 _minChunk.y = minY;
[224]387
[351]388 _maxChunk.x = maxX;
389 _maxChunk.y = maxY;
[224]390
[391]391 _minPos.x = minX * Constants.MapChunkSize;
392 _minPos.y = minY * Constants.MapChunkSize;
[224]393
[391]394 _maxPos.x = maxX * Constants.MapChunkSize;
395 _maxPos.y = maxY * Constants.MapChunkSize;
[224]396
[351]397 _widthChunks = maxX - minX + 1;
398 _heightChunks = maxY - minY + 1;
[224]399
[391]400 _widthPix = _widthChunks * Constants.MapChunkSize;
401 _heightPix = _heightChunks * Constants.MapChunkSize;
[224]402 }
403
[351]404 private static Color32 shortColorToColor32 (ushort _col) {
405 byte r = (byte) (256 * ((_col >> 10) & 31) / 32);
406 byte g = (byte) (256 * ((_col >> 5) & 31) / 32);
407 byte b = (byte) (256 * (_col & 31) / 32);
408 const byte a = 255;
[238]409 return new Color32 (r, g, b, a);
410 }
[224]411 }
412}
Note: See TracBrowser for help on using the repository browser.