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

Last change on this file since 423 was 423, checked in by alloc, 20 months ago

Fixed: MapRenderer always enabled

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