OSDN Git Service

Pre-RC0: Pass 2; JSON Field metadata dynamic generation.
[automap/automap.git] / Automap / Subsystems / AutomapSystem.cs
index 7880ac2..8cbfc20 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Threading;
@@ -10,6 +11,10 @@ using System.Threading;
 using Hjg.Pngcs;
 using Hjg.Pngcs.Chunks;
 
+using Newtonsoft.Json;
+
+using ProtoBuf;
+
 using Vintagestory.API.Client;
 using Vintagestory.API.Common;
 using Vintagestory.API.Config;
@@ -25,23 +30,28 @@ namespace Automap
                private ICoreClientAPI ClientAPI { get; set; }
                private ILogger Logger { get; set; }
                private IChunkRenderer ChunkRenderer { get; set; }
+               private JsonGenerator JsonGenerator { get; set; }
 
                private const string _mapPath = @"Maps";
                private const string _chunkPath = @"Chunks";
                private const string _domain = @"automap";
                private const string chunkFile_filter = @"*_*.png";
+               private const string poiFileName = @"poi_binary";
+               private const string eoiFileName = @"eoi_binary";
+               private const string pointsTsvFileName = @"points_of_interest.tsv";
                private static Regex chunkShardRegex = new Regex(@"(?<X>[\d]+)_(?<Z>[\d]+).png", RegexOptions.Singleline);
 
                private ConcurrentDictionary<Vec2i, uint> columnCounter = new ConcurrentDictionary<Vec2i, uint>(3, 150);
                private ColumnsMetadata chunkTopMetadata;
                private PointsOfInterest POIs = new PointsOfInterest();
                private EntitiesOfInterest EOIs = new EntitiesOfInterest();
+               private string jsonPreBuilt;
 
                internal Dictionary<int, BlockDesignator> BlockID_Designators { get; private set; }
                internal Dictionary<AssetLocation, EntityDesignator> Entity_Designators { get; private set; }
                internal Dictionary<int, string> RockIdCodes { get; private set; }
 
-               internal RunState CurrentState { get; set; }
+               internal CommandType CurrentState { get; set; }
                //Run status, Chunks processed, stats, center of map....
                private uint nullChunkCount, updatedChunksTotal;
                private Vec2i startChunkColumn;
@@ -49,17 +59,21 @@ namespace Automap
                private readonly int chunkSize;
                private string path;
                private IAsset staticMap;
+               private PersistedConfiguration configuration;
+
 
                public static string AutomapStatusEventKey = @"AutomapStatus";
                public static string AutomapCommandEventKey = @"AutomapCommand";
+               PersistedConfiguration cachedConfiguration;
 
-
-               public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger)
+               public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger, PersistedConfiguration config)
                {
                        this.ClientAPI = clientAPI;
                        this.Logger = logger;
                        chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
                        ClientAPI.Event.LevelFinalize += EngageAutomap;
+                       configuration = config;
+
 
                        //TODO:Choose which one from GUI 
                        this.ChunkRenderer = new StandardRenderer(clientAPI, logger);
@@ -67,6 +81,12 @@ namespace Automap
                        //Listen on bus for commands
                        ClientAPI.Event.RegisterEventBusListener(CommandListener, 1.0, AutomapSystem.AutomapCommandEventKey);
 
+                       if (configuration.Autostart)
+                       {
+                               CurrentState = CommandType.Run;
+                               Logger.Debug("Autostart is Enabled.");
+                       }
+
                }
 
 
@@ -75,7 +95,7 @@ namespace Automap
                {
                        path = ClientAPI.GetOrCreateDataPath(_mapPath);
                        path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName'
-
+                       JsonGenerator = new JsonGenerator(ClientAPI, Logger, path);
 
                        string mapFilename = Path.Combine(path, "automap.html");
                        StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite));
@@ -113,11 +133,11 @@ namespace Automap
                private void AwakenCartographer(float delayed)
                {
 
-                       if (CurrentState == RunState.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true))
+                       if (CurrentState == CommandType.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true))
                        {
-#if DEBUG
+                               #if DEBUG
                                Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
-#endif
+                               #endif
 
                                if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted))
                                {
@@ -132,7 +152,7 @@ namespace Automap
                                //ClientAPI.TriggerChatMessage($"Automap {updatedChunksTotal} Updates - MAX (N:{chunkTopMetadata.North_mostChunk},S:{chunkTopMetadata.South_mostChunk},E:{chunkTopMetadata.East_mostChunk}, W:{chunkTopMetadata.West_mostChunk} - TOTAL: {chunkTopMetadata.Count})");
                                //#endif
                        }
