diff --git a/TrueCraft.API/Configuration.cs b/TrueCraft.API/Configuration.cs
index 1a0ad74..ae2f7be 100644
--- a/TrueCraft.API/Configuration.cs
+++ b/TrueCraft.API/Configuration.cs
@@ -29,7 +29,7 @@ namespace TrueCraft.API
config = new T();
}
- var serializer = new Serializer();
+ var serializer = new Serializer(SerializationOptions.EmitDefaults);
using (var writer = new StreamWriter(configFileName))
serializer.Serialize(writer, config);
diff --git a/TrueCraft.API/NibbleArray.cs b/TrueCraft.API/NibbleArray.cs
deleted file mode 100644
index fd6ce28..0000000
--- a/TrueCraft.API/NibbleArray.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using fNbt;
-using fNbt.Serialization;
-using System;
-using System.Collections.ObjectModel;
-
-namespace TrueCraft.API
-{
- ///
- /// Represents an array of 4-bit values.
- ///
- public class NibbleArray : INbtSerializable
- {
- ///
- /// The data in the nibble array. Each byte contains
- /// two nibbles, stored in big-endian.
- ///
- public byte[] Data { get; set; }
-
- public NibbleArray()
- {
- }
-
- ///
- /// Creates a new nibble array with the given number of nibbles.
- ///
- public NibbleArray(int length)
- {
- Data = new byte[length/2];
- }
-
- ///
- /// Gets the current number of nibbles in this array.
- ///
- [NbtIgnore]
- public int Length
- {
- get { return Data.Length * 2; }
- }
-
- ///
- /// Gets or sets a nibble at the given index.
- ///
- [NbtIgnore]
- public byte this[int index]
- {
- get { return (byte)(Data[index / 2] >> ((index) % 2 * 4) & 0xF); }
- set
- {
- value &= 0xF;
- Data[index/2] &= (byte)(0xF << ((index + 1) % 2 * 4));
- Data[index/2] |= (byte)(value << (index % 2 * 4));
- }
- }
-
- public NbtTag Serialize(string tagName)
- {
- return new NbtByteArray(tagName, Data);
- }
-
- public void Deserialize(NbtTag value)
- {
- Data = value.ByteArrayValue;
- }
- }
-
- public class ReadOnlyNibbleArray
- {
- private NibbleArray NibbleArray { get; set; }
-
- public ReadOnlyNibbleArray(NibbleArray array)
- {
- NibbleArray = array;
- }
-
- public byte this[int index]
- {
- get { return NibbleArray[index]; }
- }
-
- public ReadOnlyCollection Data
- {
- get { return Array.AsReadOnly(NibbleArray.Data); }
- }
- }
-}
\ No newline at end of file
diff --git a/TrueCraft.API/NibbleSlice.cs b/TrueCraft.API/NibbleSlice.cs
new file mode 100644
index 0000000..c5b0259
--- /dev/null
+++ b/TrueCraft.API/NibbleSlice.cs
@@ -0,0 +1,82 @@
+using fNbt;
+using fNbt.Serialization;
+using System;
+using System.Collections.ObjectModel;
+
+namespace TrueCraft.API
+{
+ ///
+ /// Represents a slice of an array of 4-bit values.
+ ///
+ public class NibbleSlice : INbtSerializable
+ {
+ ///
+ /// The data in the nibble array. Each byte contains
+ /// two nibbles, stored in big-endian.
+ ///
+ public byte[] Data { get; private set; }
+ public int Offset { get; private set; }
+ public int Length { get; private set; }
+
+ public NibbleSlice(byte[] data, int offset, int length)
+ {
+ Data = data;
+ Offset = offset;
+ Length = length;
+ }
+
+ ///
+ /// Gets or sets a nibble at the given index.
+ ///
+ [NbtIgnore]
+ public byte this[int index]
+ {
+ get { return (byte)(Data[Offset + index / 2] >> (index % 2 * 4) & 0xF); }
+ set
+ {
+ value &= 0xF;
+ Data[Offset + index / 2] &= (byte)(~(0xF << (index % 2 * 4)));
+ Data[Offset + index / 2] |= (byte)(value << (index % 2 * 4));
+ }
+ }
+
+ public byte[] ToArray()
+ {
+ byte[] array = new byte[Length];
+ Buffer.BlockCopy(Data, Offset, array, 0, Length);
+ return array;
+ }
+
+ public NbtTag Serialize(string tagName)
+ {
+ return new NbtByteArray(tagName, ToArray());
+ }
+
+ public void Deserialize(NbtTag value)
+ {
+ Length = value.ByteArrayValue.Length;
+ Buffer.BlockCopy(value.ByteArrayValue, 0,
+ Data, Offset, Length);
+ }
+ }
+
+ public class ReadOnlyNibbleArray
+ {
+ private NibbleSlice NibbleArray { get; set; }
+
+ public ReadOnlyNibbleArray(NibbleSlice array)
+ {
+ NibbleArray = array;
+ }
+
+ public byte this[int index]
+ {
+ get { return NibbleArray[index]; }
+ }
+
+ public ReadOnlyCollection Data
+ {
+ get { return Array.AsReadOnly(NibbleArray.Data); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TrueCraft.API/Server/IEventScheduler.cs b/TrueCraft.API/Server/IEventScheduler.cs
index 897cb63..0183678 100644
--- a/TrueCraft.API/Server/IEventScheduler.cs
+++ b/TrueCraft.API/Server/IEventScheduler.cs
@@ -1,9 +1,11 @@
using System;
+using System.Collections.Generic;
namespace TrueCraft.API.Server
{
public interface IEventScheduler
{
+ HashSet DisabledEvents { get; }
///
/// Schedules an event to occur some time in the future.
///
diff --git a/TrueCraft.API/TrueCraft.API.csproj b/TrueCraft.API/TrueCraft.API.csproj
index efda6b0..57be35a 100644
--- a/TrueCraft.API/TrueCraft.API.csproj
+++ b/TrueCraft.API/TrueCraft.API.csproj
@@ -88,7 +88,7 @@
-
+
diff --git a/TrueCraft.API/World/IChunk.cs b/TrueCraft.API/World/IChunk.cs
index bfeb4f7..08f729a 100644
--- a/TrueCraft.API/World/IChunk.cs
+++ b/TrueCraft.API/World/IChunk.cs
@@ -15,12 +15,13 @@ namespace TrueCraft.API.World
int[] HeightMap { get; }
byte[] Biomes { get; }
DateTime LastAccessed { get; set; }
- byte[] Blocks { get; }
+ byte[] Data { get; }
bool TerrainPopulated { get; set; }
Dictionary TileEntities { get; set; }
- NibbleArray Metadata { get; }
- NibbleArray BlockLight { get; }
- NibbleArray SkyLight { get; }
+ NibbleSlice Metadata { get; }
+ NibbleSlice BlockLight { get; }
+ NibbleSlice SkyLight { get; }
+ IRegion ParentRegion { get; set; }
int GetHeight(byte x, byte z);
void UpdateHeightMap();
byte GetBlockID(Coordinates3D coordinates);
diff --git a/TrueCraft.API/World/IRegion.cs b/TrueCraft.API/World/IRegion.cs
index 5a2ba63..c40b457 100644
--- a/TrueCraft.API/World/IRegion.cs
+++ b/TrueCraft.API/World/IRegion.cs
@@ -9,6 +9,10 @@ namespace TrueCraft.API.World
Coordinates2D Position { get; }
IChunk GetChunk(Coordinates2D position, bool generate = true);
+ ///
+ /// Marks the chunk for saving in the next Save().
+ ///
+ void DamageChunk(Coordinates2D position);
void UnloadChunk(Coordinates2D position);
void Save(string path);
}
diff --git a/TrueCraft.Client/Handlers/ChunkHandlers.cs b/TrueCraft.Client/Handlers/ChunkHandlers.cs
index 09dbe17..042892f 100644
--- a/TrueCraft.Client/Handlers/ChunkHandlers.cs
+++ b/TrueCraft.Client/Handlers/ChunkHandlers.cs
@@ -53,14 +53,14 @@ namespace TrueCraft.Client.Handlers
&& packet.Depth == Chunk.Depth) // Fast path
{
// Block IDs
- Buffer.BlockCopy(data, 0, chunk.Blocks, 0, chunk.Blocks.Length);
+ Buffer.BlockCopy(data, 0, chunk.Data, 0, chunk.Data.Length);
// Block metadata
- Buffer.BlockCopy(data, chunk.Blocks.Length, chunk.Metadata.Data, 0, chunk.Metadata.Data.Length);
+ Buffer.BlockCopy(data, chunk.Data.Length, chunk.Metadata.Data, 0, chunk.Metadata.Data.Length);
// Block light
- Buffer.BlockCopy(data, chunk.Blocks.Length + chunk.Metadata.Data.Length,
+ Buffer.BlockCopy(data, chunk.Data.Length + chunk.Metadata.Data.Length,
chunk.BlockLight.Data, 0, chunk.BlockLight.Data.Length);
// Sky light
- Buffer.BlockCopy(data, chunk.Blocks.Length + chunk.Metadata.Data.Length + chunk.BlockLight.Data.Length,
+ Buffer.BlockCopy(data, chunk.Data.Length + chunk.Metadata.Data.Length + chunk.BlockLight.Data.Length,
chunk.SkyLight.Data, 0, chunk.SkyLight.Data.Length);
}
else // Slow path
diff --git a/TrueCraft.Client/ReadOnlyWorld.cs b/TrueCraft.Client/ReadOnlyWorld.cs
index a618b2f..1453f24 100644
--- a/TrueCraft.Client/ReadOnlyWorld.cs
+++ b/TrueCraft.Client/ReadOnlyWorld.cs
@@ -116,7 +116,7 @@ namespace TrueCraft.Client
public int X { get { return Chunk.X; } }
public int Z { get { return Chunk.Z; } }
- public ReadOnlyCollection Blocks { get { return Array.AsReadOnly(Chunk.Blocks); } }
+ public ReadOnlyCollection Blocks { get { return Array.AsReadOnly(Chunk.Data); } }
public ReadOnlyNibbleArray Metadata { get { return new ReadOnlyNibbleArray(Chunk.Metadata); } }
public ReadOnlyNibbleArray BlockLight { get { return new ReadOnlyNibbleArray(Chunk.BlockLight); } }
public ReadOnlyNibbleArray SkyLight { get { return new ReadOnlyNibbleArray(Chunk.SkyLight); } }
diff --git a/TrueCraft.Core.Test/World/ChunkTest.cs b/TrueCraft.Core.Test/World/ChunkTest.cs
index 64caa3b..49934a0 100644
--- a/TrueCraft.Core.Test/World/ChunkTest.cs
+++ b/TrueCraft.Core.Test/World/ChunkTest.cs
@@ -12,51 +12,80 @@ namespace TrueCraft.Core.Test.World
[TestFixture]
public class ChunkTest
{
- public Chunk Chunk { get; set; }
-
- [OneTimeSetUp]
- public void SetUp()
- {
- var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
- var file = new NbtFile(Path.Combine(assemblyDir, "Files", "TestChunk.nbt"));
- Chunk = Chunk.FromNbt(file);
- }
-
[Test]
public void TestGetBlockID()
{
- Assert.AreEqual(BedrockBlock.BlockID, Chunk.GetBlockID(Coordinates3D.Zero));
- Chunk.SetBlockID(Coordinates3D.Zero, 12);
- Assert.AreEqual(12, Chunk.GetBlockID(Coordinates3D.Zero));
- Chunk.SetBlockID(Coordinates3D.Zero, BedrockBlock.BlockID);
+ var chunk = new Chunk();
+ chunk.SetBlockID(Coordinates3D.Zero, 12);
+ Assert.AreEqual(12, chunk.GetBlockID(Coordinates3D.Zero));
}
[Test]
public void TestGetBlockLight()
{
- Assert.AreEqual(0, Chunk.GetBlockLight(Coordinates3D.Zero));
+ var chunk = new Chunk();
+ chunk.SetBlockLight(Coordinates3D.Zero, 5);
+ Assert.AreEqual(5, chunk.GetBlockLight(Coordinates3D.Zero));
}
[Test]
public void TestGetSkyLight()
{
- Assert.AreEqual(0, Chunk.GetBlockLight(Coordinates3D.Zero));
+ var chunk = new Chunk();
+ chunk.SetSkyLight(Coordinates3D.Zero, 5);
+ Assert.AreEqual(5, chunk.GetSkyLight(Coordinates3D.Zero));
}
[Test]
public void TestGetMetadata()
{
- Assert.AreEqual(0, Chunk.GetBlockLight(Coordinates3D.Zero));
+ var chunk = new Chunk();
+ chunk.SetMetadata(Coordinates3D.Zero, 5);
+ Assert.AreEqual(5, chunk.GetMetadata(Coordinates3D.Zero));
}
[Test]
public void TestHeightMap()
{
- Chunk.UpdateHeightMap();
- Assert.AreEqual(59, Chunk.GetHeight(0, 0));
- Assert.AreEqual(58, Chunk.GetHeight(1, 0));
- Chunk.SetBlockID(new Coordinates3D(1, 80, 0), 1);
- Assert.AreEqual(80, Chunk.GetHeight(1, 0));
+ var chunk = new Chunk();
+ for (int x = 0; x < Chunk.Width; ++x)
+ for (int z = 0; z < Chunk.Width; ++z)
+ chunk.SetBlockID(new Coordinates3D(x, 20, z), StoneBlock.BlockID);
+ chunk.UpdateHeightMap();
+ Assert.AreEqual(20, chunk.GetHeight(0, 0));
+ Assert.AreEqual(20, chunk.GetHeight(1, 0));
+ chunk.SetBlockID(new Coordinates3D(1, 80, 0), 1);
+ Assert.AreEqual(80, chunk.GetHeight(1, 0));
+ }
+
+ [Test]
+ public void TestConsistency()
+ {
+ var chunk = new Chunk();
+ byte val = 0;
+ for (int y = 0; y < Chunk.Height; y++)
+ for (int x = 0; x < Chunk.Width; x++)
+ for (int z = 0; z < Chunk.Depth; z++)
+ {
+ var coords = new Coordinates3D(x, y, z);
+ chunk.SetBlockID(coords, val);
+ chunk.SetMetadata(coords, (byte)(val % 16));
+ chunk.SetBlockLight(coords, (byte)(val % 16));
+ chunk.SetSkyLight(coords, (byte)(val % 16));
+ val++;
+ }
+ val = 0;
+ for (int y = 0; y < Chunk.Height; y++)
+ for (int x = 0; x < Chunk.Width; x++)
+ for (int z = 0; z < Chunk.Depth; z++)
+ {
+ var coords = new Coordinates3D(x, y, z);
+ Assert.AreEqual(val, chunk.GetBlockID(coords));
+ Assert.AreEqual((byte)(val % 16), chunk.GetMetadata(coords));
+ Assert.AreEqual((byte)(val % 16), chunk.GetBlockLight(coords));
+ Assert.AreEqual((byte)(val % 16), chunk.GetSkyLight(coords));
+ val++;
+ }
}
}
}
\ No newline at end of file
diff --git a/TrueCraft.Core/Lighting/WorldLighting.cs b/TrueCraft.Core/Lighting/WorldLighting.cs
index a106814..d5eb8ac 100644
--- a/TrueCraft.Core/Lighting/WorldLighting.cs
+++ b/TrueCraft.Core/Lighting/WorldLighting.cs
@@ -6,6 +6,7 @@ using TrueCraft.API;
using System.Collections.Generic;
using System.Diagnostics;
using TrueCraft.Profiling;
+using System.Collections.Concurrent;
namespace TrueCraft.Core.Lighting
{
@@ -34,15 +35,14 @@ namespace TrueCraft.Core.Lighting
public IBlockRepository BlockRepository { get; set; }
public IWorld World { get; set; }
- private object _Lock = new object();
- private List PendingOperations { get; set; }
+ private ConcurrentQueue PendingOperations { get; set; }
private Dictionary HeightMaps { get; set; }
public WorldLighting(IWorld world, IBlockRepository blockRepository)
{
BlockRepository = blockRepository;
World = world;
- PendingOperations = new List();
+ PendingOperations = new ConcurrentQueue();
HeightMaps = new Dictionary();
world.ChunkGenerated += (sender, e) => GenerateHeightMap(e.Chunk);
world.ChunkLoaded += (sender, e) => GenerateHeightMap(e.Chunk);
@@ -72,7 +72,7 @@ namespace TrueCraft.Core.Lighting
if (id == 0)
continue;
var provider = BlockRepository.GetBlockProvider(id);
- if (provider.LightOpacity != 0)
+ if (provider == null || provider.LightOpacity != 0)
{
map[x, z] = y;
break;
@@ -252,33 +252,31 @@ namespace TrueCraft.Core.Lighting
public bool TryLightNext()
{
LightingOperation op;
- lock (_Lock)
- {
- if (PendingOperations.Count == 0)
- return false;
- op = PendingOperations[0];
- PendingOperations.RemoveAt(0);
- }
- LightBox(op);
- return true;
+ if (PendingOperations.Count == 0)
+ return false;
+ // TODO: Maybe a timeout or something?
+ bool dequeued = false;
+ while (!(dequeued = PendingOperations.TryDequeue(out op)) && PendingOperations.Count > 0) ;
+ if (dequeued)
+ LightBox(op);
+ return dequeued;
}
public void EnqueueOperation(BoundingBox box, bool skyLight, bool initial = false)
{
- lock (_Lock)
+ // Try to merge with existing operation
+ /*
+ for (int i = PendingOperations.Count - 1; i > PendingOperations.Count - 5 && i > 0; i--)
{
- // Try to merge with existing operation
- for (int i = PendingOperations.Count - 1; i > PendingOperations.Count - 5 && i > 0; i--)
+ var op = PendingOperations[i];
+ if (op.Box.Intersects(box))
{
- var op = PendingOperations[i];
- if (op.Box.Intersects(box))
- {
- op.Box = new BoundingBox(Vector3.Min(op.Box.Min, box.Min), Vector3.Max(op.Box.Max, box.Max));
- return;
- }
+ op.Box = new BoundingBox(Vector3.Min(op.Box.Min, box.Min), Vector3.Max(op.Box.Max, box.Max));
+ return;
}
- PendingOperations.Add(new LightingOperation { SkyLight = skyLight, Box = box, Initial = initial });
}
+ */
+ PendingOperations.Enqueue(new LightingOperation { SkyLight = skyLight, Box = box, Initial = initial });
}
private void SetUpperVoxels(IChunk chunk)
diff --git a/TrueCraft.Core/TerrainGen/StandardGenerator.cs b/TrueCraft.Core/TerrainGen/StandardGenerator.cs
index 4123a1d..95c26e3 100644
--- a/TrueCraft.Core/TerrainGen/StandardGenerator.cs
+++ b/TrueCraft.Core/TerrainGen/StandardGenerator.cs
@@ -220,7 +220,7 @@ namespace TrueCraft.Core.TerrainGen
var coords = new Coordinates2D(x, z);
double distance = IsSpawnCoordinate(x, z) ? coords.Distance : 1000;
if (distance < 1000) // Avoids deep water within 1km sq of spawn
- value += (1 - distance / 1000f) * 12;
+ value += (1 - distance / 1000f) * 18;
if (value < 0)
value = GroundLevel;
if (value > Chunk.Height)
diff --git a/TrueCraft.Core/World/Chunk.cs b/TrueCraft.Core/World/Chunk.cs
index fd1584d..3394e00 100644
--- a/TrueCraft.Core/World/Chunk.cs
+++ b/TrueCraft.Core/World/Chunk.cs
@@ -25,13 +25,13 @@ namespace TrueCraft.Core.World
[NbtIgnore]
public bool IsModified { get; set; }
[NbtIgnore]
- public byte[] Blocks { get; set; }
+ public byte[] Data { get; set; }
[NbtIgnore]
- public NibbleArray Metadata { get; set; }
+ public NibbleSlice Metadata { get; set; }
[NbtIgnore]
- public NibbleArray BlockLight { get; set; }
+ public NibbleSlice BlockLight { get; set; }
[NbtIgnore]
- public NibbleArray SkyLight { get; set; }
+ public NibbleSlice SkyLight { get; set; }
public byte[] Biomes { get; set; }
public int[] HeightMap { get; set; }
public int MaxHeight { get; private set; }
@@ -73,7 +73,7 @@ namespace TrueCraft.Core.World
public bool TerrainPopulated { get; set; }
[NbtIgnore]
- public Region ParentRegion { get; set; }
+ public IRegion ParentRegion { get; set; }
public Chunk()
{
@@ -83,23 +83,24 @@ namespace TrueCraft.Core.World
TerrainPopulated = false;
LightPopulated = false;
MaxHeight = 0;
+ const int size = Width * Height * Depth;
+ const int halfSize = size / 2;
+ Data = new byte[size + halfSize * 3];
+ Metadata = new NibbleSlice(Data, size, halfSize);
+ BlockLight = new NibbleSlice(Data, size + halfSize, halfSize);
+ SkyLight = new NibbleSlice(Data, size + halfSize * 2, halfSize);
}
public Chunk(Coordinates2D coordinates) : this()
{
X = coordinates.X;
Z = coordinates.Z;
- const int size = Width * Height * Depth;
- Blocks = new byte[size];
- Metadata = new NibbleArray(size);
- BlockLight = new NibbleArray(size);
- SkyLight = new NibbleArray(size);
}
public byte GetBlockID(Coordinates3D coordinates)
{
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
- return Blocks[index];
+ return Data[index];
}
public byte GetMetadata(Coordinates3D coordinates)
@@ -127,8 +128,9 @@ namespace TrueCraft.Core.World
public void SetBlockID(Coordinates3D coordinates, byte value)
{
IsModified = true;
+ ParentRegion.DamageChunk(Coordinates);
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
- Blocks[index] = value;
+ Data[index] = value;
if (value == AirBlock.BlockID)
Metadata[index] = 0x0;
var oldHeight = GetHeight((byte)coordinates.X, (byte)coordinates.Z);
@@ -164,6 +166,7 @@ namespace TrueCraft.Core.World
public void SetMetadata(Coordinates3D coordinates, byte value)
{
IsModified = true;
+ ParentRegion.DamageChunk(Coordinates);
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
Metadata[index] = value;
}
@@ -175,6 +178,7 @@ namespace TrueCraft.Core.World
public void SetSkyLight(Coordinates3D coordinates, byte value)
{
IsModified = true;
+ ParentRegion.DamageChunk(Coordinates);
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
SkyLight[index] = value;
}
@@ -186,6 +190,7 @@ namespace TrueCraft.Core.World
public void SetBlockLight(Coordinates3D coordinates, byte value)
{
IsModified = true;
+ ParentRegion.DamageChunk(Coordinates);
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
BlockLight[index] = value;
}
@@ -210,6 +215,7 @@ namespace TrueCraft.Core.World
else
TileEntities[coordinates] = value;
IsModified = true;
+ ParentRegion.DamageChunk(Coordinates);
}
///
@@ -236,7 +242,7 @@ namespace TrueCraft.Core.World
for (y = Chunk.Height - 1; y >= 0; y--)
{
int index = y + (z * Height) + (x * Height * Width);
- if (Blocks[index] != 0)
+ if (Data[index] != 0)
{
SetHeight(x, z, y);
if (y > MaxHeight)
@@ -275,10 +281,10 @@ namespace TrueCraft.Core.World
chunk.Add(new NbtInt("Z", Z));
chunk.Add(new NbtByte("LightPopulated", (byte)(LightPopulated ? 1 : 0)));
chunk.Add(new NbtByte("TerrainPopulated", (byte)(TerrainPopulated ? 1 : 0)));
- chunk.Add(new NbtByteArray("Blocks", Blocks));
- chunk.Add(new NbtByteArray("Data", Metadata.Data));
- chunk.Add(new NbtByteArray("SkyLight", SkyLight.Data));
- chunk.Add(new NbtByteArray("BlockLight", BlockLight.Data));
+ chunk.Add(new NbtByteArray("Blocks", Data));
+ chunk.Add(new NbtByteArray("Data", Metadata.ToArray()));
+ chunk.Add(new NbtByteArray("SkyLight", SkyLight.ToArray()));
+ chunk.Add(new NbtByteArray("BlockLight", BlockLight.ToArray()));
var tiles = new NbtList("TileEntities", NbtTagType.Compound);
foreach (var kvp in TileEntities)
@@ -308,13 +314,17 @@ namespace TrueCraft.Core.World
TerrainPopulated = tag["TerrainPopulated"].ByteValue > 0;
if (tag.Contains("LightPopulated"))
LightPopulated = tag["LightPopulated"].ByteValue > 0;
- Blocks = tag["Blocks"].ByteArrayValue;
- Metadata = new NibbleArray();
- Metadata.Data = tag["Data"].ByteArrayValue;
- BlockLight = new NibbleArray();
- BlockLight.Data = tag["BlockLight"].ByteArrayValue;
- SkyLight = new NibbleArray();
- SkyLight.Data = tag["SkyLight"].ByteArrayValue;
+ const int size = Width * Height * Depth;
+ const int halfSize = size / 2;
+ Data = new byte[(int)(size * 2.5)];
+ Buffer.BlockCopy(tag["Blocks"].ByteArrayValue, 0, Data, 0, size);
+ Metadata = new NibbleSlice(Data, size, halfSize);
+ BlockLight = new NibbleSlice(Data, size + halfSize, halfSize);
+ SkyLight = new NibbleSlice(Data, size + halfSize * 2, halfSize);
+
+ Metadata.Deserialize(tag["Data"]);
+ BlockLight.Deserialize(tag["BlockLight"]);
+ SkyLight.Deserialize(tag["SkyLight"]);
if (tag.Contains("TileEntities"))
{
diff --git a/TrueCraft.Core/World/Region.cs b/TrueCraft.Core/World/Region.cs
index 10b964f..c9c41aa 100644
--- a/TrueCraft.Core/World/Region.cs
+++ b/TrueCraft.Core/World/Region.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -20,10 +21,11 @@ namespace TrueCraft.Core.World
// In chunks
public const int Width = 32, Depth = 32;
+ private ConcurrentDictionary _Chunks { get; set; }
///
/// The currently loaded chunk list.
///
- public IDictionary Chunks { get; set; }
+ public IDictionary Chunks { get { return _Chunks; } }
///
/// The location of this region in the overworld.
///
@@ -31,6 +33,7 @@ namespace TrueCraft.Core.World
public World World { get; set; }
+ private HashSet DirtyChunks { get; set; } = new HashSet();
private Stream regionFile { get; set; }
private object streamLock = new object();
@@ -40,7 +43,7 @@ namespace TrueCraft.Core.World
///
public Region(Coordinates2D position, World world)
{
- Chunks = new Dictionary();
+ _Chunks = new ConcurrentDictionary();
Position = position;
World = world;
}
@@ -51,7 +54,10 @@ namespace TrueCraft.Core.World
public Region(Coordinates2D position, World world, string file) : this(position, world)
{
if (File.Exists(file))
+ {
regionFile = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
+ regionFile.Read(HeaderCache, 0, 8192);
+ }
else
{
regionFile = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
@@ -59,6 +65,13 @@ namespace TrueCraft.Core.World
}
}
+ public void DamageChunk(Coordinates2D coords)
+ {
+ int x = coords.X / Region.Width - ((coords.X < 0) ? 1 : 0);
+ int z = coords.Z / Region.Depth - ((coords.Z < 0) ? 1 : 0);
+ DirtyChunks.Add(new Coordinates2D(coords.X - x * 32, coords.Z - z * 32));
+ }
+
///
/// Retrieves the requested chunk from the region, or
/// generates it if a world generator is provided.
@@ -66,25 +79,24 @@ namespace TrueCraft.Core.World
/// The position of the requested local chunk coordinates.
public IChunk GetChunk(Coordinates2D position, bool generate = true)
{
- // TODO: This could use some refactoring
if (!Chunks.ContainsKey(position))
{
if (regionFile != null)
{
// Search the stream for that region
+ var chunkData = GetChunkFromTable(position);
+ if (chunkData == null)
+ {
+ if (World.ChunkProvider == null)
+ throw new ArgumentException("The requested chunk is not loaded.", "position");
+ if (generate)
+ GenerateChunk(position);
+ else
+ return null;
+ return Chunks[position];
+ }
lock (streamLock)
{
- var chunkData = GetChunkFromTable(position);
- if (chunkData == null)
- {
- if (World.ChunkProvider == null)
- throw new ArgumentException("The requested chunk is not loaded.", "position");
- if (generate)
- GenerateChunk(position);
- else
- return null;
- return Chunks[position];
- }
regionFile.Seek(chunkData.Item1, SeekOrigin.Begin);
/*int length = */
new MinecraftStream(regionFile).ReadInt32(); // TODO: Avoid making new objects here, and in the WriteInt32
@@ -97,6 +109,7 @@ namespace TrueCraft.Core.World
var nbt = new NbtFile();
nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null);
var chunk = Chunk.FromNbt(nbt);
+ chunk.ParentRegion = this;
Chunks.Add(position, chunk);
World.OnChunkLoaded(new ChunkLoadedEventArgs(chunk));
break;
@@ -106,7 +119,7 @@ namespace TrueCraft.Core.World
}
}
else if (World.ChunkProvider == null)
- throw new ArgumentException("The requested chunk is not loaded.", "position");
+ throw new ArgumentException("The requested chunk is not loaded.", nameof(position));
else
{
if (generate)
@@ -124,7 +137,9 @@ namespace TrueCraft.Core.World
var chunk = World.ChunkProvider.GenerateChunk(World, globalPosition);
chunk.IsModified = true;
chunk.Coordinates = globalPosition;
- Chunks.Add(position, chunk);
+ chunk.ParentRegion = this;
+ DirtyChunks.Add(position);
+ Chunks[position] = chunk;
World.OnChunkGenerated(new ChunkLoadedEventArgs(chunk));
}
@@ -136,6 +151,8 @@ namespace TrueCraft.Core.World
if (!Chunks.ContainsKey(position))
Chunks.Add(position, chunk);
chunk.IsModified = true;
+ DirtyChunks.Add(position);
+ chunk.ParentRegion = this;
Chunks[position] = chunk;
}
@@ -162,17 +179,19 @@ namespace TrueCraft.Core.World
lock (streamLock)
{
var toRemove = new List();
- foreach (var kvp in Chunks)
+ var chunks = DirtyChunks.ToList();
+ DirtyChunks.Clear();
+ foreach (var coords in chunks)
{
- var chunk = kvp.Value;
+ var chunk = GetChunk(coords, generate: false);
if (chunk.IsModified)
{
var data = ((Chunk)chunk).ToNbt();
byte[] raw = data.SaveToBuffer(NbtCompression.ZLib);
- var header = GetChunkFromTable(kvp.Key);
+ var header = GetChunkFromTable(coords);
if (header == null || header.Item2 > raw.Length)
- header = AllocateNewChunks(kvp.Key, raw.Length);
+ header = AllocateNewChunks(coords, raw.Length);
regionFile.Seek(header.Item1, SeekOrigin.Begin);
new MinecraftStream(regionFile).WriteInt32(raw.Length);
@@ -182,7 +201,7 @@ namespace TrueCraft.Core.World
chunk.IsModified = false;
}
if ((DateTime.UtcNow - chunk.LastAccessed).TotalMinutes > 5)
- toRemove.Add(kvp.Key);
+ toRemove.Add(coords);
}
regionFile.Flush();
// Unload idle chunks
@@ -198,14 +217,15 @@ namespace TrueCraft.Core.World
#region Stream Helpers
private const int ChunkSizeMultiplier = 4096;
+ private byte[] HeaderCache = new byte[8192];
+
private Tuple GetChunkFromTable(Coordinates2D position) //
{
int tableOffset = ((position.X % Width) + (position.Z % Depth) * Width) * 4;
- regionFile.Seek(tableOffset, SeekOrigin.Begin);
byte[] offsetBuffer = new byte[4];
- regionFile.Read(offsetBuffer, 0, 3);
+ Buffer.BlockCopy(HeaderCache, tableOffset, offsetBuffer, 0, 3);
Array.Reverse(offsetBuffer);
- int length = regionFile.ReadByte();
+ int length = HeaderCache[tableOffset + 3];
int offset = BitConverter.ToInt32(offsetBuffer, 0) << 4;
if (offset == 0 || length == 0)
return null;
@@ -215,7 +235,8 @@ namespace TrueCraft.Core.World
private void CreateRegionHeader()
{
- regionFile.Write(new byte[8192], 0, 8192);
+ HeaderCache = new byte[8192];
+ regionFile.Write(HeaderCache, 0, 8192);
regionFile.Flush();
}
@@ -237,6 +258,7 @@ namespace TrueCraft.Core.World
entry[0] = (byte)length;
Array.Reverse(entry);
regionFile.Write(entry, 0, entry.Length);
+ Buffer.BlockCopy(entry, 0, HeaderCache, tableOffset, 4);
return new Tuple(dataOffset, length * ChunkSizeMultiplier);
}
diff --git a/TrueCraft.Profiling/Profiler.cs b/TrueCraft.Profiling/Profiler.cs
index 3e9fb76..e839a74 100644
--- a/TrueCraft.Profiling/Profiler.cs
+++ b/TrueCraft.Profiling/Profiler.cs
@@ -69,7 +69,8 @@ namespace TrueCraft.Profiling
{
if (Match(EnabledBuckets[i], timer.Bucket))
{
- Console.WriteLine("{0} took {1}ms", timer.Bucket, elapsed);
+ Console.WriteLine("[@{0:0.00}s] {1} took {2}ms",
+ Stopwatch.ElapsedMilliseconds / 1000.0, timer.Bucket, elapsed);
break;
}
}
diff --git a/TrueCraft/Commands/DebugCommands.cs b/TrueCraft/Commands/DebugCommands.cs
index 13ba9e5..4178bd0 100644
--- a/TrueCraft/Commands/DebugCommands.cs
+++ b/TrueCraft/Commands/DebugCommands.cs
@@ -519,7 +519,7 @@ namespace TrueCraft.Commands
{
lighter.InitialLighting(chunk, true);
(client as RemoteClient).UnloadChunk(chunk.Coordinates);
- (client as RemoteClient).LoadChunk(chunk.Coordinates);
+ (client as RemoteClient).LoadChunk(chunk);
}
}
diff --git a/TrueCraft/EntityManager.cs b/TrueCraft/EntityManager.cs
index 93baf3e..39bca21 100644
--- a/TrueCraft/EntityManager.cs
+++ b/TrueCraft/EntityManager.cs
@@ -79,7 +79,8 @@ namespace TrueCraft
(int)(entity.Position.Z) >> 4 != (int)(entity.OldPosition.Z) >> 4)
{
client.Log("Passed chunk boundary at {0}, {1}", (int)(entity.Position.X) >> 4, (int)(entity.Position.Z) >> 4);
- Task.Factory.StartNew(client.UpdateChunks);
+ Server.Scheduler.ScheduleEvent("client.update-chunks", client,
+ TimeSpan.Zero, s => client.UpdateChunks());
UpdateClientEntities(client);
}
break;
diff --git a/TrueCraft/EventScheduler.cs b/TrueCraft/EventScheduler.cs
index 7ceb253..6fff15c 100644
--- a/TrueCraft/EventScheduler.cs
+++ b/TrueCraft/EventScheduler.cs
@@ -1,10 +1,12 @@
using System;
+using System.Linq;
using TrueCraft.API.Server;
using System.Collections.Generic;
using TrueCraft.API;
using System.Diagnostics;
using TrueCraft.Profiling;
using System.Threading;
+using System.Collections.Concurrent;
namespace TrueCraft
{
@@ -15,80 +17,122 @@ namespace TrueCraft
private IMultiplayerServer Server { get; set; }
private HashSet Subjects { get; set; }
private Stopwatch Stopwatch { get; set; }
+ private ConcurrentQueue ImmediateEventQueue { get; set; }
+ private ConcurrentQueue LaterEventQueue { get; set; }
+ private ConcurrentQueue DisposedSubjects { get; set; }
+ public HashSet DisabledEvents { get; private set; }
public EventScheduler(IMultiplayerServer server)
{
Events = new List();
+ ImmediateEventQueue = new ConcurrentQueue();
+ LaterEventQueue = new ConcurrentQueue();
+ DisposedSubjects = new ConcurrentQueue();
Server = server;
Subjects = new HashSet();
Stopwatch = new Stopwatch();
+ DisabledEvents = new HashSet();
Stopwatch.Start();
}
+
+ private void ScheduleEvent(ScheduledEvent e)
+ {
+ int i;
+ for (i = 0; i < Events.Count; i++)
+ {
+ if (Events[i].When > e.When)
+ break;
+ }
+ Events.Insert(i, e);
+ }
public void ScheduleEvent(string name, IEventSubject subject, TimeSpan when, Action action)
{
- lock (EventLock)
+ if (DisabledEvents.Contains(name))
+ return;
+ long _when = Stopwatch.ElapsedTicks + when.Ticks;
+ if (subject != null && !Subjects.Contains(subject))
{
- long _when = Stopwatch.ElapsedTicks + when.Ticks;
- if (!Subjects.Contains(subject))
- {
- Subjects.Add(subject);
- subject.Disposed += Subject_Disposed;
- }
- int i;
- for (i = 0; i < Events.Count; i++)
- {
- if (Events[i].When > _when)
- break;
- }
- Events.Insert(i, new ScheduledEvent
- {
- Name = name,
- Subject = subject,
- When = _when,
- Action = action
- });
+ Subjects.Add(subject);
+ subject.Disposed += Subject_Disposed;
}
+ var queue = when.TotalSeconds > 3 ? LaterEventQueue : ImmediateEventQueue;
+ queue.Enqueue(new ScheduledEvent
+ {
+ Name = name,
+ Subject = subject,
+ When = _when,
+ Action = action
+ });
}
void Subject_Disposed(object sender, EventArgs e)
{
- // Cancel all events with this subject
- lock (EventLock)
- {
- for (int i = 0; i < Events.Count; i++)
- {
- if (Events[i].Subject == sender)
- {
- Events.RemoveAt(i);
- i--;
- }
- }
- Subjects.Remove((IEventSubject)sender);
- }
+ DisposedSubjects.Enqueue((IEventSubject)sender);
}
public void Update()
{
Profiler.Start("scheduler");
- lock (EventLock)
+ Profiler.Start("scheduler.receive-events");
+ long start = Stopwatch.ElapsedTicks;
+ long limit = Stopwatch.ElapsedMilliseconds + 10;
+ while (ImmediateEventQueue.Count > 0 && Stopwatch.ElapsedMilliseconds < limit)
{
- var start = Stopwatch.ElapsedTicks;
- for (int i = 0; i < Events.Count; i++)
+ ScheduledEvent e;
+ bool dequeued = false;
+ while (!(dequeued = ImmediateEventQueue.TryDequeue(out e))
+ && Stopwatch.ElapsedMilliseconds < limit) ;
+ if (dequeued)
+ ScheduleEvent(e);
+ }
+ while (LaterEventQueue.Count > 0 && Stopwatch.ElapsedMilliseconds < limit)
+ {
+ ScheduledEvent e;
+ bool dequeued = false;
+ while (!(dequeued = LaterEventQueue.TryDequeue(out e))
+ && Stopwatch.ElapsedMilliseconds < limit) ;
+ if (dequeued)
+ ScheduleEvent(e);
+ }
+ Profiler.Done();
+ Profiler.Start("scheduler.dispose-subjects");
+ while (DisposedSubjects.Count > 0 && Stopwatch.ElapsedMilliseconds < limit)
+ {
+ IEventSubject subject;
+ bool dequeued = false;
+ while (!(dequeued = DisposedSubjects.TryDequeue(out subject))
+ && Stopwatch.ElapsedMilliseconds < limit) ;
+ if (dequeued)
{
- var e = Events[i];
- if (e.When <= start)
+ // Cancel all events with this subject
+ for (int i = 0; i < Events.Count; i++)
{
- Profiler.Start("scheduler." + e.Name);
- e.Action(Server);
- Events.RemoveAt(i);
- i--;
- Profiler.Done();
+ if (Events[i].Subject == subject)
+ {
+ Events.RemoveAt(i);
+ i--;
+ }
}
- if (e.When > start)
- break; // List is sorted, we can exit early
+ Subjects.Remove(subject);
}
}
+ limit = Stopwatch.ElapsedMilliseconds + 10;
+ Profiler.Done();
+ for (int i = 0; i < Events.Count && Stopwatch.ElapsedMilliseconds < limit; i++)
+ {
+ var e = Events[i];
+ if (e.When <= start)
+ {
+ Profiler.Start("scheduler." + e.Name);
+ e.Action(Server);
+ Events.RemoveAt(i);
+ i--;
+ Profiler.Done();
+ }
+ if (e.When > start)
+ break; // List is sorted, we can exit early
+ }
Profiler.Done(20);
}
diff --git a/TrueCraft/Handlers/LoginHandlers.cs b/TrueCraft/Handlers/LoginHandlers.cs
index 4ba2842..239e23c 100644
--- a/TrueCraft/Handlers/LoginHandlers.cs
+++ b/TrueCraft/Handlers/LoginHandlers.cs
@@ -59,7 +59,8 @@ namespace TrueCraft.Handlers
// Send setup packets
remoteClient.QueuePacket(new LoginResponsePacket(client.Entity.EntityID, 0, Dimension.Overworld));
- remoteClient.UpdateChunks();
+ server.Scheduler.ScheduleEvent("client.update-chunks", remoteClient,
+ TimeSpan.Zero, s => remoteClient.UpdateChunks());
remoteClient.QueuePacket(new WindowItemsPacket(0, remoteClient.Inventory.GetSlots()));
remoteClient.QueuePacket(new UpdateHealthPacket((remoteClient.Entity as PlayerEntity).Health));
remoteClient.QueuePacket(new SpawnPositionPacket((int)remoteClient.Entity.Position.X,
diff --git a/TrueCraft/MultiplayerServer.cs b/TrueCraft/MultiplayerServer.cs
index bdd278d..cb18e02 100644
--- a/TrueCraft/MultiplayerServer.cs
+++ b/TrueCraft/MultiplayerServer.cs
@@ -125,6 +125,10 @@ namespace TrueCraft
public void Start(IPEndPoint endPoint)
{
+ Scheduler.DisabledEvents.Clear();
+ if (Program.ServerConfiguration.DisabledEvents != null)
+ Program.ServerConfiguration.DisabledEvents.ToList().ForEach(
+ ev => Scheduler.DisabledEvents.Add(ev));
ShuttingDown = false;
Time.Reset();
Time.Start();
@@ -156,16 +160,6 @@ namespace TrueCraft
DisconnectClient(c);
}
- public void Pause()
- {
- EnvironmentWorker.Change(Timeout.Infinite, Timeout.Infinite);
- }
-
- public void Resume()
- {
- EnvironmentWorker.Change(0, Timeout.Infinite);
- }
-
public void AddWorld(IWorld world)
{
Worlds.Add(world);
@@ -183,7 +177,8 @@ namespace TrueCraft
void HandleChunkLoaded(object sender, ChunkLoadedEventArgs e)
{
- ChunksToSchedule.Add(new Tuple(sender as IWorld, e.Chunk));
+ if (Program.ServerConfiguration.EnableEventLoading)
+ ChunksToSchedule.Add(new Tuple(sender as IWorld, e.Chunk));
if (Program.ServerConfiguration.EnableLighting)
{
var lighter = WorldLighters.SingleOrDefault(l => l.World == sender);
@@ -234,9 +229,9 @@ namespace TrueCraft
}
else
{
- for (int i = 0; i < e.Chunk.SkyLight.Data.Length; i++)
+ for (int i = 0; i < e.Chunk.SkyLight.Length * 2; i++)
{
- e.Chunk.SkyLight.Data[i] = 0xFF;
+ e.Chunk.SkyLight[i] = 0xF;
}
}
HandleChunkLoaded(sender, e);
@@ -427,11 +422,14 @@ namespace TrueCraft
Profiler.Done();
}
- Profiler.Start("environment.chunks");
- Tuple t;
- if (ChunksToSchedule.TryTake(out t))
- ScheduleUpdatesForChunk(t.Item1, t.Item2);
- Profiler.Done();
+ if (Program.ServerConfiguration.EnableEventLoading)
+ {
+ Profiler.Start("environment.chunks");
+ Tuple t;
+ if (ChunksToSchedule.TryTake(out t))
+ ScheduleUpdatesForChunk(t.Item1, t.Item2);
+ Profiler.Done();
+ }
Profiler.Done(MillisecondsPerTick);
long end = Time.ElapsedMilliseconds;
diff --git a/TrueCraft/Program.cs b/TrueCraft/Program.cs
index 40139b2..6f8355b 100644
--- a/TrueCraft/Program.cs
+++ b/TrueCraft/Program.cs
@@ -109,20 +109,24 @@ namespace TrueCraft
Server.ChatMessageReceived += HandleChatMessageReceived;
Server.Start(new IPEndPoint(IPAddress.Parse(ServerConfiguration.ServerAddress), ServerConfiguration.ServerPort));
Console.CancelKeyPress += HandleCancelKeyPress;
+ Server.Scheduler.ScheduleEvent("world.save", null,
+ TimeSpan.FromSeconds(ServerConfiguration.WorldSaveInterval), SaveWorlds);
while (true)
{
- Thread.Sleep(1000 * ServerConfiguration.WorldSaveInterval);
- Server.Pause();
- Server.Log(LogCategory.Notice, "Saving world...");
- foreach (var w in Server.Worlds)
- {
- w.Save();
- }
- Server.Log(LogCategory.Notice, "Done.");
- Server.Resume();
+ Thread.Yield();
}
}
+ static void SaveWorlds(IMultiplayerServer server)
+ {
+ Server.Log(LogCategory.Notice, "Saving world...");
+ foreach (var w in Server.Worlds)
+ w.Save();
+ Server.Log(LogCategory.Notice, "Done.");
+ server.Scheduler.ScheduleEvent("world.save", null,
+ TimeSpan.FromSeconds(ServerConfiguration.WorldSaveInterval), SaveWorlds);
+ }
+
static void HandleCancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
Server.Stop();
diff --git a/TrueCraft/RemoteClient.cs b/TrueCraft/RemoteClient.cs
index 5940cde..d31b5f6 100644
--- a/TrueCraft/RemoteClient.cs
+++ b/TrueCraft/RemoteClient.cs
@@ -22,6 +22,7 @@ using fNbt;
using TrueCraft.API.Logging;
using TrueCraft.API.Logic;
using TrueCraft.Exceptions;
+using TrueCraft.Profiling;
namespace TrueCraft
{
@@ -29,7 +30,7 @@ namespace TrueCraft
{
public RemoteClient(IMultiplayerServer server, IPacketReader packetReader, PacketHandler[] packetHandlers, Socket connection)
{
- LoadedChunks = new List();
+ LoadedChunks = new HashSet();
Server = server;
Inventory = new InventoryWindow(server.CraftingRepository);
InventoryWindow.WindowChange += HandleWindowChange;
@@ -145,7 +146,7 @@ namespace TrueCraft
}
internal int ChunkRadius { get; set; }
- internal IList LoadedChunks { get; set; }
+ internal HashSet LoadedChunks { get; set; }
public bool DataAvailable
{
@@ -398,8 +399,10 @@ namespace TrueCraft
if (ChunkRadius < 8) // TODO: Allow customization of this number
{
ChunkRadius++;
- UpdateChunks();
- server.Scheduler.ScheduleEvent("remote.chunks", this, TimeSpan.FromSeconds(1), ExpandChunkRadius);
+ server.Scheduler.ScheduleEvent("client.update-chunks", this,
+ TimeSpan.Zero, s => UpdateChunks());
+ server.Scheduler.ScheduleEvent("remote.chunks", this,
+ TimeSpan.FromSeconds(1), ExpandChunkRadius);
}
});
}
@@ -412,67 +415,77 @@ namespace TrueCraft
internal void UpdateChunks()
{
- var newChunks = new List();
+ var newChunks = new HashSet();
+ var toLoad = new List>();
+ Profiler.Start("client.new-chunks");
for (int x = -ChunkRadius; x < ChunkRadius; x++)
{
for (int z = -ChunkRadius; z < ChunkRadius; z++)
{
- newChunks.Add(new Coordinates2D(
+ var coords = new Coordinates2D(
((int)Entity.Position.X >> 4) + x,
- ((int)Entity.Position.Z >> 4) + z));
+ ((int)Entity.Position.Z >> 4) + z);
+ newChunks.Add(coords);
+ if (!LoadedChunks.Contains(coords))
+ toLoad.Add(new Tuple(
+ coords, World.GetChunk(coords, generate: false)));
}
}
- // Unload extraneous columns
- lock (LoadedChunks)
+ Profiler.Done();
+ Task.Factory.StartNew(() =>
{
- var currentChunks = new List(LoadedChunks);
- foreach (Coordinates2D chunk in currentChunks)
+ Profiler.Start("client.encode-chunks");
+ foreach (var tup in toLoad)
{
- if (!newChunks.Contains(chunk))
- UnloadChunk(chunk);
+ var coords = tup.Item1;
+ var chunk = tup.Item2;
+ if (chunk == null)
+ chunk = World.GetChunk(coords);
+ chunk.LastAccessed = DateTime.UtcNow;
+ LoadChunk(chunk);
}
- // Load new columns
- foreach (Coordinates2D chunk in newChunks)
- {
- if (!LoadedChunks.Contains(chunk))
- LoadChunk(chunk);
- }
- }
+ Profiler.Done();
+ });
+ Profiler.Start("client.old-chunks");
+ LoadedChunks.IntersectWith(newChunks);
+ Profiler.Done();
+ Profiler.Start("client.update-entities");
((EntityManager)Server.GetEntityManagerForWorld(World)).UpdateClientEntities(this);
+ Profiler.Done();
}
internal void UnloadAllChunks()
{
- lock (LoadedChunks)
+ while (LoadedChunks.Any())
{
- while (LoadedChunks.Any())
- {
- UnloadChunk(LoadedChunks[0]);
- }
+ UnloadChunk(LoadedChunks.First());
}
}
- internal void LoadChunk(Coordinates2D position)
+ internal void LoadChunk(IChunk chunk)
{
- var chunk = World.GetChunk(position);
- chunk.LastAccessed = DateTime.UtcNow;
QueuePacket(new ChunkPreamblePacket(chunk.Coordinates.X, chunk.Coordinates.Z));
QueuePacket(CreatePacket(chunk));
- LoadedChunks.Add(position);
- foreach (var kvp in chunk.TileEntities)
- {
- var coords = kvp.Key;
- var descriptor = new BlockDescriptor
+ Server.Scheduler.ScheduleEvent("client.finalize-chunks", this,
+ TimeSpan.Zero, server =>
{
- Coordinates = coords + new Coordinates3D(chunk.X, 0, chunk.Z),
- Metadata = chunk.GetMetadata(coords),
- ID = chunk.GetBlockID(coords),
- BlockLight = chunk.GetBlockLight(coords),
- SkyLight = chunk.GetSkyLight(coords)
- };
- var provider = Server.BlockRepository.GetBlockProvider(descriptor.ID);
- provider.TileEntityLoadedForClient(descriptor, World, kvp.Value, this);
- }
+ return;
+ LoadedChunks.Add(chunk.Coordinates);
+ foreach (var kvp in chunk.TileEntities)
+ {
+ var coords = kvp.Key;
+ var descriptor = new BlockDescriptor
+ {
+ Coordinates = coords + new Coordinates3D(chunk.X, 0, chunk.Z),
+ Metadata = chunk.GetMetadata(coords),
+ ID = chunk.GetBlockID(coords),
+ BlockLight = chunk.GetBlockLight(coords),
+ SkyLight = chunk.GetSkyLight(coords)
+ };
+ var provider = Server.BlockRepository.GetBlockProvider(descriptor.ID);
+ provider.TileEntityLoadedForClient(descriptor, World, kvp.Value, this);
+ }
+ });
}
internal void UnloadChunk(Coordinates2D position)
@@ -511,19 +524,20 @@ namespace TrueCraft
var X = chunk.Coordinates.X;
var Z = chunk.Coordinates.Z;
- const int blocksPerChunk = Chunk.Width * Chunk.Height * Chunk.Depth;
- const int bytesPerChunk = (int)(blocksPerChunk * 2.5);
-
- byte[] data = new byte[bytesPerChunk];
-
- Buffer.BlockCopy(chunk.Blocks, 0, data, 0, chunk.Blocks.Length);
- Buffer.BlockCopy(chunk.Metadata.Data, 0, data, chunk.Blocks.Length, chunk.Metadata.Data.Length);
- Buffer.BlockCopy(chunk.BlockLight.Data, 0, data, chunk.Blocks.Length + chunk.Metadata.Data.Length, chunk.BlockLight.Data.Length);
- Buffer.BlockCopy(chunk.SkyLight.Data, 0, data, chunk.Blocks.Length + chunk.Metadata.Data.Length
- + chunk.BlockLight.Data.Length, chunk.SkyLight.Data.Length);
-
- var result = ZlibStream.CompressBuffer(data);
- return new ChunkDataPacket(X * Chunk.Width, 0, Z * Chunk.Depth, Chunk.Width, Chunk.Height, Chunk.Depth, result);
+ Profiler.Start("client.encode-chunks.compress");
+ byte[] result;
+ using (var ms = new MemoryStream())
+ {
+ using (var deflate = new ZlibStream(new MemoryStream(chunk.Data),
+ CompressionMode.Compress,
+ CompressionLevel.BestSpeed))
+ deflate.CopyTo(ms);
+ result = ms.ToArray();
+ }
+ Profiler.Done();
+
+ return new ChunkDataPacket(X * Chunk.Width, 0, Z * Chunk.Depth,
+ Chunk.Width, Chunk.Height, Chunk.Depth, result);
}
public void Dispose()
diff --git a/TrueCraft/ServerConfiguration.cs b/TrueCraft/ServerConfiguration.cs
index 3df8b54..b607ce3 100644
--- a/TrueCraft/ServerConfiguration.cs
+++ b/TrueCraft/ServerConfiguration.cs
@@ -48,6 +48,8 @@ namespace TrueCraft
Query = true;
QueryPort = 25566;
EnableLighting = true;
+ EnableEventLoading = true;
+ DisabledEvents = new string[0];
}
[YamlMember(Alias = "motd")]
@@ -76,5 +78,11 @@ namespace TrueCraft
[YamlMember(Alias = "enable-lighting")]
public bool EnableLighting { get; set; }
+
+ [YamlMember(Alias = "enable-event-loading")]
+ public bool EnableEventLoading { get; set; }
+
+ [YamlMember(Alias = "disable-events")]
+ public string[] DisabledEvents { get; set; }
}
}
\ No newline at end of file
diff --git a/TrueCraft/TrueCraft.csproj b/TrueCraft/TrueCraft.csproj
index ec209eb..1883dec 100644
--- a/TrueCraft/TrueCraft.csproj
+++ b/TrueCraft/TrueCraft.csproj
@@ -34,12 +34,12 @@
-
- ..\lib\Ionic.Zip.Reduced.dll
-
..\packages\YamlDotNet.3.9.0\lib\net35\YamlDotNet.dll
+
+ ..\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll
+
diff --git a/TrueCraft/packages.config b/TrueCraft/packages.config
index f61df4d..1616a62 100644
--- a/TrueCraft/packages.config
+++ b/TrueCraft/packages.config
@@ -1,4 +1,5 @@
+
\ No newline at end of file