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

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