-                       else if (CurrentState == RunState.Snapshot)
+                       else if (CurrentState == CommandType.Snapshot)
                        {
                                //TODO: Snapshot generator second thread...
                        }
@@ -149,14 +169,16 @@ namespace Automap
                        {
                                uint ejectedItem = 0;
                                uint updatedChunks = 0;
+                               uint updatedPixels = 0;
 
                                //-- Should dodge enumerator changing underfoot....at a cost.
                                if (!columnCounter.IsEmpty)
                                {
                                        var tempSet = columnCounter.ToArray().OrderByDescending(kvp => kvp.Value);
+                                       UpdateEntityMetadata();
+
                                        foreach (var mostActiveCol in tempSet)
                                        {
-
                                                var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key);
 
                                                if (mapChunk == null)
@@ -167,20 +189,27 @@ namespace Automap
                                                        continue;
                                                }
 
-                                               ColumnMeta chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk);
-                                               PngWriter pngWriter = SetupPngImage(mostActiveCol.Key, chunkMeta);
-                                               UpdateEntityMetadata();
-                                               ProcessChunkBlocks(mostActiveCol.Key, mapChunk, chunkMeta);
+                                               ColumnMeta chunkMeta;
+                                               if (chunkTopMetadata.Contains(mostActiveCol.Key))
+                                               {
+                                                       chunkMeta = chunkTopMetadata[mostActiveCol.Key];
+                                               }
+                                               else
+                                               {
+                                                       chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk);
+                                               }
 
+                                               ProcessChunkBlocks(mostActiveCol.Key, mapChunk, ref chunkMeta);
 
-                                               ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter, out uint updatedPixels);
+                                               PngWriter pngWriter = SetupPngImage(mostActiveCol.Key, chunkMeta);
+                                               ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter, out updatedPixels);
 
                                                if (updatedPixels > 0)
                                                {
 
-#if DEBUG
+                                                       #if DEBUG
                                                        Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, updatedPixels);
-#endif
+                                                       #endif
                                                        updatedChunks++;
                                                        chunkTopMetadata.Update(chunkMeta);
                                                        columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
@@ -200,7 +229,7 @@ namespace Automap
                                {
                                        //What about chunk updates themselves; a update bitmap isn't kept...
                                        updatedChunksTotal += updatedChunks;
-                                       GenerateJSONMetadata();
+                                       JsonGenerator.GenerateJSONMetadata(chunkTopMetadata, startChunkColumn, POIs, EOIs, RockIdCodes);
                                        updatedChunks = 0;
                                }
 
@@ -226,12 +255,13 @@ namespace Automap
                        finally
                        {
                                Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
+                               PersistPointsData( );
                        }
                }
 
                private void UpdateStatus(uint totalUpdates, uint voidChunks, uint delta)
                {
-                       StatusData updateData = new StatusData(totalUpdates, voidChunks, delta, RunState.Run);
+                       StatusData updateData = new StatusData(totalUpdates, voidChunks, delta, CommandType.Run);
 
                        this.ClientAPI.Event.PushEvent(AutomapStatusEventKey, updateData);
                }
@@ -241,17 +271,17 @@ namespace Automap
 
                        this.BlockID_Designators = new Dictionary<int, BlockDesignator>();
                        this.Entity_Designators = new Dictionary<AssetLocation, EntityDesignator>();
-                       this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock"), EnumBlockMaterial.Stone);
+                       this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock-"), EnumBlockMaterial.Stone);
 
                        //Add special marker types for BlockID's of "Interest", overwrite colour, and method
 
-                       Install_POI_Designators(DefaultDesignators.DefaultBlockDesignators(), DefaultDesignators.DefaultEntityDesignators());
+                       Reload_POI_Designators();
                }
 
