diff --git a/MinecraftClient/Mapping/Chunk.cs b/MinecraftClient/Mapping/Chunk.cs
index 3ae88b73..0ee8c7ed 100644
--- a/MinecraftClient/Mapping/Chunk.cs
+++ b/MinecraftClient/Mapping/Chunk.cs
@@ -20,11 +20,6 @@ namespace MinecraftClient.Mapping
///
private readonly Block[,,] blocks = new Block[SizeX, SizeY, SizeZ];
- ///
- /// Lock for thread safety
- ///
- private readonly ReaderWriterLockSlim blockLock = new ReaderWriterLockSlim();
-
///
/// Read, or set the specified block
///
@@ -43,15 +38,7 @@ namespace MinecraftClient.Mapping
if (blockZ < 0 || blockZ >= SizeZ)
throw new ArgumentOutOfRangeException("blockZ", "Must be between 0 and " + (SizeZ - 1) + " (inclusive)");
- blockLock.EnterReadLock();
- try
- {
- return blocks[blockX, blockY, blockZ];
- }
- finally
- {
- blockLock.ExitReadLock();
- }
+ return blocks[blockX, blockY, blockZ];
}
set
{
@@ -62,18 +49,22 @@ namespace MinecraftClient.Mapping
if (blockZ < 0 || blockZ >= SizeZ)
throw new ArgumentOutOfRangeException("blockZ", "Must be between 0 and " + (SizeZ - 1) + " (inclusive)");
- blockLock.EnterWriteLock();
- try
- {
- blocks[blockX, blockY, blockZ] = value;
- }
- finally
- {
- blockLock.ExitWriteLock();
- }
+ blocks[blockX, blockY, blockZ] = value;
}
}
+ ///
+ /// Used when parsing chunks
+ ///
+ /// Block X
+ /// Block Y
+ /// Block Z
+ /// Block
+ public void SetWithoutCheck(int blockX, int blockY, int blockZ, Block block)
+ {
+ blocks[blockX, blockY, blockZ] = block;
+ }
+
///
/// Get block at the specified location
///
diff --git a/MinecraftClient/Mapping/ChunkColumn.cs b/MinecraftClient/Mapping/ChunkColumn.cs
index 3c2272e3..ca828712 100644
--- a/MinecraftClient/Mapping/ChunkColumn.cs
+++ b/MinecraftClient/Mapping/ChunkColumn.cs
@@ -20,11 +20,6 @@ namespace MinecraftClient.Mapping
///
private readonly Chunk[] chunks;
- ///
- /// Lock for thread safety
- ///
- private readonly ReaderWriterLockSlim chunkLock = new ReaderWriterLockSlim();
-
///
/// Create a new ChunkColumn
///
@@ -44,27 +39,11 @@ namespace MinecraftClient.Mapping
{
get
{
- chunkLock.EnterReadLock();
- try
- {
- return chunks[chunkY];
- }
- finally
- {
- chunkLock.ExitReadLock();
- }
+ return chunks[chunkY];
}
set
{
- chunkLock.EnterWriteLock();
- try
- {
- chunks[chunkY] = value;
- }
- finally
- {
- chunkLock.ExitWriteLock();
- }
+ chunks[chunkY] = value;
}
}
diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs
index 454d5a3b..5810d2cc 100644
--- a/MinecraftClient/Mapping/World.cs
+++ b/MinecraftClient/Mapping/World.cs
@@ -14,17 +14,17 @@ namespace MinecraftClient.Mapping
///
/// The chunks contained into the Minecraft world
///
- private Dictionary> chunks = new Dictionary>();
+ private Dictionary> chunks = new();
///
/// The dimension info of the world
///
- private static Dimension dimension = new Dimension();
+ private static Dimension dimension = new();
///
/// Lock for thread safety
///
- private readonly ReaderWriterLockSlim chunksLock = new ReaderWriterLockSlim();
+ private readonly ReaderWriterLockSlim chunksLock = new();
///
/// Chunk data parsing progress
@@ -109,6 +109,54 @@ namespace MinecraftClient.Mapping
return dimension;
}
+ ///
+ /// Set chunk column at the specified location
+ ///
+ /// ChunkColumn X
+ /// ChunkColumn Y
+ /// ChunkColumn Z
+ /// ChunkColumn size
+ /// Chunk data
+ /// Whether the ChunkColumn has been fully loaded
+ public void StoreChunk(int chunkX, int chunkY, int chunkZ, int chunkColumnSize, Chunk chunk, bool loadCompleted)
+ {
+ ChunkColumn? chunkColumn = null;
+
+ chunksLock.EnterUpgradeableReadLock();
+ try
+ {
+ //Read a chunk
+ if (chunks.ContainsKey(chunkX))
+ if (chunks[chunkX].ContainsKey(chunkZ))
+ chunkColumn = chunks[chunkX][chunkZ];
+
+ if (chunkColumn == null)
+ {
+ chunkColumn = new ChunkColumn(chunkColumnSize);
+ chunksLock.EnterWriteLock();
+ try
+ {
+ //Update a chunk column
+ if (!chunks.ContainsKey(chunkX))
+ chunks[chunkX] = new Dictionary();
+ chunks[chunkX][chunkZ] = chunkColumn;
+ }
+ finally
+ {
+ chunksLock.ExitWriteLock();
+ }
+ }
+ }
+ finally
+ {
+ chunksLock.ExitUpgradeableReadLock();
+ }
+
+ chunkColumn[chunkY] = chunk;
+ if (loadCompleted)
+ chunkColumn.FullyLoaded = true;
+ }
+
///
/// Get chunk column at the specified location
///
diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs
index 334de9bc..e3b6aa63 100644
--- a/MinecraftClient/Program.cs
+++ b/MinecraftClient/Program.cs
@@ -16,6 +16,7 @@ using MinecraftClient.WinAPI;
using MinecraftClient.Protocol.Keys;
using System.Security.Cryptography;
using System.Xml.Linq;
+using System.Threading.Tasks;
namespace MinecraftClient
{
@@ -84,9 +85,12 @@ namespace MinecraftClient
//Take advantage of Windows 10 / Mac / Linux UTF-8 console
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- // If we're on windows, check if our version is Win10 or greater.
- if (WindowsVersion.WinMajorVersion >= 10)
- Console.OutputEncoding = Console.InputEncoding = Encoding.UTF8;
+ Parallel.Invoke(() =>
+ {
+ // If we're on windows, check if our version is Win10 or greater.
+ if (WindowsVersion.WinMajorVersion >= 10)
+ Console.OutputEncoding = Console.InputEncoding = Encoding.UTF8;
+ });
}
else
{
diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs
index 624924ab..2d9d7226 100644
--- a/MinecraftClient/Protocol/Handlers/DataTypes.cs
+++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs
@@ -253,17 +253,15 @@ namespace MinecraftClient.Protocol.Handlers
/// The integer
public int ReadNextVarInt(Queue cache)
{
- string rawData = BitConverter.ToString(cache.ToArray());
int i = 0;
int j = 0;
- int k = 0;
- while (true)
+ byte b;
+ do
{
- k = ReadNextByte(cache);
- i |= (k & 0x7F) << j++ * 7;
- if (j > 5) throw new OverflowException("VarInt too big " + rawData);
- if ((k & 0x80) != 128) break;
- }
+ b = cache.Dequeue();
+ i |= (b & 127) << j++ * 7;
+ if (j > 5) throw new OverflowException("VarInt too big");
+ } while ((b & 128) == 128);
return i;
}
diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs
index 5ba94832..b3bd4ff0 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol18.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs
@@ -558,12 +558,19 @@ namespace MinecraftClient.Protocol.Handlers
int dataSize = dataTypes.ReadNextVarInt(packetData); // Size
- new Task(() =>
+ Parallel.Invoke(() =>
{
bool loaded = pTerrain.ProcessChunkColumnData(chunkX, chunkZ, verticalStripBitmask, packetData, cancellationToken);
if (loaded)
+ {
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
- }).Start();
+ if (handler.GetWorld().chunkCnt == 464)
+ {
+ log.Info("Loaded 464 chunks, exit");
+ System.Environment.Exit(0);
+ }
+ }
+ });
// Block Entity data: ignored
// Light data: ignored
@@ -582,12 +589,12 @@ namespace MinecraftClient.Protocol.Handlers
int compressedDataSize = dataTypes.ReadNextInt(packetData);
byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData);
byte[] decompressed = ZlibUtils.Decompress(compressed);
- new Task(() =>
+ Parallel.Invoke(() =>
{
bool loaded = pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed), cancellationToken);
if (loaded)
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
- }).Start();
+ });
}
else
{
@@ -611,12 +618,12 @@ namespace MinecraftClient.Protocol.Handlers
else dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above
}
int dataSize = dataTypes.ReadNextVarInt(packetData);
- new Task(() =>
+ Parallel.Invoke(() =>
{
bool loaded = pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData, cancellationToken);
if (loaded)
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
- }).Start();
+ });
}
}
}
@@ -872,7 +879,7 @@ namespace MinecraftClient.Protocol.Handlers
}
//Process chunk records
- new Task(() =>
+ Parallel.Invoke(() =>
{
for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++)
{
@@ -882,7 +889,7 @@ namespace MinecraftClient.Protocol.Handlers
if (loaded)
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
}
- }).Start();
+ });
}
break;
diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs
index 015a5538..06109f79 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Numerics;
using System.Threading;
//using System.Linq;
//using System.Text;
@@ -42,24 +43,19 @@ namespace MinecraftClient.Protocol.Handlers
if (bitsPerEntry == 0 && protocolversion >= Protocol18Handler.MC_1_18_1_Version)
{
// Palettes: Single valued - 1.18(1.18.1) and above
- ushort value = (ushort)dataTypes.ReadNextVarInt(cache);
+ ushort blockId = (ushort)dataTypes.ReadNextVarInt(cache);
dataTypes.SkipNextVarInt(cache); // Data Array Length will be zero
// Empty chunks will not be stored
- if (new Block(value).Type == Material.Air)
+ if (new Block(blockId).Type == Material.Air)
return null;
for (int blockY = 0; blockY < Chunk.SizeY; blockY++)
- {
for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++)
- {
for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
- {
- chunk[blockX, blockY, blockZ] = new Block(value);
- }
- }
- }
+ chunk.SetWithoutCheck(blockX, blockY, blockZ, new Block(blockId));
+
}
else
{
@@ -129,7 +125,7 @@ namespace MinecraftClient.Protocol.Handlers
}
// We have our block, save the block into the chunk
- chunk[blockX, blockY, blockZ] = new Block(blockId);
+ chunk.SetWithoutCheck(blockX, blockY, blockZ, new Block(blockId));
}
}
}
@@ -152,7 +148,7 @@ namespace MinecraftClient.Protocol.Handlers
if (cancellationToken.IsCancellationRequested)
return false;
- var world = handler.GetWorld();
+ World world = handler.GetWorld();
int chunkColumnSize = (World.GetDimension().height + 15) / 16; // Round up
@@ -167,10 +163,8 @@ namespace MinecraftClient.Protocol.Handlers
// 1.18 and above always contains all chunk section in data
// 1.17 and 1.17.1 need vertical strip bitmask to know if the chunk section is included
- if ((protocolversion >= Protocol18Handler.MC_1_18_1_Version) ||
- (((protocolversion == Protocol18Handler.MC_1_17_Version) ||
- (protocolversion == Protocol18Handler.MC_1_17_1_Version)) &&
- ((verticalStripBitmask![chunkY / 64] & (1UL << (chunkY % 64))) != 0)))
+ if ((protocolversion >= Protocol18Handler.MC_1_18_1_Version) ||
+ ((verticalStripBitmask![chunkY / 64] & (1UL << (chunkY % 64))) != 0))
{
// Non-air block count inside chunk section, for lighting purposes
int blockCnt = dataTypes.ReadNextShort(cache);
@@ -186,9 +180,7 @@ namespace MinecraftClient.Protocol.Handlers
//We have our chunk, save the chunk into the world
handler.InvokeOnMainThread(() =>
{
- if (handler.GetWorld()[chunkX, chunkZ] == null)
- handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn(chunkColumnSize);
- handler.GetWorld()[chunkX, chunkZ]![chunkY] = chunk;
+ world.StoreChunk(chunkX, chunkY, chunkZ, chunkColumnSize, chunk, chunkY == (chunkColumnSize - 1));
});
// Skip Read Biomes (Type: Paletted Container) - 1.18(1.18.1) and above
@@ -218,11 +210,6 @@ namespace MinecraftClient.Protocol.Handlers
// Don't worry about skipping remaining data since there is no useful data afterwards in 1.9
// (plus, it would require parsing the tile entity lists' NBT)
}
- handler.InvokeOnMainThread(() =>
- {
- if (handler.GetWorld()[chunkX, chunkZ] != null)
- handler.GetWorld()[chunkX, chunkZ]!.FullyLoaded = true;
- });
return true;
}
@@ -244,12 +231,15 @@ namespace MinecraftClient.Protocol.Handlers
if (cancellationToken.IsCancellationRequested)
return false;
+ World world = handler.GetWorld();
+
const int chunkColumnSize = 16;
if (protocolversion >= Protocol18Handler.MC_1_9_Version)
{
// 1.9 and above chunk format
// Unloading chunks is handled by a separate packet
- for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++)
+ int maxChunkY = sizeof(int) * 8 - 1 - BitOperations.LeadingZeroCount(chunkMask);
+ for (int chunkY = 0; chunkY <= maxChunkY; chunkY++)
{
if (cancellationToken.IsCancellationRequested)
return false;
@@ -363,7 +353,7 @@ namespace MinecraftClient.Protocol.Handlers
}
// We have our block, save the block into the chunk
- chunk[blockX, blockY, blockZ] = new Block(blockId);
+ chunk.SetWithoutCheck(blockX, blockY, blockZ, new Block(blockId));
}
}
}
@@ -376,9 +366,7 @@ namespace MinecraftClient.Protocol.Handlers
//We have our chunk, save the chunk into the world
handler.InvokeOnMainThread(() =>
{
- if (handler.GetWorld()[chunkX, chunkZ] == null)
- handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
- handler.GetWorld()[chunkX, chunkZ]![chunkY] = chunk;
+ world.StoreChunk(chunkX, chunkY, chunkZ, chunkColumnSize, chunk, chunkY == maxChunkY);
});
//Pre-1.14 Lighting data
@@ -406,13 +394,14 @@ namespace MinecraftClient.Protocol.Handlers
//Unload the entire chunk column
handler.InvokeOnMainThread(() =>
{
- handler.GetWorld()[chunkX, chunkZ] = null;
+ world[chunkX, chunkZ] = null;
});
}
else
{
//Load chunk data from the server
- for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++)
+ int maxChunkY = sizeof(int) * 8 - 1 - BitOperations.LeadingZeroCount(chunkMask);
+ for (int chunkY = 0; chunkY <= maxChunkY; chunkY++)
{
if (cancellationToken.IsCancellationRequested)
return false;
@@ -426,7 +415,7 @@ namespace MinecraftClient.Protocol.Handlers
for (int blockY = 0; blockY < Chunk.SizeY; blockY++)
for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++)
for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
- chunk[blockX, blockY, blockZ] = new Block(queue.Dequeue());
+ chunk.SetWithoutCheck(blockX, blockY, blockZ, new Block(queue.Dequeue()));
// check before store chunk
if (cancellationToken.IsCancellationRequested)
@@ -435,9 +424,7 @@ namespace MinecraftClient.Protocol.Handlers
//We have our chunk, save the chunk into the world
handler.InvokeOnMainThread(() =>
{
- if (handler.GetWorld()[chunkX, chunkZ] == null)
- handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
- handler.GetWorld()[chunkX, chunkZ]![chunkY] = chunk;
+ world.StoreChunk(chunkX, chunkY, chunkZ, chunkColumnSize, chunk, chunkY == maxChunkY);
});
}
}
@@ -469,7 +456,7 @@ namespace MinecraftClient.Protocol.Handlers
//Unload the entire chunk column
handler.InvokeOnMainThread(() =>
{
- handler.GetWorld()[chunkX, chunkZ] = null;
+ world[chunkX, chunkZ] = null;
});
}
else
@@ -505,7 +492,8 @@ namespace MinecraftClient.Protocol.Handlers
dataTypes.ReadData(Chunk.SizeX * Chunk.SizeZ, cache); //Biomes
//Load chunk data
- for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++)
+ int maxChunkY = sizeof(int) * 8 - 1 - BitOperations.LeadingZeroCount(chunkMask);
+ for (int chunkY = 0; chunkY <= maxChunkY; chunkY++)
{
if ((chunkMask & (1 << chunkY)) != 0)
{
@@ -514,7 +502,7 @@ namespace MinecraftClient.Protocol.Handlers
for (int blockY = 0; blockY < Chunk.SizeY; blockY++)
for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++)
for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
- chunk[blockX, blockY, blockZ] = new Block(blockTypes.Dequeue(), blockMeta.Dequeue());
+ chunk.SetWithoutCheck(blockX, blockY, blockZ, new Block(blockTypes.Dequeue(), blockMeta.Dequeue()));
// check before store chunk
if (cancellationToken.IsCancellationRequested)
@@ -522,19 +510,12 @@ namespace MinecraftClient.Protocol.Handlers
handler.InvokeOnMainThread(() =>
{
- if (handler.GetWorld()[chunkX, chunkZ] == null)
- handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
- handler.GetWorld()[chunkX, chunkZ]![chunkY] = chunk;
+ world.StoreChunk(chunkX, chunkY, chunkZ, chunkColumnSize, chunk, chunkY == maxChunkY);
});
}
}
}
}
- handler.InvokeOnMainThread(() =>
- {
- if (handler.GetWorld()[chunkX, chunkZ] != null)
- handler.GetWorld()[chunkX, chunkZ]!.FullyLoaded = true;
- });
return true;
}
}
diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs
index 6f0f6654..f0794927 100644
--- a/MinecraftClient/Settings.cs
+++ b/MinecraftClient/Settings.cs
@@ -7,6 +7,8 @@ using System.Text.RegularExpressions;
using MinecraftClient.Protocol.Session;
using MinecraftClient.Protocol;
using MinecraftClient.Mapping;
+using System.Threading.Tasks;
+using System.Collections.Concurrent;
namespace MinecraftClient
{
@@ -261,29 +263,39 @@ namespace MinecraftClient
try
{
string[] Lines = File.ReadAllLines(file);
- Section section = Section.Default;
- foreach (string lineRAW in Lines)
- {
- string line = section == Section.Main && lineRAW.ToLower().Trim().StartsWith("password")
- ? lineRAW.Trim() //Do not strip # in passwords
- : lineRAW.Split('#')[0].Trim();
- if (line.Length > 0)
+ ConcurrentDictionary sectionInfo = new();
+ Parallel.For(0, Lines.Length, idx =>
+ {
+ string line = Lines[idx].Split('#')[0].Trim();
+ if (line.Length > 2 && line[0] == '[' && line[^1] == ']')
+ sectionInfo[idx] = GetSection(line[1..^1]);
+ });
+
+ Section section = Section.Default;
+ for (int idx = 0; idx < Lines.Length; ++idx)
+ {
+ if (sectionInfo.ContainsKey(idx))
{
- if (line[0] == '[' && line[line.Length - 1] == ']')
- {
- section = GetSection(line.Substring(1, line.Length - 2).ToLower());
- }
- else
+ section = sectionInfo[idx];
+ continue;
+ }
+
+ Parallel.Invoke(() =>
+ {
+ string line = Lines[idx].Split('#')[0].Trim();
+ if (line.Length > 1)
{
string argName = line.Split('=')[0];
+ if (section == Section.Main && argName == "password")
+ line = Lines[idx].Trim(); //Do not strip # in passwords
if (line.Length > (argName.Length + 1))
{
- string argValue = line.Substring(argName.Length + 1);
+ string argValue = line[(argName.Length + 1)..];
LoadSingleSetting(section, argName, argValue);
}
}
- }
+ });
}
}
catch (IOException) { }