Many more optimizations and bugfixes
Again, sorry for the huge commit. Just taking on performance issues as I see them. Changes in this: - Deadlocks in region code finally fixed - Chunk packet preparation optimized (saves ~10-20ms per packet, since we're sending these like 30 at a time that's pretty important) by storing chunks pre-encoded in memory (basically just using a single big array for IDs, metadata, and light) - Move chunk generation and compression to the thread pool - Move client chunk updates to the scheduler - Improve profiler coverage - Add knob to disable scheduling chunk events on chunk load - Make it possible to disable specific scheduled events in config.yml
This commit is contained in:
parent
362c852f51
commit
6fb8ee7ba5
@ -29,7 +29,7 @@ namespace TrueCraft.API
|
|||||||
config = new T();
|
config = new T();
|
||||||
}
|
}
|
||||||
|
|
||||||
var serializer = new Serializer();
|
var serializer = new Serializer(SerializationOptions.EmitDefaults);
|
||||||
using (var writer = new StreamWriter(configFileName))
|
using (var writer = new StreamWriter(configFileName))
|
||||||
serializer.Serialize(writer, config);
|
serializer.Serialize(writer, config);
|
||||||
|
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
using fNbt;
|
|
||||||
using fNbt.Serialization;
|
|
||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace TrueCraft.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an array of 4-bit values.
|
|
||||||
/// </summary>
|
|
||||||
public class NibbleArray : INbtSerializable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The data in the nibble array. Each byte contains
|
|
||||||
/// two nibbles, stored in big-endian.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Data { get; set; }
|
|
||||||
|
|
||||||
public NibbleArray()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new nibble array with the given number of nibbles.
|
|
||||||
/// </summary>
|
|
||||||
public NibbleArray(int length)
|
|
||||||
{
|
|
||||||
Data = new byte[length/2];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current number of nibbles in this array.
|
|
||||||
/// </summary>
|
|
||||||
[NbtIgnore]
|
|
||||||
public int Length
|
|
||||||
{
|
|
||||||
get { return Data.Length * 2; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a nibble at the given index.
|
|
||||||
/// </summary>
|
|
||||||
[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<byte> Data
|
|
||||||
{
|
|
||||||
get { return Array.AsReadOnly(NibbleArray.Data); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
82
TrueCraft.API/NibbleSlice.cs
Normal file
82
TrueCraft.API/NibbleSlice.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using fNbt;
|
||||||
|
using fNbt.Serialization;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace TrueCraft.API
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a slice of an array of 4-bit values.
|
||||||
|
/// </summary>
|
||||||
|
public class NibbleSlice : INbtSerializable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The data in the nibble array. Each byte contains
|
||||||
|
/// two nibbles, stored in big-endian.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a nibble at the given index.
|
||||||
|
/// </summary>
|
||||||
|
[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<byte> Data
|
||||||
|
{
|
||||||
|
get { return Array.AsReadOnly(NibbleArray.Data); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace TrueCraft.API.Server
|
namespace TrueCraft.API.Server
|
||||||
{
|
{
|
||||||
public interface IEventScheduler
|
public interface IEventScheduler
|
||||||
{
|
{
|
||||||
|
HashSet<string> DisabledEvents { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Schedules an event to occur some time in the future.
|
/// Schedules an event to occur some time in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
<Compile Include="World\IWorld.cs" />
|
<Compile Include="World\IWorld.cs" />
|
||||||
<Compile Include="World\IChunk.cs" />
|
<Compile Include="World\IChunk.cs" />
|
||||||
<Compile Include="World\IChunkProvider.cs" />
|
<Compile Include="World\IChunkProvider.cs" />
|
||||||
<Compile Include="NibbleArray.cs" />
|
<Compile Include="NibbleSlice.cs" />
|
||||||
<Compile Include="World\IRegion.cs" />
|
<Compile Include="World\IRegion.cs" />
|
||||||
<Compile Include="Biome.cs" />
|
<Compile Include="Biome.cs" />
|
||||||
<Compile Include="Logging\LogCategory.cs" />
|
<Compile Include="Logging\LogCategory.cs" />
|
||||||
|
@ -15,12 +15,13 @@ namespace TrueCraft.API.World
|
|||||||
int[] HeightMap { get; }
|
int[] HeightMap { get; }
|
||||||
byte[] Biomes { get; }
|
byte[] Biomes { get; }
|
||||||
DateTime LastAccessed { get; set; }
|
DateTime LastAccessed { get; set; }
|
||||||
byte[] Blocks { get; }
|
byte[] Data { get; }
|
||||||
bool TerrainPopulated { get; set; }
|
bool TerrainPopulated { get; set; }
|
||||||
Dictionary<Coordinates3D, NbtCompound> TileEntities { get; set; }
|
Dictionary<Coordinates3D, NbtCompound> TileEntities { get; set; }
|
||||||
NibbleArray Metadata { get; }
|
NibbleSlice Metadata { get; }
|
||||||
NibbleArray BlockLight { get; }
|
NibbleSlice BlockLight { get; }
|
||||||
NibbleArray SkyLight { get; }
|
NibbleSlice SkyLight { get; }
|
||||||
|
IRegion ParentRegion { get; set; }
|
||||||
int GetHeight(byte x, byte z);
|
int GetHeight(byte x, byte z);
|
||||||
void UpdateHeightMap();
|
void UpdateHeightMap();
|
||||||
byte GetBlockID(Coordinates3D coordinates);
|
byte GetBlockID(Coordinates3D coordinates);
|
||||||
|
@ -9,6 +9,10 @@ namespace TrueCraft.API.World
|
|||||||
Coordinates2D Position { get; }
|
Coordinates2D Position { get; }
|
||||||
|
|
||||||
IChunk GetChunk(Coordinates2D position, bool generate = true);
|
IChunk GetChunk(Coordinates2D position, bool generate = true);
|
||||||
|
/// <summary>
|
||||||
|
/// Marks the chunk for saving in the next Save().
|
||||||
|
/// </summary>
|
||||||
|
void DamageChunk(Coordinates2D position);
|
||||||
void UnloadChunk(Coordinates2D position);
|
void UnloadChunk(Coordinates2D position);
|
||||||
void Save(string path);
|
void Save(string path);
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,14 @@ namespace TrueCraft.Client.Handlers
|
|||||||
&& packet.Depth == Chunk.Depth) // Fast path
|
&& packet.Depth == Chunk.Depth) // Fast path
|
||||||
{
|
{
|
||||||
// Block IDs
|
// Block IDs
|
||||||
Buffer.BlockCopy(data, 0, chunk.Blocks, 0, chunk.Blocks.Length);
|
Buffer.BlockCopy(data, 0, chunk.Data, 0, chunk.Data.Length);
|
||||||
// Block metadata
|
// 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
|
// 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);
|
chunk.BlockLight.Data, 0, chunk.BlockLight.Data.Length);
|
||||||
// Sky light
|
// 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);
|
chunk.SkyLight.Data, 0, chunk.SkyLight.Data.Length);
|
||||||
}
|
}
|
||||||
else // Slow path
|
else // Slow path
|
||||||
|
@ -116,7 +116,7 @@ namespace TrueCraft.Client
|
|||||||
public int X { get { return Chunk.X; } }
|
public int X { get { return Chunk.X; } }
|
||||||
public int Z { get { return Chunk.Z; } }
|
public int Z { get { return Chunk.Z; } }
|
||||||
|
|
||||||
public ReadOnlyCollection<byte> Blocks { get { return Array.AsReadOnly(Chunk.Blocks); } }
|
public ReadOnlyCollection<byte> Blocks { get { return Array.AsReadOnly(Chunk.Data); } }
|
||||||
public ReadOnlyNibbleArray Metadata { get { return new ReadOnlyNibbleArray(Chunk.Metadata); } }
|
public ReadOnlyNibbleArray Metadata { get { return new ReadOnlyNibbleArray(Chunk.Metadata); } }
|
||||||
public ReadOnlyNibbleArray BlockLight { get { return new ReadOnlyNibbleArray(Chunk.BlockLight); } }
|
public ReadOnlyNibbleArray BlockLight { get { return new ReadOnlyNibbleArray(Chunk.BlockLight); } }
|
||||||
public ReadOnlyNibbleArray SkyLight { get { return new ReadOnlyNibbleArray(Chunk.SkyLight); } }
|
public ReadOnlyNibbleArray SkyLight { get { return new ReadOnlyNibbleArray(Chunk.SkyLight); } }
|
||||||
|
@ -12,51 +12,80 @@ namespace TrueCraft.Core.Test.World
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ChunkTest
|
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]
|
[Test]
|
||||||
public void TestGetBlockID()
|
public void TestGetBlockID()
|
||||||
{
|
{
|
||||||
Assert.AreEqual(BedrockBlock.BlockID, Chunk.GetBlockID(Coordinates3D.Zero));
|
var chunk = new Chunk();
|
||||||
Chunk.SetBlockID(Coordinates3D.Zero, 12);
|
chunk.SetBlockID(Coordinates3D.Zero, 12);
|
||||||
Assert.AreEqual(12, Chunk.GetBlockID(Coordinates3D.Zero));
|
Assert.AreEqual(12, chunk.GetBlockID(Coordinates3D.Zero));
|
||||||
Chunk.SetBlockID(Coordinates3D.Zero, BedrockBlock.BlockID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGetBlockLight()
|
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]
|
[Test]
|
||||||
public void TestGetSkyLight()
|
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]
|
[Test]
|
||||||
public void TestGetMetadata()
|
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]
|
[Test]
|
||||||
public void TestHeightMap()
|
public void TestHeightMap()
|
||||||
{
|
{
|
||||||
Chunk.UpdateHeightMap();
|
var chunk = new Chunk();
|
||||||
Assert.AreEqual(59, Chunk.GetHeight(0, 0));
|
for (int x = 0; x < Chunk.Width; ++x)
|
||||||
Assert.AreEqual(58, Chunk.GetHeight(1, 0));
|
for (int z = 0; z < Chunk.Width; ++z)
|
||||||
Chunk.SetBlockID(new Coordinates3D(1, 80, 0), 1);
|
chunk.SetBlockID(new Coordinates3D(x, 20, z), StoneBlock.BlockID);
|
||||||
Assert.AreEqual(80, Chunk.GetHeight(1, 0));
|
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++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ using TrueCraft.API;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using TrueCraft.Profiling;
|
using TrueCraft.Profiling;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace TrueCraft.Core.Lighting
|
namespace TrueCraft.Core.Lighting
|
||||||
{
|
{
|
||||||
@ -34,15 +35,14 @@ namespace TrueCraft.Core.Lighting
|
|||||||
public IBlockRepository BlockRepository { get; set; }
|
public IBlockRepository BlockRepository { get; set; }
|
||||||
public IWorld World { get; set; }
|
public IWorld World { get; set; }
|
||||||
|
|
||||||
private object _Lock = new object();
|
private ConcurrentQueue<LightingOperation> PendingOperations { get; set; }
|
||||||
private List<LightingOperation> PendingOperations { get; set; }
|
|
||||||
private Dictionary<Coordinates2D, byte[,]> HeightMaps { get; set; }
|
private Dictionary<Coordinates2D, byte[,]> HeightMaps { get; set; }
|
||||||
|
|
||||||
public WorldLighting(IWorld world, IBlockRepository blockRepository)
|
public WorldLighting(IWorld world, IBlockRepository blockRepository)
|
||||||
{
|
{
|
||||||
BlockRepository = blockRepository;
|
BlockRepository = blockRepository;
|
||||||
World = world;
|
World = world;
|
||||||
PendingOperations = new List<LightingOperation>();
|
PendingOperations = new ConcurrentQueue<LightingOperation>();
|
||||||
HeightMaps = new Dictionary<Coordinates2D, byte[,]>();
|
HeightMaps = new Dictionary<Coordinates2D, byte[,]>();
|
||||||
world.ChunkGenerated += (sender, e) => GenerateHeightMap(e.Chunk);
|
world.ChunkGenerated += (sender, e) => GenerateHeightMap(e.Chunk);
|
||||||
world.ChunkLoaded += (sender, e) => GenerateHeightMap(e.Chunk);
|
world.ChunkLoaded += (sender, e) => GenerateHeightMap(e.Chunk);
|
||||||
@ -72,7 +72,7 @@ namespace TrueCraft.Core.Lighting
|
|||||||
if (id == 0)
|
if (id == 0)
|
||||||
continue;
|
continue;
|
||||||
var provider = BlockRepository.GetBlockProvider(id);
|
var provider = BlockRepository.GetBlockProvider(id);
|
||||||
if (provider.LightOpacity != 0)
|
if (provider == null || provider.LightOpacity != 0)
|
||||||
{
|
{
|
||||||
map[x, z] = y;
|
map[x, z] = y;
|
||||||
break;
|
break;
|
||||||
@ -252,33 +252,31 @@ namespace TrueCraft.Core.Lighting
|
|||||||
public bool TryLightNext()
|
public bool TryLightNext()
|
||||||
{
|
{
|
||||||
LightingOperation op;
|
LightingOperation op;
|
||||||
lock (_Lock)
|
if (PendingOperations.Count == 0)
|
||||||
{
|
return false;
|
||||||
if (PendingOperations.Count == 0)
|
// TODO: Maybe a timeout or something?
|
||||||
return false;
|
bool dequeued = false;
|
||||||
op = PendingOperations[0];
|
while (!(dequeued = PendingOperations.TryDequeue(out op)) && PendingOperations.Count > 0) ;
|
||||||
PendingOperations.RemoveAt(0);
|
if (dequeued)
|
||||||
}
|
LightBox(op);
|
||||||
LightBox(op);
|
return dequeued;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnqueueOperation(BoundingBox box, bool skyLight, bool initial = false)
|
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
|
var op = PendingOperations[i];
|
||||||
for (int i = PendingOperations.Count - 1; i > PendingOperations.Count - 5 && i > 0; i--)
|
if (op.Box.Intersects(box))
|
||||||
{
|
{
|
||||||
var op = PendingOperations[i];
|
op.Box = new BoundingBox(Vector3.Min(op.Box.Min, box.Min), Vector3.Max(op.Box.Max, box.Max));
|
||||||
if (op.Box.Intersects(box))
|
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)
|
private void SetUpperVoxels(IChunk chunk)
|
||||||
|
@ -220,7 +220,7 @@ namespace TrueCraft.Core.TerrainGen
|
|||||||
var coords = new Coordinates2D(x, z);
|
var coords = new Coordinates2D(x, z);
|
||||||
double distance = IsSpawnCoordinate(x, z) ? coords.Distance : 1000;
|
double distance = IsSpawnCoordinate(x, z) ? coords.Distance : 1000;
|
||||||
if (distance < 1000) // Avoids deep water within 1km sq of spawn
|
if (distance < 1000) // Avoids deep water within 1km sq of spawn
|
||||||
value += (1 - distance / 1000f) * 12;
|
value += (1 - distance / 1000f) * 18;
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
value = GroundLevel;
|
value = GroundLevel;
|
||||||
if (value > Chunk.Height)
|
if (value > Chunk.Height)
|
||||||
|
@ -25,13 +25,13 @@ namespace TrueCraft.Core.World
|
|||||||
[NbtIgnore]
|
[NbtIgnore]
|
||||||
public bool IsModified { get; set; }
|
public bool IsModified { get; set; }
|
||||||
[NbtIgnore]
|
[NbtIgnore]
|
||||||
public byte[] Blocks { get; set; }
|
public byte[] Data { get; set; }
|
||||||
[NbtIgnore]
|
[NbtIgnore]
|
||||||
public NibbleArray Metadata { get; set; }
|
public NibbleSlice Metadata { get; set; }
|
||||||
[NbtIgnore]
|
[NbtIgnore]
|
||||||
public NibbleArray BlockLight { get; set; }
|
public NibbleSlice BlockLight { get; set; }
|
||||||
[NbtIgnore]
|
[NbtIgnore]
|
||||||
public NibbleArray SkyLight { get; set; }
|
public NibbleSlice SkyLight { get; set; }
|
||||||
public byte[] Biomes { get; set; }
|
public byte[] Biomes { get; set; }
|
||||||
public int[] HeightMap { get; set; }
|
public int[] HeightMap { get; set; }
|
||||||
public int MaxHeight { get; private set; }
|
public int MaxHeight { get; private set; }
|
||||||
@ -73,7 +73,7 @@ namespace TrueCraft.Core.World
|
|||||||
public bool TerrainPopulated { get; set; }
|
public bool TerrainPopulated { get; set; }
|
||||||
|
|
||||||
[NbtIgnore]
|
[NbtIgnore]
|
||||||
public Region ParentRegion { get; set; }
|
public IRegion ParentRegion { get; set; }
|
||||||
|
|
||||||
public Chunk()
|
public Chunk()
|
||||||
{
|
{
|
||||||
@ -83,23 +83,24 @@ namespace TrueCraft.Core.World
|
|||||||
TerrainPopulated = false;
|
TerrainPopulated = false;
|
||||||
LightPopulated = false;
|
LightPopulated = false;
|
||||||
MaxHeight = 0;
|
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()
|
public Chunk(Coordinates2D coordinates) : this()
|
||||||
{
|
{
|
||||||
X = coordinates.X;
|
X = coordinates.X;
|
||||||
Z = coordinates.Z;
|
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)
|
public byte GetBlockID(Coordinates3D coordinates)
|
||||||
{
|
{
|
||||||
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
||||||
return Blocks[index];
|
return Data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte GetMetadata(Coordinates3D coordinates)
|
public byte GetMetadata(Coordinates3D coordinates)
|
||||||
@ -127,8 +128,9 @@ namespace TrueCraft.Core.World
|
|||||||
public void SetBlockID(Coordinates3D coordinates, byte value)
|
public void SetBlockID(Coordinates3D coordinates, byte value)
|
||||||
{
|
{
|
||||||
IsModified = true;
|
IsModified = true;
|
||||||
|
ParentRegion.DamageChunk(Coordinates);
|
||||||
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
||||||
Blocks[index] = value;
|
Data[index] = value;
|
||||||
if (value == AirBlock.BlockID)
|
if (value == AirBlock.BlockID)
|
||||||
Metadata[index] = 0x0;
|
Metadata[index] = 0x0;
|
||||||
var oldHeight = GetHeight((byte)coordinates.X, (byte)coordinates.Z);
|
var oldHeight = GetHeight((byte)coordinates.X, (byte)coordinates.Z);
|
||||||
@ -164,6 +166,7 @@ namespace TrueCraft.Core.World
|
|||||||
public void SetMetadata(Coordinates3D coordinates, byte value)
|
public void SetMetadata(Coordinates3D coordinates, byte value)
|
||||||
{
|
{
|
||||||
IsModified = true;
|
IsModified = true;
|
||||||
|
ParentRegion.DamageChunk(Coordinates);
|
||||||
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
||||||
Metadata[index] = value;
|
Metadata[index] = value;
|
||||||
}
|
}
|
||||||
@ -175,6 +178,7 @@ namespace TrueCraft.Core.World
|
|||||||
public void SetSkyLight(Coordinates3D coordinates, byte value)
|
public void SetSkyLight(Coordinates3D coordinates, byte value)
|
||||||
{
|
{
|
||||||
IsModified = true;
|
IsModified = true;
|
||||||
|
ParentRegion.DamageChunk(Coordinates);
|
||||||
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
||||||
SkyLight[index] = value;
|
SkyLight[index] = value;
|
||||||
}
|
}
|
||||||
@ -186,6 +190,7 @@ namespace TrueCraft.Core.World
|
|||||||
public void SetBlockLight(Coordinates3D coordinates, byte value)
|
public void SetBlockLight(Coordinates3D coordinates, byte value)
|
||||||
{
|
{
|
||||||
IsModified = true;
|
IsModified = true;
|
||||||
|
ParentRegion.DamageChunk(Coordinates);
|
||||||
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
int index = coordinates.Y + (coordinates.Z * Height) + (coordinates.X * Height * Width);
|
||||||
BlockLight[index] = value;
|
BlockLight[index] = value;
|
||||||
}
|
}
|
||||||
@ -210,6 +215,7 @@ namespace TrueCraft.Core.World
|
|||||||
else
|
else
|
||||||
TileEntities[coordinates] = value;
|
TileEntities[coordinates] = value;
|
||||||
IsModified = true;
|
IsModified = true;
|
||||||
|
ParentRegion.DamageChunk(Coordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -236,7 +242,7 @@ namespace TrueCraft.Core.World
|
|||||||
for (y = Chunk.Height - 1; y >= 0; y--)
|
for (y = Chunk.Height - 1; y >= 0; y--)
|
||||||
{
|
{
|
||||||
int index = y + (z * Height) + (x * Height * Width);
|
int index = y + (z * Height) + (x * Height * Width);
|
||||||
if (Blocks[index] != 0)
|
if (Data[index] != 0)
|
||||||
{
|
{
|
||||||
SetHeight(x, z, y);
|
SetHeight(x, z, y);
|
||||||
if (y > MaxHeight)
|
if (y > MaxHeight)
|
||||||
@ -275,10 +281,10 @@ namespace TrueCraft.Core.World
|
|||||||
chunk.Add(new NbtInt("Z", Z));
|
chunk.Add(new NbtInt("Z", Z));
|
||||||
chunk.Add(new NbtByte("LightPopulated", (byte)(LightPopulated ? 1 : 0)));
|
chunk.Add(new NbtByte("LightPopulated", (byte)(LightPopulated ? 1 : 0)));
|
||||||
chunk.Add(new NbtByte("TerrainPopulated", (byte)(TerrainPopulated ? 1 : 0)));
|
chunk.Add(new NbtByte("TerrainPopulated", (byte)(TerrainPopulated ? 1 : 0)));
|
||||||
chunk.Add(new NbtByteArray("Blocks", Blocks));
|
chunk.Add(new NbtByteArray("Blocks", Data));
|
||||||
chunk.Add(new NbtByteArray("Data", Metadata.Data));
|
chunk.Add(new NbtByteArray("Data", Metadata.ToArray()));
|
||||||
chunk.Add(new NbtByteArray("SkyLight", SkyLight.Data));
|
chunk.Add(new NbtByteArray("SkyLight", SkyLight.ToArray()));
|
||||||
chunk.Add(new NbtByteArray("BlockLight", BlockLight.Data));
|
chunk.Add(new NbtByteArray("BlockLight", BlockLight.ToArray()));
|
||||||
|
|
||||||
var tiles = new NbtList("TileEntities", NbtTagType.Compound);
|
var tiles = new NbtList("TileEntities", NbtTagType.Compound);
|
||||||
foreach (var kvp in TileEntities)
|
foreach (var kvp in TileEntities)
|
||||||
@ -308,13 +314,17 @@ namespace TrueCraft.Core.World
|
|||||||
TerrainPopulated = tag["TerrainPopulated"].ByteValue > 0;
|
TerrainPopulated = tag["TerrainPopulated"].ByteValue > 0;
|
||||||
if (tag.Contains("LightPopulated"))
|
if (tag.Contains("LightPopulated"))
|
||||||
LightPopulated = tag["LightPopulated"].ByteValue > 0;
|
LightPopulated = tag["LightPopulated"].ByteValue > 0;
|
||||||
Blocks = tag["Blocks"].ByteArrayValue;
|
const int size = Width * Height * Depth;
|
||||||
Metadata = new NibbleArray();
|
const int halfSize = size / 2;
|
||||||
Metadata.Data = tag["Data"].ByteArrayValue;
|
Data = new byte[(int)(size * 2.5)];
|
||||||
BlockLight = new NibbleArray();
|
Buffer.BlockCopy(tag["Blocks"].ByteArrayValue, 0, Data, 0, size);
|
||||||
BlockLight.Data = tag["BlockLight"].ByteArrayValue;
|
Metadata = new NibbleSlice(Data, size, halfSize);
|
||||||
SkyLight = new NibbleArray();
|
BlockLight = new NibbleSlice(Data, size + halfSize, halfSize);
|
||||||
SkyLight.Data = tag["SkyLight"].ByteArrayValue;
|
SkyLight = new NibbleSlice(Data, size + halfSize * 2, halfSize);
|
||||||
|
|
||||||
|
Metadata.Deserialize(tag["Data"]);
|
||||||
|
BlockLight.Deserialize(tag["BlockLight"]);
|
||||||
|
SkyLight.Deserialize(tag["SkyLight"]);
|
||||||
|
|
||||||
if (tag.Contains("TileEntities"))
|
if (tag.Contains("TileEntities"))
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -20,10 +21,11 @@ namespace TrueCraft.Core.World
|
|||||||
// In chunks
|
// In chunks
|
||||||
public const int Width = 32, Depth = 32;
|
public const int Width = 32, Depth = 32;
|
||||||
|
|
||||||
|
private ConcurrentDictionary<Coordinates2D, IChunk> _Chunks { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently loaded chunk list.
|
/// The currently loaded chunk list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<Coordinates2D, IChunk> Chunks { get; set; }
|
public IDictionary<Coordinates2D, IChunk> Chunks { get { return _Chunks; } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The location of this region in the overworld.
|
/// The location of this region in the overworld.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -31,6 +33,7 @@ namespace TrueCraft.Core.World
|
|||||||
|
|
||||||
public World World { get; set; }
|
public World World { get; set; }
|
||||||
|
|
||||||
|
private HashSet<Coordinates2D> DirtyChunks { get; set; } = new HashSet<Coordinates2D>();
|
||||||
private Stream regionFile { get; set; }
|
private Stream regionFile { get; set; }
|
||||||
private object streamLock = new object();
|
private object streamLock = new object();
|
||||||
|
|
||||||
@ -40,7 +43,7 @@ namespace TrueCraft.Core.World
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Region(Coordinates2D position, World world)
|
public Region(Coordinates2D position, World world)
|
||||||
{
|
{
|
||||||
Chunks = new Dictionary<Coordinates2D, IChunk>();
|
_Chunks = new ConcurrentDictionary<Coordinates2D, IChunk>();
|
||||||
Position = position;
|
Position = position;
|
||||||
World = world;
|
World = world;
|
||||||
}
|
}
|
||||||
@ -51,7 +54,10 @@ namespace TrueCraft.Core.World
|
|||||||
public Region(Coordinates2D position, World world, string file) : this(position, world)
|
public Region(Coordinates2D position, World world, string file) : this(position, world)
|
||||||
{
|
{
|
||||||
if (File.Exists(file))
|
if (File.Exists(file))
|
||||||
|
{
|
||||||
regionFile = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
|
regionFile = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||||
|
regionFile.Read(HeaderCache, 0, 8192);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
regionFile = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the requested chunk from the region, or
|
/// Retrieves the requested chunk from the region, or
|
||||||
/// generates it if a world generator is provided.
|
/// generates it if a world generator is provided.
|
||||||
@ -66,25 +79,24 @@ namespace TrueCraft.Core.World
|
|||||||
/// <param name="position">The position of the requested local chunk coordinates.</param>
|
/// <param name="position">The position of the requested local chunk coordinates.</param>
|
||||||
public IChunk GetChunk(Coordinates2D position, bool generate = true)
|
public IChunk GetChunk(Coordinates2D position, bool generate = true)
|
||||||
{
|
{
|
||||||
// TODO: This could use some refactoring
|
|
||||||
if (!Chunks.ContainsKey(position))
|
if (!Chunks.ContainsKey(position))
|
||||||
{
|
{
|
||||||
if (regionFile != null)
|
if (regionFile != null)
|
||||||
{
|
{
|
||||||
// Search the stream for that region
|
// 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)
|
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);
|
regionFile.Seek(chunkData.Item1, SeekOrigin.Begin);
|
||||||
/*int length = */
|
/*int length = */
|
||||||
new MinecraftStream(regionFile).ReadInt32(); // TODO: Avoid making new objects here, and in the WriteInt32
|
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();
|
var nbt = new NbtFile();
|
||||||
nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null);
|
nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null);
|
||||||
var chunk = Chunk.FromNbt(nbt);
|
var chunk = Chunk.FromNbt(nbt);
|
||||||
|
chunk.ParentRegion = this;
|
||||||
Chunks.Add(position, chunk);
|
Chunks.Add(position, chunk);
|
||||||
World.OnChunkLoaded(new ChunkLoadedEventArgs(chunk));
|
World.OnChunkLoaded(new ChunkLoadedEventArgs(chunk));
|
||||||
break;
|
break;
|
||||||
@ -106,7 +119,7 @@ namespace TrueCraft.Core.World
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (World.ChunkProvider == null)
|
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
|
else
|
||||||
{
|
{
|
||||||
if (generate)
|
if (generate)
|
||||||
@ -124,7 +137,9 @@ namespace TrueCraft.Core.World
|
|||||||
var chunk = World.ChunkProvider.GenerateChunk(World, globalPosition);
|
var chunk = World.ChunkProvider.GenerateChunk(World, globalPosition);
|
||||||
chunk.IsModified = true;
|
chunk.IsModified = true;
|
||||||
chunk.Coordinates = globalPosition;
|
chunk.Coordinates = globalPosition;
|
||||||
Chunks.Add(position, chunk);
|
chunk.ParentRegion = this;
|
||||||
|
DirtyChunks.Add(position);
|
||||||
|
Chunks[position] = chunk;
|
||||||
World.OnChunkGenerated(new ChunkLoadedEventArgs(chunk));
|
World.OnChunkGenerated(new ChunkLoadedEventArgs(chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +151,8 @@ namespace TrueCraft.Core.World
|
|||||||
if (!Chunks.ContainsKey(position))
|
if (!Chunks.ContainsKey(position))
|
||||||
Chunks.Add(position, chunk);
|
Chunks.Add(position, chunk);
|
||||||
chunk.IsModified = true;
|
chunk.IsModified = true;
|
||||||
|
DirtyChunks.Add(position);
|
||||||
|
chunk.ParentRegion = this;
|
||||||
Chunks[position] = chunk;
|
Chunks[position] = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,17 +179,19 @@ namespace TrueCraft.Core.World
|
|||||||
lock (streamLock)
|
lock (streamLock)
|
||||||
{
|
{
|
||||||
var toRemove = new List<Coordinates2D>();
|
var toRemove = new List<Coordinates2D>();
|
||||||
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)
|
if (chunk.IsModified)
|
||||||
{
|
{
|
||||||
var data = ((Chunk)chunk).ToNbt();
|
var data = ((Chunk)chunk).ToNbt();
|
||||||
byte[] raw = data.SaveToBuffer(NbtCompression.ZLib);
|
byte[] raw = data.SaveToBuffer(NbtCompression.ZLib);
|
||||||
|
|
||||||
var header = GetChunkFromTable(kvp.Key);
|
var header = GetChunkFromTable(coords);
|
||||||
if (header == null || header.Item2 > raw.Length)
|
if (header == null || header.Item2 > raw.Length)
|
||||||
header = AllocateNewChunks(kvp.Key, raw.Length);
|
header = AllocateNewChunks(coords, raw.Length);
|
||||||
|
|
||||||
regionFile.Seek(header.Item1, SeekOrigin.Begin);
|
regionFile.Seek(header.Item1, SeekOrigin.Begin);
|
||||||
new MinecraftStream(regionFile).WriteInt32(raw.Length);
|
new MinecraftStream(regionFile).WriteInt32(raw.Length);
|
||||||
@ -182,7 +201,7 @@ namespace TrueCraft.Core.World
|
|||||||
chunk.IsModified = false;
|
chunk.IsModified = false;
|
||||||
}
|
}
|
||||||
if ((DateTime.UtcNow - chunk.LastAccessed).TotalMinutes > 5)
|
if ((DateTime.UtcNow - chunk.LastAccessed).TotalMinutes > 5)
|
||||||
toRemove.Add(kvp.Key);
|
toRemove.Add(coords);
|
||||||
}
|
}
|
||||||
regionFile.Flush();
|
regionFile.Flush();
|
||||||
// Unload idle chunks
|
// Unload idle chunks
|
||||||
@ -198,14 +217,15 @@ namespace TrueCraft.Core.World
|
|||||||
#region Stream Helpers
|
#region Stream Helpers
|
||||||
|
|
||||||
private const int ChunkSizeMultiplier = 4096;
|
private const int ChunkSizeMultiplier = 4096;
|
||||||
|
private byte[] HeaderCache = new byte[8192];
|
||||||
|
|
||||||
private Tuple<int, int> GetChunkFromTable(Coordinates2D position) // <offset, length>
|
private Tuple<int, int> GetChunkFromTable(Coordinates2D position) // <offset, length>
|
||||||
{
|
{
|
||||||
int tableOffset = ((position.X % Width) + (position.Z % Depth) * Width) * 4;
|
int tableOffset = ((position.X % Width) + (position.Z % Depth) * Width) * 4;
|
||||||
regionFile.Seek(tableOffset, SeekOrigin.Begin);
|
|
||||||
byte[] offsetBuffer = new byte[4];
|
byte[] offsetBuffer = new byte[4];
|
||||||
regionFile.Read(offsetBuffer, 0, 3);
|
Buffer.BlockCopy(HeaderCache, tableOffset, offsetBuffer, 0, 3);
|
||||||
Array.Reverse(offsetBuffer);
|
Array.Reverse(offsetBuffer);
|
||||||
int length = regionFile.ReadByte();
|
int length = HeaderCache[tableOffset + 3];
|
||||||
int offset = BitConverter.ToInt32(offsetBuffer, 0) << 4;
|
int offset = BitConverter.ToInt32(offsetBuffer, 0) << 4;
|
||||||
if (offset == 0 || length == 0)
|
if (offset == 0 || length == 0)
|
||||||
return null;
|
return null;
|
||||||
@ -215,7 +235,8 @@ namespace TrueCraft.Core.World
|
|||||||
|
|
||||||
private void CreateRegionHeader()
|
private void CreateRegionHeader()
|
||||||
{
|
{
|
||||||
regionFile.Write(new byte[8192], 0, 8192);
|
HeaderCache = new byte[8192];
|
||||||
|
regionFile.Write(HeaderCache, 0, 8192);
|
||||||
regionFile.Flush();
|
regionFile.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +258,7 @@ namespace TrueCraft.Core.World
|
|||||||
entry[0] = (byte)length;
|
entry[0] = (byte)length;
|
||||||
Array.Reverse(entry);
|
Array.Reverse(entry);
|
||||||
regionFile.Write(entry, 0, entry.Length);
|
regionFile.Write(entry, 0, entry.Length);
|
||||||
|
Buffer.BlockCopy(entry, 0, HeaderCache, tableOffset, 4);
|
||||||
|
|
||||||
return new Tuple<int, int>(dataOffset, length * ChunkSizeMultiplier);
|
return new Tuple<int, int>(dataOffset, length * ChunkSizeMultiplier);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,8 @@ namespace TrueCraft.Profiling
|
|||||||
{
|
{
|
||||||
if (Match(EnabledBuckets[i], timer.Bucket))
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -519,7 +519,7 @@ namespace TrueCraft.Commands
|
|||||||
{
|
{
|
||||||
lighter.InitialLighting(chunk, true);
|
lighter.InitialLighting(chunk, true);
|
||||||
(client as RemoteClient).UnloadChunk(chunk.Coordinates);
|
(client as RemoteClient).UnloadChunk(chunk.Coordinates);
|
||||||
(client as RemoteClient).LoadChunk(chunk.Coordinates);
|
(client as RemoteClient).LoadChunk(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,8 @@ namespace TrueCraft
|
|||||||
(int)(entity.Position.Z) >> 4 != (int)(entity.OldPosition.Z) >> 4)
|
(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);
|
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);
|
UpdateClientEntities(client);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using TrueCraft.API.Server;
|
using TrueCraft.API.Server;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using TrueCraft.API;
|
using TrueCraft.API;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using TrueCraft.Profiling;
|
using TrueCraft.Profiling;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace TrueCraft
|
namespace TrueCraft
|
||||||
{
|
{
|
||||||
@ -15,80 +17,122 @@ namespace TrueCraft
|
|||||||
private IMultiplayerServer Server { get; set; }
|
private IMultiplayerServer Server { get; set; }
|
||||||
private HashSet<IEventSubject> Subjects { get; set; }
|
private HashSet<IEventSubject> Subjects { get; set; }
|
||||||
private Stopwatch Stopwatch { get; set; }
|
private Stopwatch Stopwatch { get; set; }
|
||||||
|
private ConcurrentQueue<ScheduledEvent> ImmediateEventQueue { get; set; }
|
||||||
|
private ConcurrentQueue<ScheduledEvent> LaterEventQueue { get; set; }
|
||||||
|
private ConcurrentQueue<IEventSubject> DisposedSubjects { get; set; }
|
||||||
|
public HashSet<string> DisabledEvents { get; private set; }
|
||||||
|
|
||||||
public EventScheduler(IMultiplayerServer server)
|
public EventScheduler(IMultiplayerServer server)
|
||||||
{
|
{
|
||||||
Events = new List<ScheduledEvent>();
|
Events = new List<ScheduledEvent>();
|
||||||
|
ImmediateEventQueue = new ConcurrentQueue<ScheduledEvent>();
|
||||||
|
LaterEventQueue = new ConcurrentQueue<ScheduledEvent>();
|
||||||
|
DisposedSubjects = new ConcurrentQueue<IEventSubject>();
|
||||||
Server = server;
|
Server = server;
|
||||||
Subjects = new HashSet<IEventSubject>();
|
Subjects = new HashSet<IEventSubject>();
|
||||||
Stopwatch = new Stopwatch();
|
Stopwatch = new Stopwatch();
|
||||||
|
DisabledEvents = new HashSet<string>();
|
||||||
Stopwatch.Start();
|
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<IMultiplayerServer> action)
|
public void ScheduleEvent(string name, IEventSubject subject, TimeSpan when, Action<IMultiplayerServer> 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;
|
Subjects.Add(subject);
|
||||||
if (!Subjects.Contains(subject))
|
subject.Disposed += Subject_Disposed;
|
||||||
{
|
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
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)
|
void Subject_Disposed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// Cancel all events with this subject
|
DisposedSubjects.Enqueue((IEventSubject)sender);
|
||||||
lock (EventLock)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < Events.Count; i++)
|
|
||||||
{
|
|
||||||
if (Events[i].Subject == sender)
|
|
||||||
{
|
|
||||||
Events.RemoveAt(i);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Subjects.Remove((IEventSubject)sender);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
Profiler.Start("scheduler");
|
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;
|
ScheduledEvent e;
|
||||||
for (int i = 0; i < Events.Count; i++)
|
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];
|
// Cancel all events with this subject
|
||||||
if (e.When <= start)
|
for (int i = 0; i < Events.Count; i++)
|
||||||
{
|
{
|
||||||
Profiler.Start("scheduler." + e.Name);
|
if (Events[i].Subject == subject)
|
||||||
e.Action(Server);
|
{
|
||||||
Events.RemoveAt(i);
|
Events.RemoveAt(i);
|
||||||
i--;
|
i--;
|
||||||
Profiler.Done();
|
}
|
||||||
}
|
}
|
||||||
if (e.When > start)
|
Subjects.Remove(subject);
|
||||||
break; // List is sorted, we can exit early
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
Profiler.Done(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,8 @@ namespace TrueCraft.Handlers
|
|||||||
|
|
||||||
// Send setup packets
|
// Send setup packets
|
||||||
remoteClient.QueuePacket(new LoginResponsePacket(client.Entity.EntityID, 0, Dimension.Overworld));
|
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 WindowItemsPacket(0, remoteClient.Inventory.GetSlots()));
|
||||||
remoteClient.QueuePacket(new UpdateHealthPacket((remoteClient.Entity as PlayerEntity).Health));
|
remoteClient.QueuePacket(new UpdateHealthPacket((remoteClient.Entity as PlayerEntity).Health));
|
||||||
remoteClient.QueuePacket(new SpawnPositionPacket((int)remoteClient.Entity.Position.X,
|
remoteClient.QueuePacket(new SpawnPositionPacket((int)remoteClient.Entity.Position.X,
|
||||||
|
@ -125,6 +125,10 @@ namespace TrueCraft
|
|||||||
|
|
||||||
public void Start(IPEndPoint endPoint)
|
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;
|
ShuttingDown = false;
|
||||||
Time.Reset();
|
Time.Reset();
|
||||||
Time.Start();
|
Time.Start();
|
||||||
@ -156,16 +160,6 @@ namespace TrueCraft
|
|||||||
DisconnectClient(c);
|
DisconnectClient(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Pause()
|
|
||||||
{
|
|
||||||
EnvironmentWorker.Change(Timeout.Infinite, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Resume()
|
|
||||||
{
|
|
||||||
EnvironmentWorker.Change(0, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddWorld(IWorld world)
|
public void AddWorld(IWorld world)
|
||||||
{
|
{
|
||||||
Worlds.Add(world);
|
Worlds.Add(world);
|
||||||
@ -183,7 +177,8 @@ namespace TrueCraft
|
|||||||
|
|
||||||
void HandleChunkLoaded(object sender, ChunkLoadedEventArgs e)
|
void HandleChunkLoaded(object sender, ChunkLoadedEventArgs e)
|
||||||
{
|
{
|
||||||
ChunksToSchedule.Add(new Tuple<IWorld, IChunk>(sender as IWorld, e.Chunk));
|
if (Program.ServerConfiguration.EnableEventLoading)
|
||||||
|
ChunksToSchedule.Add(new Tuple<IWorld, IChunk>(sender as IWorld, e.Chunk));
|
||||||
if (Program.ServerConfiguration.EnableLighting)
|
if (Program.ServerConfiguration.EnableLighting)
|
||||||
{
|
{
|
||||||
var lighter = WorldLighters.SingleOrDefault(l => l.World == sender);
|
var lighter = WorldLighters.SingleOrDefault(l => l.World == sender);
|
||||||
@ -234,9 +229,9 @@ namespace TrueCraft
|
|||||||
}
|
}
|
||||||
else
|
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);
|
HandleChunkLoaded(sender, e);
|
||||||
@ -427,11 +422,14 @@ namespace TrueCraft
|
|||||||
Profiler.Done();
|
Profiler.Done();
|
||||||
}
|
}
|
||||||
|
|
||||||
Profiler.Start("environment.chunks");
|
if (Program.ServerConfiguration.EnableEventLoading)
|
||||||
Tuple<IWorld, IChunk> t;
|
{
|
||||||
if (ChunksToSchedule.TryTake(out t))
|
Profiler.Start("environment.chunks");
|
||||||
ScheduleUpdatesForChunk(t.Item1, t.Item2);
|
Tuple<IWorld, IChunk> t;
|
||||||
Profiler.Done();
|
if (ChunksToSchedule.TryTake(out t))
|
||||||
|
ScheduleUpdatesForChunk(t.Item1, t.Item2);
|
||||||
|
Profiler.Done();
|
||||||
|
}
|
||||||
|
|
||||||
Profiler.Done(MillisecondsPerTick);
|
Profiler.Done(MillisecondsPerTick);
|
||||||
long end = Time.ElapsedMilliseconds;
|
long end = Time.ElapsedMilliseconds;
|
||||||
|
@ -109,20 +109,24 @@ namespace TrueCraft
|
|||||||
Server.ChatMessageReceived += HandleChatMessageReceived;
|
Server.ChatMessageReceived += HandleChatMessageReceived;
|
||||||
Server.Start(new IPEndPoint(IPAddress.Parse(ServerConfiguration.ServerAddress), ServerConfiguration.ServerPort));
|
Server.Start(new IPEndPoint(IPAddress.Parse(ServerConfiguration.ServerAddress), ServerConfiguration.ServerPort));
|
||||||
Console.CancelKeyPress += HandleCancelKeyPress;
|
Console.CancelKeyPress += HandleCancelKeyPress;
|
||||||
|
Server.Scheduler.ScheduleEvent("world.save", null,
|
||||||
|
TimeSpan.FromSeconds(ServerConfiguration.WorldSaveInterval), SaveWorlds);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Thread.Sleep(1000 * ServerConfiguration.WorldSaveInterval);
|
Thread.Yield();
|
||||||
Server.Pause();
|
|
||||||
Server.Log(LogCategory.Notice, "Saving world...");
|
|
||||||
foreach (var w in Server.Worlds)
|
|
||||||
{
|
|
||||||
w.Save();
|
|
||||||
}
|
|
||||||
Server.Log(LogCategory.Notice, "Done.");
|
|
||||||
Server.Resume();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
static void HandleCancelKeyPress(object sender, ConsoleCancelEventArgs e)
|
||||||
{
|
{
|
||||||
Server.Stop();
|
Server.Stop();
|
||||||
|
@ -22,6 +22,7 @@ using fNbt;
|
|||||||
using TrueCraft.API.Logging;
|
using TrueCraft.API.Logging;
|
||||||
using TrueCraft.API.Logic;
|
using TrueCraft.API.Logic;
|
||||||
using TrueCraft.Exceptions;
|
using TrueCraft.Exceptions;
|
||||||
|
using TrueCraft.Profiling;
|
||||||
|
|
||||||
namespace TrueCraft
|
namespace TrueCraft
|
||||||
{
|
{
|
||||||
@ -29,7 +30,7 @@ namespace TrueCraft
|
|||||||
{
|
{
|
||||||
public RemoteClient(IMultiplayerServer server, IPacketReader packetReader, PacketHandler[] packetHandlers, Socket connection)
|
public RemoteClient(IMultiplayerServer server, IPacketReader packetReader, PacketHandler[] packetHandlers, Socket connection)
|
||||||
{
|
{
|
||||||
LoadedChunks = new List<Coordinates2D>();
|
LoadedChunks = new HashSet<Coordinates2D>();
|
||||||
Server = server;
|
Server = server;
|
||||||
Inventory = new InventoryWindow(server.CraftingRepository);
|
Inventory = new InventoryWindow(server.CraftingRepository);
|
||||||
InventoryWindow.WindowChange += HandleWindowChange;
|
InventoryWindow.WindowChange += HandleWindowChange;
|
||||||
@ -145,7 +146,7 @@ namespace TrueCraft
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal int ChunkRadius { get; set; }
|
internal int ChunkRadius { get; set; }
|
||||||
internal IList<Coordinates2D> LoadedChunks { get; set; }
|
internal HashSet<Coordinates2D> LoadedChunks { get; set; }
|
||||||
|
|
||||||
public bool DataAvailable
|
public bool DataAvailable
|
||||||
{
|
{
|
||||||
@ -398,8 +399,10 @@ namespace TrueCraft
|
|||||||
if (ChunkRadius < 8) // TODO: Allow customization of this number
|
if (ChunkRadius < 8) // TODO: Allow customization of this number
|
||||||
{
|
{
|
||||||
ChunkRadius++;
|
ChunkRadius++;
|
||||||
UpdateChunks();
|
server.Scheduler.ScheduleEvent("client.update-chunks", this,
|
||||||
server.Scheduler.ScheduleEvent("remote.chunks", this, TimeSpan.FromSeconds(1), ExpandChunkRadius);
|
TimeSpan.Zero, s => UpdateChunks());
|
||||||
|
server.Scheduler.ScheduleEvent("remote.chunks", this,
|
||||||
|
TimeSpan.FromSeconds(1), ExpandChunkRadius);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -412,67 +415,77 @@ namespace TrueCraft
|
|||||||
|
|
||||||
internal void UpdateChunks()
|
internal void UpdateChunks()
|
||||||
{
|
{
|
||||||
var newChunks = new List<Coordinates2D>();
|
var newChunks = new HashSet<Coordinates2D>();
|
||||||
|
var toLoad = new List<Tuple<Coordinates2D, IChunk>>();
|
||||||
|
Profiler.Start("client.new-chunks");
|
||||||
for (int x = -ChunkRadius; x < ChunkRadius; x++)
|
for (int x = -ChunkRadius; x < ChunkRadius; x++)
|
||||||
{
|
{
|
||||||
for (int z = -ChunkRadius; z < ChunkRadius; z++)
|
for (int z = -ChunkRadius; z < ChunkRadius; z++)
|
||||||
{
|
{
|
||||||
newChunks.Add(new Coordinates2D(
|
var coords = new Coordinates2D(
|
||||||
((int)Entity.Position.X >> 4) + x,
|
((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<Coordinates2D, IChunk>(
|
||||||
|
coords, World.GetChunk(coords, generate: false)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unload extraneous columns
|
Profiler.Done();
|
||||||
lock (LoadedChunks)
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
var currentChunks = new List<Coordinates2D>(LoadedChunks);
|
Profiler.Start("client.encode-chunks");
|
||||||
foreach (Coordinates2D chunk in currentChunks)
|
foreach (var tup in toLoad)
|
||||||
{
|
{
|
||||||
if (!newChunks.Contains(chunk))
|
var coords = tup.Item1;
|
||||||
UnloadChunk(chunk);
|
var chunk = tup.Item2;
|
||||||
|
if (chunk == null)
|
||||||
|
chunk = World.GetChunk(coords);
|
||||||
|
chunk.LastAccessed = DateTime.UtcNow;
|
||||||
|
LoadChunk(chunk);
|
||||||
}
|
}
|
||||||
// Load new columns
|
Profiler.Done();
|
||||||
foreach (Coordinates2D chunk in newChunks)
|
});
|
||||||
{
|
Profiler.Start("client.old-chunks");
|
||||||
if (!LoadedChunks.Contains(chunk))
|
LoadedChunks.IntersectWith(newChunks);
|
||||||
LoadChunk(chunk);
|
Profiler.Done();
|
||||||
}
|
Profiler.Start("client.update-entities");
|
||||||
}
|
|
||||||
((EntityManager)Server.GetEntityManagerForWorld(World)).UpdateClientEntities(this);
|
((EntityManager)Server.GetEntityManagerForWorld(World)).UpdateClientEntities(this);
|
||||||
|
Profiler.Done();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void UnloadAllChunks()
|
internal void UnloadAllChunks()
|
||||||
{
|
{
|
||||||
lock (LoadedChunks)
|
while (LoadedChunks.Any())
|
||||||
{
|
{
|
||||||
while (LoadedChunks.Any())
|
UnloadChunk(LoadedChunks.First());
|
||||||
{
|
|
||||||
UnloadChunk(LoadedChunks[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(new ChunkPreamblePacket(chunk.Coordinates.X, chunk.Coordinates.Z));
|
||||||
QueuePacket(CreatePacket(chunk));
|
QueuePacket(CreatePacket(chunk));
|
||||||
LoadedChunks.Add(position);
|
Server.Scheduler.ScheduleEvent("client.finalize-chunks", this,
|
||||||
foreach (var kvp in chunk.TileEntities)
|
TimeSpan.Zero, server =>
|
||||||
{
|
|
||||||
var coords = kvp.Key;
|
|
||||||
var descriptor = new BlockDescriptor
|
|
||||||
{
|
{
|
||||||
Coordinates = coords + new Coordinates3D(chunk.X, 0, chunk.Z),
|
return;
|
||||||
Metadata = chunk.GetMetadata(coords),
|
LoadedChunks.Add(chunk.Coordinates);
|
||||||
ID = chunk.GetBlockID(coords),
|
foreach (var kvp in chunk.TileEntities)
|
||||||
BlockLight = chunk.GetBlockLight(coords),
|
{
|
||||||
SkyLight = chunk.GetSkyLight(coords)
|
var coords = kvp.Key;
|
||||||
};
|
var descriptor = new BlockDescriptor
|
||||||
var provider = Server.BlockRepository.GetBlockProvider(descriptor.ID);
|
{
|
||||||
provider.TileEntityLoadedForClient(descriptor, World, kvp.Value, this);
|
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)
|
internal void UnloadChunk(Coordinates2D position)
|
||||||
@ -511,19 +524,20 @@ namespace TrueCraft
|
|||||||
var X = chunk.Coordinates.X;
|
var X = chunk.Coordinates.X;
|
||||||
var Z = chunk.Coordinates.Z;
|
var Z = chunk.Coordinates.Z;
|
||||||
|
|
||||||
const int blocksPerChunk = Chunk.Width * Chunk.Height * Chunk.Depth;
|
Profiler.Start("client.encode-chunks.compress");
|
||||||
const int bytesPerChunk = (int)(blocksPerChunk * 2.5);
|
byte[] result;
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
byte[] data = new byte[bytesPerChunk];
|
{
|
||||||
|
using (var deflate = new ZlibStream(new MemoryStream(chunk.Data),
|
||||||
Buffer.BlockCopy(chunk.Blocks, 0, data, 0, chunk.Blocks.Length);
|
CompressionMode.Compress,
|
||||||
Buffer.BlockCopy(chunk.Metadata.Data, 0, data, chunk.Blocks.Length, chunk.Metadata.Data.Length);
|
CompressionLevel.BestSpeed))
|
||||||
Buffer.BlockCopy(chunk.BlockLight.Data, 0, data, chunk.Blocks.Length + chunk.Metadata.Data.Length, chunk.BlockLight.Data.Length);
|
deflate.CopyTo(ms);
|
||||||
Buffer.BlockCopy(chunk.SkyLight.Data, 0, data, chunk.Blocks.Length + chunk.Metadata.Data.Length
|
result = ms.ToArray();
|
||||||
+ chunk.BlockLight.Data.Length, chunk.SkyLight.Data.Length);
|
}
|
||||||
|
Profiler.Done();
|
||||||
var result = ZlibStream.CompressBuffer(data);
|
|
||||||
return new ChunkDataPacket(X * Chunk.Width, 0, Z * Chunk.Depth, Chunk.Width, Chunk.Height, Chunk.Depth, result);
|
return new ChunkDataPacket(X * Chunk.Width, 0, Z * Chunk.Depth,
|
||||||
|
Chunk.Width, Chunk.Height, Chunk.Depth, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -48,6 +48,8 @@ namespace TrueCraft
|
|||||||
Query = true;
|
Query = true;
|
||||||
QueryPort = 25566;
|
QueryPort = 25566;
|
||||||
EnableLighting = true;
|
EnableLighting = true;
|
||||||
|
EnableEventLoading = true;
|
||||||
|
DisabledEvents = new string[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
[YamlMember(Alias = "motd")]
|
[YamlMember(Alias = "motd")]
|
||||||
@ -76,5 +78,11 @@ namespace TrueCraft
|
|||||||
|
|
||||||
[YamlMember(Alias = "enable-lighting")]
|
[YamlMember(Alias = "enable-lighting")]
|
||||||
public bool EnableLighting { get; set; }
|
public bool EnableLighting { get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Alias = "enable-event-loading")]
|
||||||
|
public bool EnableEventLoading { get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Alias = "disable-events")]
|
||||||
|
public string[] DisabledEvents { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,12 +34,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="Ionic.Zip.Reduced">
|
|
||||||
<HintPath>..\lib\Ionic.Zip.Reduced.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="YamlDotNet">
|
<Reference Include="YamlDotNet">
|
||||||
<HintPath>..\packages\YamlDotNet.3.9.0\lib\net35\YamlDotNet.dll</HintPath>
|
<HintPath>..\packages\YamlDotNet.3.9.0\lib\net35\YamlDotNet.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="DotNetZip">
|
||||||
|
<HintPath>..\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="AccessConfiguration.cs" />
|
<Compile Include="AccessConfiguration.cs" />
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
|
<package id="DotNetZip" version="1.10.1" targetFramework="net45" />
|
||||||
<package id="YamlDotNet" version="3.9.0" targetFramework="net45" />
|
<package id="YamlDotNet" version="3.9.0" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
Reference in New Issue
Block a user