-               private void Install_POI_Designators(ICollection<BlockDesignator> blockDesig, List<EntityDesignator> entDesig)
+               private void Reload_POI_Designators()
                {
-                       Logger.VerboseDebug("Connecting {0} standard Block-Designators", blockDesig.Count);
-                       foreach (var designator in blockDesig)
+                       Logger.VerboseDebug("Connecting {0} Configured Block-Designators", configuration.BlockDesignators.Count);
+                       foreach (var designator in configuration.BlockDesignators)
                        {
                                var blockIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, designator.Pattern, designator.Material);
                                if (blockIDs.Count > 0) { Logger.VerboseDebug("Designator {0} has {1} associated blockIDs", designator.ToString(), blockIDs.Count); }
@@ -263,8 +293,8 @@ namespace Automap
                        this.ChunkRenderer.BlockID_Designators = BlockID_Designators;
 
 
-                       Logger.VerboseDebug("Connecting {0} standard Entity-Designators", entDesig.Count);
-                       foreach (var designator in entDesig)
+                       Logger.VerboseDebug("Connecting {0} Configured Entity-Designators", configuration.EntityDesignators.Count);
+                       foreach (var designator in configuration.EntityDesignators)
                        {
                                //Get Variants first, from EntityTypes...better be populated!
                                var matched = ClientAPI.World.EntityTypes.FindAll(entp => entp.Code.BeginsWith(designator.Pattern.Domain, designator.Pattern.Path));
@@ -283,81 +313,59 @@ namespace Automap
 
                }
 
+
+
                /// <summary>
-               /// Generates the JSON Metadata. (in Map object format )
+               /// Store Points/Entity of Interest
                /// </summary>
-               private void GenerateJSONMetadata()
+               private void PersistPointsData( )
                {
-                       string jsonFilename = Path.Combine(path, "Metadata.js");
-
-                       StreamWriter jsonWriter = new StreamWriter(jsonFilename, false, Encoding.UTF8);
-                       using (jsonWriter)
-                       {
-                               jsonWriter.Write("ViewFrame.chunks={};");
-                               jsonWriter.Write("ViewFrame.chunks.worldSeedNum={0};", ClientAPI.World.Seed);
-                               jsonWriter.Write("ViewFrame.chunks.genTime=new Date('{0}');", DateTimeOffset.UtcNow.ToString("O"));
-                               jsonWriter.Write("ViewFrame.chunks.startCoords=[{0},{1}];", startChunkColumn.X, startChunkColumn.Y);
-                               jsonWriter.Write("ViewFrame.chunks.chunkSize={0};", chunkSize);
-                               jsonWriter.Write("ViewFrame.chunks.northMostChunk={0};", chunkTopMetadata.North_mostChunk);
-                               jsonWriter.Write("ViewFrame.chunks.southMostChunk={0};", chunkTopMetadata.South_mostChunk);
-                               jsonWriter.Write("ViewFrame.chunks.eastMostChunk={0};", chunkTopMetadata.East_mostChunk);
-                               jsonWriter.Write("ViewFrame.chunks.westMostChunk={0};", chunkTopMetadata.West_mostChunk);
-                               //MAP object format - [key, value]: key is "x_y"
-                               jsonWriter.Write("ViewFrame.chunks.chunkMetadata=new Map([");
-                               foreach (var shard in chunkTopMetadata)
-                               {
-                                       jsonWriter.Write("['{0}_{1}',", shard.Location.X, shard.Location.Y);
-                                       jsonWriter.Write("{");
-                                       jsonWriter.Write("prettyCoord:'{0}',", shard.Location.PrettyCoords(ClientAPI));
-                                       jsonWriter.Write("chunkAge:'{0}',", shard.ChunkAge.ToString("g"));//World age - relative? or last edit ??
-                                       jsonWriter.Write("temp:'{0}',", shard.Temperature.ToString("F1"));
-                                       jsonWriter.Write("YMax:'{0}',", shard.YMax);
-                                       jsonWriter.Write("fert:'{0}',", shard.Fertility.ToString("F1"));
-                                       jsonWriter.Write("forestDens:'{0}',", shard.ForestDensity.ToString("F1"));
-                                       jsonWriter.Write("rain:'{0}',", shard.Rainfall.ToString("F1"));
-                                       jsonWriter.Write("shrubDens:'{0}',", shard.ShrubDensity.ToString("F1"));
-                                       jsonWriter.Write("airBlocks:'{0}',", shard.AirBlocks);
-                                       jsonWriter.Write("nonAirBlocks:'{0}',", shard.NonAirBlocks);
-                                       //TODO: Heightmap
-                                       //TODO: Rock-ratio
-                                       jsonWriter.Write("}],");
-                               }
-                               jsonWriter.Write("]);");
-
+               //POI and EOI raw dump files ~ WRITE em!
+               //var poiRawFile = File.
+               string poiPath = Path.Combine(path, poiFileName);
+               string eoiPath = Path.Combine(path, eoiFileName);
+
+               if (this.POIs.Count > 0) {
+               using (var poiFile = File.OpenWrite(poiPath)) {
+                       Serializer.Serialize<PointsOfInterest>(poiFile, this.POIs);
+               }
+               }
 
-                               jsonWriter.Write("ViewFrame.chunks.pointsOfInterest = new Map([");
-                               foreach (var poi in POIs)
-                               {
-                                       jsonWriter.Write("['{0}_{1}',", poi.Location.X, poi.Location.Z);
-                                       jsonWriter.Write("{");
-                                       jsonWriter.Write("prettyCoord:'{0}',", poi.Location.PrettyCoords(ClientAPI));
-                                       jsonWriter.Write("notes:'{0}',", poi.Notes.Replace("'", "\'").Replace("\n","\\n"));
-                                       jsonWriter.Write("time:new Date('{0}'),", poi.Timestamp.ToString("O"));
-                                       jsonWriter.Write("chunkPos:'{0}_{1}',", (poi.Location.X / chunkSize), (poi.Location.Z / chunkSize));
-                                       jsonWriter.Write("}],");
-                               }
+               if (this.EOIs.Count > 0) {
+               using (var eoiFile = File.OpenWrite(eoiPath)) {
+                       Serializer.Serialize<EntitiesOfInterest>(eoiFile, this.EOIs);
+               }
+               }
 
-                               foreach (var poi in EOIs)
-                               {
-                                       jsonWriter.Write("['{0}_{1}',", poi.Location.X, poi.Location.Z);
-                                       jsonWriter.Write("{");
-                                       jsonWriter.Write("prettyCoord:'{0}',", poi.Location.PrettyCoords(ClientAPI));
-                                       jsonWriter.Write("notes:'{0}',", poi.Notes.Replace("'", "\'"));
-                                       jsonWriter.Write("time:new Date('{0}'),", poi.Timestamp.ToString("O"));
-                                       jsonWriter.Write("chunkPos:'{0}_{1}',", (poi.Location.X / chunkSize), (poi.Location.Z / chunkSize));
-                                       jsonWriter.Write("}],");
-                               }
-                               jsonWriter.Write("]);");
+               //Create Easy to Parse TSV file for tool/human use....
+               string pointsTsvPath = Path.Combine(path, pointsTsvFileName);
 
-                               jsonWriter.Flush();
+               using (var tsvWriter = new StreamWriter(pointsTsvPath, false, Encoding.UTF8))
+               {
+               tsvWriter.WriteLine("Name\tDescription\tLocation\tTime\t");
+                       foreach (var point in this.POIs) {
+                                       tsvWriter.Write(point.Name + "\t");
+                                       tsvWriter.Write(point.Notes + "\t");
+                                       tsvWriter.Write(point.Location.PrettyCoords(ClientAPI) + "\t");
+                                       tsvWriter.Write(point.Timestamp.ToString("u")+"\t");
+                                       tsvWriter.WriteLine();
                        }
-
+                       foreach (var entity in this.EOIs) {
+                                       tsvWriter.Write(entity.Name + "\t");
+                                       tsvWriter.Write(entity.Notes + "\t");
+                                       tsvWriter.Write(entity.Location.PrettyCoords(ClientAPI) + "\t");
+                                       tsvWriter.Write(entity.Timestamp.ToString("u") + "\t");
+                                       tsvWriter.WriteLine( );
+                       }
+               tsvWriter.WriteLine();
+               tsvWriter.Flush( );
                }
 
+               }
 
                private ColumnMeta CreateColumnMetadata(KeyValuePair<Vec2i, uint> mostActiveCol, IMapChunk mapChunk)
                {
-                       ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), chunkSize);
+                       ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), (byte) chunkSize);
                        BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * chunkSize,
                                                                                        mapChunk.YMax,
                                                                                        mostActiveCol.Key.Y * chunkSize);
