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