source: binary-improvements/MapRendering/MapRendering/MapRendering.cs@ 325

Last change on this file since 325 was 325, checked in by alloc, 7 years ago

Code style cleanup (mostly whitespace changes, enforcing braces, using cleanup)

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