@@ -379,47 +387,71 @@ namespace Automap
                        if (worldmapDir.Exists)
                        {
 
-                               var files = worldmapDir.GetFiles(chunkFile_filter);
+                               var shardFiles = worldmapDir.GetFiles(chunkFile_filter);
 
-                               if (files.Length > 0)
+                               if (shardFiles.Length > 0)
                                {
-#if DEBUG
-                                       Logger.VerboseDebug("{0} Existing world chunk shards", files.Length);
-#endif
-
+                                       #if DEBUG
+                                       Logger.VerboseDebug("Metadata reloading from {0} shards", shardFiles.Length);
+                                       #endif
+                                        
+                                       foreach (var shardFile in shardFiles) {
+
+                                       if (shardFile.Length < 1024) continue;
+                                       var result = chunkShardRegex.Match(shardFile.Name);
+                                       if (result.Success) {
+                                       int X_chunk_pos = int.Parse(result.Groups["X"].Value);
+                                       int Z_chunk_pos = int.Parse(result.Groups["Z"].Value);
+
+                                       try 
+                                               {
+                                               using (var fileStream = shardFile.OpenRead( )) {
 
+                                               PngReader pngRead = new PngReader(fileStream);
+                                               pngRead.ReadSkippingAllRows( );
+                                               pngRead.End( );
+                                               //Parse PNG chunks for METADATA in shard
+                                               PngMetadataChunk metadataFromPng = pngRead.GetChunksList( ).GetById1(PngMetadataChunk.ID) as PngMetadataChunk;
 
-                                       foreach (var shardFile in files)
-                                       {
+                                               chunkTopMetadata.Add(metadataFromPng.ChunkMetadata);
+                                               }
 
-                                               if (shardFile.Length < 512) continue;
-                                               var result = chunkShardRegex.Match(shardFile.Name);
-                                               if (result.Success)
+                                               }
+                                               catch (PngjException someEx)
                                                {
-                                                       int X_chunk_pos = int.Parse(result.Groups["X"].Value);
-                                                       int Z_chunk_pos = int.Parse(result.Groups["Z"].Value);
-
-                                                       //Parse PNG chunks for METADATA in shard
-                                                       using var fileStream = shardFile.OpenRead();
-                                                       //TODO: Add corrupted PNG Exception handing HERE !
-                                                       PngReader pngRead = new PngReader(fileStream);
-                                                       pngRead.ReadSkippingAllRows();
-                                                       pngRead.End();
+                                                       Logger.Error("PNG Corruption file '{0}' - Reason: {1}", shardFile.Name, someEx);
+                                                       continue;
+                                               }
+                                               }
 
-                                                       PngMetadataChunk metadataFromPng = pngRead.GetChunksList().GetById1(PngMetadataChunk.ID) as PngMetadataChunk;
+                                       }
+                               }
 
-                                                       chunkTopMetadata.Add(metadataFromPng.ChunkMetadata);
+                               //POI and EOI raw dump files ~ reload em!
+                               //var poiRawFile = File.
+                               string poiPath = Path.Combine(path, poiFileName);
+                               string eoiPath = Path.Combine(path, eoiFileName);
 
-                                               }
+                               if (File.Exists(poiPath)) {
+                                       using (var poiFile = File.OpenRead(poiPath)) {
+                                       this.POIs = Serializer.Deserialize<PointsOfInterest>(poiFile);
+                                       Logger.VerboseDebug("Reloaded {0} POIs from file.", this.POIs.Count);
                                        }
+                               }
 
+                               if (File.Exists(eoiPath)) {
+                                       using (var eoiFile = File.OpenRead(eoiPath)) {
+                                       this.EOIs = Serializer.Deserialize<EntitiesOfInterest>(eoiFile);
+                                       Logger.VerboseDebug("Reloaded {0} EOIs from file.", this.EOIs.Count);
+                                       }
                                }
+
                        }
                        else
                        {
-#if DEBUG
+                               #if DEBUG
                                Logger.VerboseDebug("Could not open world map directory");
-#endif
+                               #endif
                        }
 
 
