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
Line 
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.IO;
5using System.Text;
6using System.Threading;
7using UnityEngine;
8using UnityEngine.Profiling;
9using Utf8Json;
10using Webserver.FileCache;
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 if (!LoadMapInfo ()) {
32 WriteMapInfo ();
33 }
34
35 cache.SetZoomCount (Constants.Zoomlevels);
36
37 zoomLevelBuffers = new MapRenderBlockBuffer[Constants.Zoomlevels];
38 for (int i = 0; i < Constants.Zoomlevels; i++) {
39 zoomLevelBuffers [i] = new MapRenderBlockBuffer (i, cache);
40 }
41
42 renderCoroutineRef = ThreadManager.StartCoroutine (renderCoroutine ());
43 }
44
45 public static bool HasInstance => instance != null;
46
47 public static MapRenderer Instance => instance ??= new MapRenderer ();
48
49 public static AbstractCache 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 instance = null;
66 }
67
68 public static void RenderSingleChunk (Chunk _chunk) {
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) {
88 return;
89 }
90
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 }
96
97 instance.dirtyChunks [cPos2] = realColors;
98
99 //Log.Out ("Add Dirty: " + cPos2);
100 }
101 } catch (Exception e) {
102 Log.Out ($"Exception in MapRendering.RenderSingleChunk(): {e}");
103 }
104 }, _chunk);
105 }
106
107 public void RenderFullMap () {
108 MicroStopwatch microStopwatch = new MicroStopwatch ();
109
110 string regionSaveDir = GameIO.GetSaveGameRegionDir ();
111 RegionFileManager rfm = new RegionFileManager (regionSaveDir, regionSaveDir, 0, false);
112 Texture2D fullMapTexture = null;
113
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);
116
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}"
119 );
120
121 lock (lockObject) {
122 for (int i = 0; i < Constants.Zoomlevels; i++) {
123 zoomLevelBuffers [i].ResetBlock ();
124 }
125
126 if (Directory.Exists (Constants.MapDirectory)) {
127 Directory.Delete (Constants.MapDirectory, true);
128 }
129
130 WriteMapInfo ();
131
132 renderingFullMap = true;
133
134 if (widthPix <= 8192 && heightPix <= 8192) {
135 fullMapTexture = new Texture2D (widthPix, heightPix);
136 }
137
138 Vector2i curFullMapPos = default;
139 Vector2i curChunkPos = default;
140 for (curFullMapPos.x = 0; curFullMapPos.x < widthPix; curFullMapPos.x += Constants.MapChunkSize) {
141 for (curFullMapPos.y = 0;
142 curFullMapPos.y < heightPix;
143 curFullMapPos.y += Constants.MapChunkSize) {
144 curChunkPos.x = curFullMapPos.x / Constants.MapChunkSize + minChunk.x;
145 curChunkPos.y = curFullMapPos.y / Constants.MapChunkSize + minChunk.y;
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) {
153 Color32[] realColors =
154 new Color32[Constants.MapChunkSize * Constants.MapChunkSize];
155 for (int iColors = 0; iColors < mapColors.Length; iColors++) {
156 realColors [iColors] = shortColorToColor32 (mapColors [iColors]);
157 }
158
159 dirtyChunks [curChunkPos] = realColors;
160 if (fullMapTexture != null) {
161 fullMapTexture.SetPixels32 (curFullMapPos.x, curFullMapPos.y,
162 Constants.MapChunkSize, Constants.MapChunkSize, realColors);
163 }
164 }
165 }
166 } catch (Exception e) {
167 Log.Out ($"Exception: {e}");
168 }
169 }
170
171 while (dirtyChunks.Count > 0) {
172 RenderDirtyChunks ();
173 }
174
175 Log.Out ($"RenderMap: {curFullMapPos.x}/{widthPix} ({(int)((float)curFullMapPos.x / widthPix * 100)}%)");
176 }
177 }
178
179 rfm.Cleanup ();
180
181 if (fullMapTexture != null) {
182 byte[] array = fullMapTexture.EncodeToPNG ();
183 File.WriteAllBytes ($"{Constants.MapDirectory}/map.png", array);
184 Object.Destroy (fullMapTexture);
185 }
186
187 renderingFullMap = false;
188
189 Log.Out ($"Generating map took: {microStopwatch.ElapsedMilliseconds} ms");
190 Log.Out ($"World extent: {minPos} - {maxPos}");
191 }
192
193 private void SaveAllBlockMaps () {
194 for (int i = 0; i < Constants.Zoomlevels; i++) {
195 zoomLevelBuffers [i].SaveBlock ();
196 }
197 }
198
199 private readonly WaitForSeconds coroutineDelay = new WaitForSeconds (0.2f);
200
201 private IEnumerator renderCoroutine () {
202 while (!shutdown) {
203 lock (lockObject) {
204 if (dirtyChunks.Count > 0 && renderTimeout >= float.MaxValue / 2) {
205 renderTimeout = Time.time + 0.5f;
206 }
207
208 if (Time.time > renderTimeout || dirtyChunks.Count > 200) {
209 Profiler.BeginSample ("RenderDirtyChunks");
210 RenderDirtyChunks ();
211 Profiler.EndSample ();
212 }
213 }
214
215 yield return coroutineDelay;
216 }
217 }
218
219 private readonly List<Vector2i> chunksToRender = new List<Vector2i> ();
220 private readonly List<Vector2i> chunksRendered = new List<Vector2i> ();
221
222 private void RenderDirtyChunks () {
223 msw.ResetAndRestart ();
224
225 if (dirtyChunks.Count <= 0) {
226 return;
227 }
228
229 Profiler.BeginSample ("RenderDirtyChunks.Prepare");
230 chunksToRender.Clear ();
231 chunksRendered.Clear ();
232
233 dirtyChunks.CopyKeysTo (chunksToRender);
234
235 Vector2i chunkPos = chunksToRender [0];
236 chunksRendered.Add (chunkPos);
237
238 //Log.Out ("Start Dirty: " + chunkPos);
239
240 getBlockNumber (chunkPos, out Vector2i block, out _, Constants.MAP_BLOCK_TO_CHUNK_DIV, Constants.MapChunkSize);
241
242 zoomLevelBuffers [Constants.Zoomlevels - 1].LoadBlock (block);
243 Profiler.EndSample ();
244
245 Profiler.BeginSample ("RenderDirtyChunks.Work");
246 // Write all chunks that are in the same image tile of the highest zoom level
247 foreach (Vector2i v in chunksToRender) {
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 }
253
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}");
259 }
260
261 zoomLevelBuffers [Constants.Zoomlevels - 1]
262 .SetPart (vBlockOffset, Constants.MapChunkSize, dirtyChunks [v]);
263 }
264 Profiler.EndSample ();
265
266 foreach (Vector2i v in chunksRendered) {
267 dirtyChunks.Remove (v);
268 }
269
270 // Update lower zoom levels affected by the change of the highest one
271 RenderZoomLevel (block);
272
273 Profiler.BeginSample ("RenderDirtyChunks.SaveAll");
274 SaveAllBlockMaps ();
275 Profiler.EndSample ();
276 }
277
278 private void RenderZoomLevel (Vector2i _innerBlock) {
279 Profiler.BeginSample ("RenderZoomLevel");
280 int level = Constants.Zoomlevels - 1;
281 while (level > 0) {
282 getBlockNumber (_innerBlock, out Vector2i block, out Vector2i blockOffset, 2, Constants.MapBlockSize / 2);
283
284 zoomLevelBuffers [level - 1].LoadBlock (block);
285
286 Profiler.BeginSample ("RenderZoomLevel.Transfer");
287 if ((zoomLevelBuffers [level].FormatSelf == TextureFormat.ARGB32 ||
288 zoomLevelBuffers [level].FormatSelf == TextureFormat.RGBA32) &&
289 zoomLevelBuffers [level].FormatSelf == zoomLevelBuffers [level - 1].FormatSelf) {
290 zoomLevelBuffers [level - 1].SetPartNative (blockOffset, Constants.MapBlockSize / 2, zoomLevelBuffers [level].GetHalfScaledNative ());
291 } else {
292 zoomLevelBuffers [level - 1].SetPart (blockOffset, Constants.MapBlockSize / 2, zoomLevelBuffers [level].GetHalfScaled ());
293 }
294 Profiler.EndSample ();
295
296 level--;
297 _innerBlock = block;
298 }
299 Profiler.EndSample ();
300 }
301
302 private void getBlockNumber (Vector2i _innerPos, out Vector2i _block, out Vector2i _blockOffset, int _scaleFactor,
303 int _offsetSize) {
304 _block = default;
305 _blockOffset = default;
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;
310 }
311
312 private void WriteMapInfo () {
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 ();
326
327 Directory.CreateDirectory (Constants.MapDirectory);
328 File.WriteAllBytes ($"{Constants.MapDirectory}/mapinfo.json", writer.ToUtf8ByteArray ());
329 }
330
331 private bool LoadMapInfo () {
332 if (!File.Exists ($"{Constants.MapDirectory}/mapinfo.json")) {
333 return false;
334 }
335
336 string json = File.ReadAllText ($"{Constants.MapDirectory}/mapinfo.json", Encoding.UTF8);
337 try {
338 IDictionary<string,object> inputJson = JsonSerializer.Deserialize<IDictionary<string, object>> (json);
339
340 if (inputJson.TryGetValue ("blockSize", out object fieldNode) && fieldNode is double value) {
341 Constants.MapBlockSize = (int)value;
342 }
343
344 if (inputJson.TryGetValue ("maxZoom", out fieldNode) && fieldNode is double value2) {
345 Constants.Zoomlevels = (int)value2 + 1;
346 }
347
348 return true;
349 } catch (Exception e) {
350 Log.Out ($"Exception in LoadMapInfo: {e}");
351 }
352
353 return false;
354 }
355
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) {
360 _minChunk = default;
361 _maxChunk = default;
362 _minPos = default;
363 _maxPos = default;
364
365 long[] keys = _rfm.GetAllChunkKeys ();
366 int minX = int.MaxValue;
367 int minY = int.MaxValue;
368 int maxX = int.MinValue;
369 int maxY = int.MinValue;
370 foreach (long key in keys) {
371 int x = WorldChunkCache.extractX (key);
372 int y = WorldChunkCache.extractZ (key);
373
374 if (x < minX) {
375 minX = x;
376 }
377
378 if (x > maxX) {
379 maxX = x;
380 }
381
382 if (y < minY) {
383 minY = y;
384 }
385
386 if (y > maxY) {
387 maxY = y;
388 }
389 }
390
391 _minChunk.x = minX;
392 _minChunk.y = minY;
393
394 _maxChunk.x = maxX;
395 _maxChunk.y = maxY;
396
397 _minPos.x = minX * Constants.MapChunkSize;
398 _minPos.y = minY * Constants.MapChunkSize;
399
400 _maxPos.x = maxX * Constants.MapChunkSize;
401 _maxPos.y = maxY * Constants.MapChunkSize;
402
403 _widthChunks = maxX - minX + 1;
404 _heightChunks = maxY - minY + 1;
405
406 _widthPix = _widthChunks * Constants.MapChunkSize;
407 _heightPix = _heightChunks * Constants.MapChunkSize;
408 }
409
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;
415 return new Color32 (r, g, b, a);
416 }
417 }
418}
Note: See TracBrowser for help on using the repository browser.