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

Last change on this file since 391 was 391, checked in by alloc, 2 years ago

Major refactoring/cleanup

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