@@ -444,36 +476,39 @@ namespace Automap
                                ChunkMetadata = metadata
                        };
                        pngWriter.GetChunksList().Queue(pngChunkMeta);
+                       pngWriter.CompLevel = 9;// 9 is the maximum compression
+                       pngWriter.CompressionStrategy = Hjg.Pngcs.Zlib.EDeflateCompressStrategy.Huffman;
 
                        return pngWriter;
                }
 
                /// <summary>
-               /// Does the heavy lifting of Scanning columns of chunks - creates Heightmap and Processes POIs, Entities, and stats...
+               /// Does the heavy lifting of Scanning columns of chunks - scans for BlockEntity, creates Heightmap and stats...
                /// </summary>
                /// <param name="key">Chunk Coordinate</param>
                /// <param name="mapChunk">Map chunk.</param>
                /// <param name="chunkMeta">Chunk metadata</param>
-               private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ColumnMeta chunkMeta)
+               private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ref ColumnMeta chunkMeta)
                {
 
                        int targetChunkY = mapChunk.YMax / chunkSize;//Surface ... 
                        for (; targetChunkY > 0; targetChunkY--)
                        {
-                               if (!(ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) is WorldChunk chunkData) || chunkData.BlockEntities == null)
-                               {
-#if DEBUG
-                                       Logger.VerboseDebug("Chunk null or empty X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
-#endif
-                                       continue;
-                               }
+                       WorldChunk chunkData = ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) as WorldChunk;
+
+                       if (chunkData == null || chunkData.BlockEntities == null) {
+                       #if DEBUG
+                       Logger.VerboseDebug("Chunk null or empty X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
+                       #endif
+                       continue;
+                       }
 
                                /*************** Chunk Entities Scanning *********************/
                                if (chunkData.BlockEntities != null && chunkData.BlockEntities.Length > 0)
                                {
-#if DEBUG
+                               #if DEBUG
                                        Logger.VerboseDebug("Surface@ {0} = BlockEntities: {1}", key, chunkData.BlockEntities.Length);
-#endif
+                               #endif
 
                                        foreach (var blockEnt in chunkData.BlockEntities)
                                        {
@@ -538,12 +573,12 @@ namespace Automap
                {
                        Logger.Debug("Presently {0} Entities", ClientAPI.World.LoadedEntities.Count);
                        //Mabey scan only for 'new' entities by tracking ID in set?
-                       foreach (var loadedEntity in ClientAPI.World.LoadedEntities.ToList())
+                       foreach (var loadedEntity in ClientAPI.World.LoadedEntities.ToArray())
                        {
 
-#if DEBUG
+                               #if DEBUG
                                //Logger.VerboseDebug($"ENTITY: ({loadedEntity.Value.Code}) = #{loadedEntity.Value.EntityId} {loadedEntity.Value.State} {loadedEntity.Value.LocalPos}    <<<<<<<<<<<<");
-#endif
+                               #endif
 
                                var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code));
                                if (dMatch.Value != null)
@@ -560,9 +595,10 @@ namespace Automap
                {
                        var playerNodePoi = new PointOfInterest()
                        {
+                               Name = "Note",
                                Location = ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Copy(),
                                Notes = notation,
-                               Timestamp = DateTimeOffset.UtcNow,
+                               Timestamp = DateTime.UtcNow,
                        };
 
                        this.POIs.AddReplace(playerNodePoi);
@@ -577,38 +613,32 @@ namespace Automap
                        CommandData cmdData = data as CommandData;
 
 
-                       if (CurrentState != RunState.Snapshot)
+                       if (CurrentState != CommandType.Snapshot)
                        {
                                switch (cmdData.State)
                                {
-                                       case RunState.Run:
+                                       case CommandType.Run:                                                                                           
+                                       case CommandType.Stop:
+                                               if (CurrentState != cmdData.State) {
                                                CurrentState = cmdData.State;
                                                AwakenCartographer(0.0f);
+                                               }
                                                break;
 
-                                       case RunState.Stop:
-                                               CurrentState = cmdData.State;
-                                               break;
-
-                                       case RunState.Snapshot:
-                                               CurrentState = RunState.Stop;
+                                       case CommandType.Snapshot:
+                                               CurrentState = CommandType.Stop;
                                                //Snapshot starts a second thread/process...
 
                                                break;
 
-                                       case RunState.Notation:
+                                       case CommandType.Notation:
                                                //Add to POI list where player location
                                                AddNote(cmdData.Notation);
                                                break;
                                }
-
                        }
 
-                       if (CurrentState != cmdData.State)
-                       {
-                               CurrentState = cmdData.State;
-                               AwakenCartographer(0.0f);
-                       }
+
 
 #if DEBUG
                        ClientAPI.TriggerChatMessage($"Automap commanded to: {cmdData.State} ");
@@ -617,8 +647,10 @@ namespace Automap
                }
 
 
+
+
                #endregion
 
        }
 
-}
\ No newline at end of file
+}