Many improvements to server stability+performance

Sorry for the vague commit message. There were a lot of changes. Here's
a list of most of them:

- Lighting updates are timeboxed each frame
- The next environment frame is queued sooner if the current one took
  longer (as soon as immediately)
- Issues with the physics engines and mobs using it were (mostly) fixed,
  mobs no longer freak out and get stuck on physics objects
- Mob AI/pathfinding is done more intelligently
- The player can no longer spawn in an ocean or a desert biome
- Some deadlocks in Region were fixed (more remain to be fixed)

The current performance bottlenecks are lighting (still) and propegating
initial chunk loads to blocks that need to schedule things (such as
grass blocks). I think the main culprit in the latter case is grass
blocks and water blocks. The former can be improved by adding a block
cache to World, but that'll take some custom work. This step is just
gonna be slow no matter what, we might have to split it across several
frames but it's never going to be great.

There still seems to be a deadlock somewhere in all of this mess, in the
world code. I'll find it later.
This commit is contained in:
Drew DeVault 2017-05-22 19:51:23 -04:00
parent 7f0e3338d2
commit 362c852f51
26 changed files with 300 additions and 138 deletions

View File

@ -1,4 +1,5 @@
using System; using System;
using TrueCraft.API.AI;
using TrueCraft.API.Physics; using TrueCraft.API.Physics;
namespace TrueCraft.API.Entities namespace TrueCraft.API.Entities
@ -8,6 +9,7 @@ namespace TrueCraft.API.Entities
event EventHandler PathComplete; event EventHandler PathComplete;
PathResult CurrentPath { get; set; } PathResult CurrentPath { get; set; }
bool AdvancePath(TimeSpan time, bool faceRoute = true); bool AdvancePath(TimeSpan time, bool faceRoute = true);
IMobState CurrentState { get; set; }
void Face(Vector3 target); void Face(Vector3 target);
} }
} }

View File

@ -2,11 +2,13 @@
using TrueCraft.API.Entities; using TrueCraft.API.Entities;
using System.Collections.Generic; using System.Collections.Generic;
using TrueCraft.API.Networking; using TrueCraft.API.Networking;
using TrueCraft.API.World;
namespace TrueCraft.API.Server namespace TrueCraft.API.Server
{ {
public interface IEntityManager public interface IEntityManager
{ {
IWorld World { get; }
TimeSpan TimeSinceLastUpdate { get; } TimeSpan TimeSinceLastUpdate { get; }
/// <summary> /// <summary>
/// Adds an entity to the world and assigns it an entity ID. /// Adds an entity to the world and assigns it an entity ID.

View File

@ -22,7 +22,7 @@ namespace TrueCraft.API.World
IList<BiomeCell> BiomeCells { get; } IList<BiomeCell> BiomeCells { get; }
void AddCell(BiomeCell cell); void AddCell(BiomeCell cell);
byte GetBiome(Coordinates2D location); byte GetBiome(Coordinates2D location);
byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location); byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location, bool spawn);
BiomeCell ClosestCell(Coordinates2D location); BiomeCell ClosestCell(Coordinates2D location);
double ClosestCellPoint(Coordinates2D location); double ClosestCellPoint(Coordinates2D location);
} }

View File

@ -8,6 +8,7 @@ namespace TrueCraft.API.World
{ {
public interface IBiomeProvider public interface IBiomeProvider
{ {
bool Spawn { get; }
byte ID { get; } byte ID { get; }
int Elevation { get; } int Elevation { get; }
double Temperature { get; } double Temperature { get; }

View File

@ -8,7 +8,7 @@ namespace TrueCraft.API.World
public interface IBiomeRepository public interface IBiomeRepository
{ {
IBiomeProvider GetBiome(byte id); IBiomeProvider GetBiome(byte id);
IBiomeProvider GetBiome(double temperature, double rainfall); IBiomeProvider GetBiome(double temperature, double rainfall, bool spawn);
void RegisterBiomeProvider(IBiomeProvider provider); void RegisterBiomeProvider(IBiomeProvider provider);
} }
} }

View File

@ -23,7 +23,7 @@ namespace TrueCraft.Core.Test.Logic
public Mock<IRemoteClient> User { get; set; } public Mock<IRemoteClient> User { get; set; }
public Mock<IBlockRepository> BlockRepository { get; set; } public Mock<IBlockRepository> BlockRepository { get; set; }
[TestFixtureSetUp] [OneTimeSetUp]
public void SetUp() public void SetUp()
{ {
World = new Mock<IWorld>(); World = new Mock<IWorld>();

View File

@ -14,7 +14,7 @@ namespace TrueCraft.Core.Test.World
{ {
public Chunk Chunk { get; set; } public Chunk Chunk { get; set; }
[TestFixtureSetUp] [OneTimeSetUp]
public void SetUp() public void SetUp()
{ {
var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

View File

@ -12,7 +12,7 @@ namespace TrueCraft.Core.Test.World
{ {
public Region Region { get; set; } public Region Region { get; set; }
[TestFixtureSetUp] [OneTimeSetUp]
public void SetUp() public void SetUp()
{ {
var world = new TrueCraft.Core.World.World(); var world = new TrueCraft.Core.World.World();

View File

@ -14,7 +14,7 @@ namespace TrueCraft.Core.Test.World
{ {
public TrueCraft.Core.World.World World { get; set; } public TrueCraft.Core.World.World World { get; set; }
[TestFixtureSetUp] [OneTimeSetUp]
public void SetUp() public void SetUp()
{ {
var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using TrueCraft.API; using TrueCraft.API;
using TrueCraft.API.World; using TrueCraft.API.World;
using System.Diagnostics; using System.Diagnostics;
using TrueCraft.API.Logic;
namespace TrueCraft.Core.AI namespace TrueCraft.Core.AI
{ {
@ -40,8 +41,12 @@ namespace TrueCraft.Core.AI
private bool CanOccupyVoxel(IWorld world, BoundingBox box, Coordinates3D voxel) private bool CanOccupyVoxel(IWorld world, BoundingBox box, Coordinates3D voxel)
{ {
var id = world.GetBlockID(voxel); var id = world.GetBlockID(voxel);
// TODO: Make this more sophisticated if (world.BlockRepository == null)
return id == 0; return id == 0;
var provider = world.BlockRepository.GetBlockProvider(id);
if (provider == null)
return true;
return provider.BoundingBox == null;
} }
private IEnumerable<Coordinates3D> GetNeighbors(IWorld world, BoundingBox subject, Coordinates3D current) private IEnumerable<Coordinates3D> GetNeighbors(IWorld world, BoundingBox subject, Coordinates3D current)

View File

@ -0,0 +1,28 @@
using System;
using TrueCraft.API.AI;
using TrueCraft.API.Entities;
using TrueCraft.API.Server;
namespace TrueCraft.Core.AI
{
public class IdleState : IMobState
{
private DateTime Expiry { get; set; }
private IMobState NextState { get; set; }
public IdleState(IMobState nextState, DateTime? expiry = null)
{
NextState = nextState;
if (expiry != null)
Expiry = expiry.Value;
else
Expiry = DateTime.UtcNow.AddSeconds(MathHelper.Random.Next(5, 15));
}
public void Update(IMobEntity entity, IEntityManager manager)
{
if (DateTime.UtcNow >= Expiry)
entity.CurrentState = NextState;
}
}
}

View File

@ -5,16 +5,12 @@ using TrueCraft.API.Server;
using TrueCraft.API; using TrueCraft.API;
using TrueCraft.API.World; using TrueCraft.API.World;
using System.Threading.Tasks; using System.Threading.Tasks;
using TrueCraft.API.Logic;
namespace TrueCraft.Core.AI namespace TrueCraft.Core.AI
{ {
public class WanderState : IMobState public class WanderState : IMobState
{ {
/// <summary>
/// Chance that mob will decide to move during an update when idle.
/// Chance is equal to 1 / IdleChance.
/// </summary>
public int IdleChance { get; set; }
/// <summary> /// <summary>
/// The maximum distance the mob will move in an iteration. /// The maximum distance the mob will move in an iteration.
/// </summary> /// </summary>
@ -24,7 +20,6 @@ namespace TrueCraft.Core.AI
public WanderState() public WanderState()
{ {
IdleChance = 10;
Distance = 25; Distance = 25;
PathFinder = new AStarPathFinder(); PathFinder = new AStarPathFinder();
} }
@ -33,25 +28,27 @@ namespace TrueCraft.Core.AI
{ {
var cast = entity as IEntity; var cast = entity as IEntity;
if (entity.CurrentPath != null) if (entity.CurrentPath != null)
entity.AdvancePath(manager.TimeSinceLastUpdate); {
if (entity.AdvancePath(manager.TimeSinceLastUpdate))
{
entity.CurrentState = new IdleState(new WanderState());
}
}
else else
{ {
if (MathHelper.Random.Next(IdleChance) == 0) var target = new Coordinates3D(
(int)(cast.Position.X + (MathHelper.Random.Next(Distance) - Distance / 2)),
0,
(int)(cast.Position.Z + (MathHelper.Random.Next(Distance) - Distance / 2))
);
IChunk chunk;
var adjusted = entity.World.FindBlockPosition(target, out chunk, generate: false);
target.Y = chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) + 1;
Task.Factory.StartNew(() =>
{ {
var target = new Coordinates3D( entity.CurrentPath = PathFinder.FindPath(entity.World, entity.BoundingBox,
(int)(cast.Position.X + (MathHelper.Random.Next(Distance) - Distance / 2)), (Coordinates3D)cast.Position, target);
0, });
(int)(cast.Position.Z + (MathHelper.Random.Next(Distance) - Distance / 2))
);
IChunk chunk;
var adjusted = entity.World.FindBlockPosition(target, out chunk, generate: false);
target.Y = chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) + 1;
Task.Factory.StartNew(() =>
{
entity.CurrentPath = PathFinder.FindPath(entity.World, entity.BoundingBox,
(Coordinates3D)cast.Position, target);
});
}
} }
} }
} }

View File

@ -97,11 +97,6 @@ namespace TrueCraft.Core.Entities
public IMobState CurrentState { get; set; } public IMobState CurrentState { get; set; }
public void ChangeState(IMobState state)
{
CurrentState = state;
}
public void Face(Vector3 target) public void Face(Vector3 target)
{ {
var diff = target - Position; var diff = target - Position;
@ -115,14 +110,17 @@ namespace TrueCraft.Core.Entities
{ {
// Advance along path // Advance along path
var target = (Vector3)CurrentPath.Waypoints[CurrentPath.Index]; var target = (Vector3)CurrentPath.Waypoints[CurrentPath.Index];
target += new Vector3(Size.Width / 2, 0, Size.Depth / 2); // Center it
target.Y = Position.Y; // TODO: Find better way of doing this target.Y = Position.Y; // TODO: Find better way of doing this
if (faceRoute) if (faceRoute)
Face(target); Face(target);
var lookAt = Vector3.Forwards.Transform(Matrix.CreateRotationY(MathHelper.ToRadians(-(Yaw - 180) + 180))); var lookAt = Vector3.Forwards.Transform(Matrix.CreateRotationY(MathHelper.ToRadians(-(Yaw - 180) + 180)));
lookAt *= modifier; lookAt *= modifier;
Velocity = new Vector3(lookAt.X, Velocity.Y, lookAt.Z); Velocity = new Vector3(lookAt.X, Velocity.Y, lookAt.Z);
if (Position.DistanceTo(target) < 0.1) if (Position.DistanceTo(target) < Velocity.Distance)
{ {
Position = target;
Velocity = Vector3.Zero;
CurrentPath.Index++; CurrentPath.Index++;
if (CurrentPath.Index >= CurrentPath.Waypoints.Count) if (CurrentPath.Index >= CurrentPath.Waypoints.Count)
{ {

View File

@ -300,6 +300,7 @@ namespace TrueCraft.Core.Lighting
EnqueueOperation(new BoundingBox(new Vector3(coords.X, 0, coords.Z), EnqueueOperation(new BoundingBox(new Vector3(coords.X, 0, coords.Z),
new Vector3(coords.X + Chunk.Width, chunk.MaxHeight + 2, coords.Z + Chunk.Depth)), new Vector3(coords.X + Chunk.Width, chunk.MaxHeight + 2, coords.Z + Chunk.Depth)),
true, true); true, true);
TryLightNext();
while (flush && TryLightNext()) while (flush && TryLightNext())
{ {
} }

View File

@ -41,10 +41,21 @@ namespace TrueCraft.Core.Physics
Entities.Remove(entity); Entities.Remove(entity);
} }
private void TruncateVelocity(IPhysicsEntity entity, double multiplier)
{
if (Math.Abs(entity.Velocity.X) < 0.1 * multiplier)
entity.Velocity = new Vector3(0, entity.Velocity.Y, entity.Velocity.Z);
if (Math.Abs(entity.Velocity.Y) < 0.1 * multiplier)
entity.Velocity = new Vector3(entity.Velocity.X, 0, entity.Velocity.Z);
if (Math.Abs(entity.Velocity.Z) < 0.1 * multiplier)
entity.Velocity = new Vector3(entity.Velocity.X, entity.Velocity.Y, 0);
entity.Velocity.Clamp(entity.TerminalVelocity);
}
public void Update(TimeSpan time) public void Update(TimeSpan time)
{ {
double multiplier = time.TotalSeconds; double multiplier = time.TotalSeconds;
if (multiplier == 0) if (multiplier == 0 || multiplier > 1)
return; return;
lock (EntityLock) lock (EntityLock)
{ {
@ -55,9 +66,7 @@ namespace TrueCraft.Core.Physics
{ {
entity.Velocity -= new Vector3(0, entity.AccelerationDueToGravity * multiplier, 0); entity.Velocity -= new Vector3(0, entity.AccelerationDueToGravity * multiplier, 0);
entity.Velocity *= 1 - entity.Drag * multiplier; entity.Velocity *= 1 - entity.Drag * multiplier;
if (entity.Velocity.Distance < 0.001) TruncateVelocity(entity, multiplier);
entity.Velocity = Vector3.Zero;
entity.Velocity.Clamp(entity.TerminalVelocity);
Vector3 collision, before = entity.Velocity; Vector3 collision, before = entity.Velocity;
@ -74,8 +83,8 @@ namespace TrueCraft.Core.Physics
if (TestTerrainCollisionCylinder(aabbEntity, out collision)) if (TestTerrainCollisionCylinder(aabbEntity, out collision))
aabbEntity.TerrainCollision(collision, before); aabbEntity.TerrainCollision(collision, before);
} }
entity.EndUpdate(entity.Position + entity.Velocity); entity.EndUpdate(entity.Position + entity.Velocity);
TruncateVelocity(entity, multiplier);
} }
} }
} }

View File

@ -47,7 +47,7 @@ namespace TrueCraft.Core.TerrainGen
return BiomeProviders[id]; return BiomeProviders[id];
} }
public IBiomeProvider GetBiome(double temperature, double rainfall) public IBiomeProvider GetBiome(double temperature, double rainfall, bool spawn)
{ {
List<IBiomeProvider> temperatureResults = new List<IBiomeProvider>(); List<IBiomeProvider> temperatureResults = new List<IBiomeProvider>();
foreach (var biome in BiomeProviders) foreach (var biome in BiomeProviders)
@ -79,7 +79,10 @@ namespace TrueCraft.Core.TerrainGen
foreach (var biome in BiomeProviders) foreach (var biome in BiomeProviders)
{ {
if (biome != null && biome.Rainfall.Equals(rainfall) && temperatureResults.Contains(biome)) if (biome != null
&& biome.Rainfall.Equals(rainfall)
&& temperatureResults.Contains(biome)
&& (!spawn || biome.Spawn))
{ {
return biome; return biome;
} }
@ -92,14 +95,15 @@ namespace TrueCraft.Core.TerrainGen
if (biome != null) if (biome != null)
{ {
var difference = Math.Abs(temperature - biome.Temperature); var difference = Math.Abs(temperature - biome.Temperature);
if (biomeProvider == null || difference < rainfallDifference) if ((biomeProvider == null || difference < rainfallDifference)
&& (!spawn || biome.Spawn))
{ {
biomeProvider = biome; biomeProvider = biome;
rainfallDifference = (float)difference; rainfallDifference = (float)difference;
} }
} }
} }
return biomeProvider; return biomeProvider ?? new PlainsBiome();
} }
} }
} }

View File

@ -27,6 +27,14 @@ namespace TrueCraft.Core.TerrainGen.Biomes
/// The base rainfall of the biome. /// The base rainfall of the biome.
/// </summary> /// </summary>
public abstract double Rainfall { get; } public abstract double Rainfall { get; }
public virtual bool Spawn
{
get
{
return true;
}
}
/// <summary> /// <summary>
/// The tree types generated in the biome. /// The tree types generated in the biome.

View File

@ -25,6 +25,11 @@ namespace TrueCraft.Core.TerrainGen.Biomes
{ {
get { return 0.0f; } get { return 0.0f; }
} }
public override bool Spawn
{
get { return false; }
}
public override TreeSpecies[] Trees public override TreeSpecies[] Trees
{ {

View File

@ -12,7 +12,7 @@ namespace TrueCraft.Core.TerrainGen.Decorators
{ {
public class LiquidDecorator : IChunkDecorator public class LiquidDecorator : IChunkDecorator
{ {
const int WaterLevel = 40; public static readonly int WaterLevel = 40;
public void Decorate(IWorld world, IChunk chunk, IBiomeRepository biomes) public void Decorate(IWorld world, IChunk chunk, IBiomeRepository biomes)
{ {

View File

@ -137,7 +137,9 @@ namespace TrueCraft.Core.TerrainGen
|| cellValue.Equals(1) || cellValue.Equals(1)
&& world.BiomeDiagram.ClosestCellPoint(location) >= featurePointDistance) && world.BiomeDiagram.ClosestCellPoint(location) >= featurePointDistance)
{ {
byte id = (SingleBiome) ? GenerationBiome : world.BiomeDiagram.GenerateBiome(seed, Biomes, location); byte id = (SingleBiome) ? GenerationBiome
: world.BiomeDiagram.GenerateBiome(seed, Biomes, location,
IsSpawnCoordinate(location.X, location.Z));
var cell = new BiomeCell(id, location); var cell = new BiomeCell(id, location);
world.BiomeDiagram.AddCell(cell); world.BiomeDiagram.AddCell(cell);
} }
@ -207,14 +209,23 @@ namespace TrueCraft.Core.TerrainGen
return world.BiomeDiagram.GetBiome(location); return world.BiomeDiagram.GetBiome(location);
} }
bool IsSpawnCoordinate(int x, int z)
{
return x > -1000 && x < 1000 || z > -1000 && z < 1000;
}
int GetHeight(int x, int z) int GetHeight(int x, int z)
{ {
var NoiseValue = FinalNoise.Value2D(x, z) + GroundLevel; var value = FinalNoise.Value2D(x, z) + GroundLevel;
if (NoiseValue < 0) var coords = new Coordinates2D(x, z);
NoiseValue = GroundLevel; double distance = IsSpawnCoordinate(x, z) ? coords.Distance : 1000;
if (NoiseValue > Chunk.Height) if (distance < 1000) // Avoids deep water within 1km sq of spawn
NoiseValue = Chunk.Height - 1; value += (1 - distance / 1000f) * 12;
return (int)NoiseValue; if (value < 0)
value = GroundLevel;
if (value > Chunk.Height)
value = Chunk.Height - 1;
return (int)value;
} }
} }
} }

View File

@ -355,6 +355,7 @@
<Compile Include="Windows\FurnaceWindow.cs" /> <Compile Include="Windows\FurnaceWindow.cs" />
<Compile Include="Paths.cs" /> <Compile Include="Paths.cs" />
<Compile Include="Logic\CraftingRepository.cs" /> <Compile Include="Logic\CraftingRepository.cs" />
<Compile Include="AI\IdleState.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>

View File

@ -42,11 +42,11 @@ namespace TrueCraft.Core.World
return BiomeID; return BiomeID;
} }
public byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location) public byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location, bool spawn)
{ {
double temp = Math.Abs(TempNoise.Value2D(location.X, location.Z)); double temp = Math.Abs(TempNoise.Value2D(location.X, location.Z));
double rainfall = Math.Abs(RainNoise.Value2D(location.X, location.Z)); double rainfall = Math.Abs(RainNoise.Value2D(location.X, location.Z));
byte ID = biomes.GetBiome(temp, rainfall).ID; byte ID = biomes.GetBiome(temp, rainfall, spawn).ID;
return ID; return ID;
} }

View File

@ -67,58 +67,55 @@ namespace TrueCraft.Core.World
public IChunk GetChunk(Coordinates2D position, bool generate = true) public IChunk GetChunk(Coordinates2D position, bool generate = true)
{ {
// TODO: This could use some refactoring // TODO: This could use some refactoring
lock (Chunks) if (!Chunks.ContainsKey(position))
{ {
if (!Chunks.ContainsKey(position)) if (regionFile != null)
{ {
if (regionFile != null) // Search the stream for that region
lock (streamLock)
{ {
// Search the stream for that region var chunkData = GetChunkFromTable(position);
lock (regionFile) if (chunkData == null)
{ {
var chunkData = GetChunkFromTable(position); if (World.ChunkProvider == null)
if (chunkData == null) throw new ArgumentException("The requested chunk is not loaded.", "position");
{ if (generate)
if (World.ChunkProvider == null) GenerateChunk(position);
throw new ArgumentException("The requested chunk is not loaded.", "position"); else
if (generate) return null;
GenerateChunk(position); return Chunks[position];
else }
return null; regionFile.Seek(chunkData.Item1, SeekOrigin.Begin);
return Chunks[position]; /*int length = */
} new MinecraftStream(regionFile).ReadInt32(); // TODO: Avoid making new objects here, and in the WriteInt32
regionFile.Seek(chunkData.Item1, SeekOrigin.Begin); int compressionMode = regionFile.ReadByte();
/*int length = */ switch (compressionMode)
new MinecraftStream(regionFile).ReadInt32(); // TODO: Avoid making new objects here, and in the WriteInt32 {
int compressionMode = regionFile.ReadByte(); case 1: // gzip
switch (compressionMode) throw new NotImplementedException("gzipped chunks are not implemented");
{ case 2: // zlib
case 1: // gzip var nbt = new NbtFile();
throw new NotImplementedException("gzipped chunks are not implemented"); nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null);
case 2: // zlib var chunk = Chunk.FromNbt(nbt);
var nbt = new NbtFile(); Chunks.Add(position, chunk);
nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null); World.OnChunkLoaded(new ChunkLoadedEventArgs(chunk));
var chunk = Chunk.FromNbt(nbt); break;
Chunks.Add(position, chunk); default:
World.OnChunkLoaded(new ChunkLoadedEventArgs(chunk)); throw new InvalidDataException("Invalid compression scheme provided by region file.");
break;
default:
throw new InvalidDataException("Invalid compression scheme provided by region file.");
}
} }
} }
else if (World.ChunkProvider == null)
throw new ArgumentException("The requested chunk is not loaded.", "position");
else
{
if (generate)
GenerateChunk(position);
else
return null;
}
} }
return Chunks[position]; else if (World.ChunkProvider == null)
throw new ArgumentException("The requested chunk is not loaded.", "position");
else
{
if (generate)
GenerateChunk(position);
else
return null;
}
} }
return Chunks[position];
} }
public void GenerateChunk(Coordinates2D position) public void GenerateChunk(Coordinates2D position)
@ -162,41 +159,38 @@ namespace TrueCraft.Core.World
/// </summary> /// </summary>
public void Save() public void Save()
{ {
lock (Chunks) lock (streamLock)
{ {
lock (streamLock) var toRemove = new List<Coordinates2D>();
foreach (var kvp in Chunks)
{ {
var toRemove = new List<Coordinates2D>(); var chunk = kvp.Value;
foreach (var kvp in Chunks) if (chunk.IsModified)
{ {
var chunk = kvp.Value; var data = ((Chunk)chunk).ToNbt();
if (chunk.IsModified) byte[] raw = data.SaveToBuffer(NbtCompression.ZLib);
{
var data = ((Chunk)chunk).ToNbt();
byte[] raw = data.SaveToBuffer(NbtCompression.ZLib);
var header = GetChunkFromTable(kvp.Key); var header = GetChunkFromTable(kvp.Key);
if (header == null || header.Item2 > raw.Length) if (header == null || header.Item2 > raw.Length)
header = AllocateNewChunks(kvp.Key, raw.Length); header = AllocateNewChunks(kvp.Key, 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);
regionFile.WriteByte(2); // Compressed with zlib regionFile.WriteByte(2); // Compressed with zlib
regionFile.Write(raw, 0, raw.Length); regionFile.Write(raw, 0, raw.Length);
chunk.IsModified = false; chunk.IsModified = false;
}
if ((DateTime.UtcNow - chunk.LastAccessed).TotalMinutes > 5)
toRemove.Add(kvp.Key);
}
regionFile.Flush();
// Unload idle chunks
foreach (var chunk in toRemove)
{
var c = Chunks[chunk];
Chunks.Remove(chunk);
c.Dispose();
} }
if ((DateTime.UtcNow - chunk.LastAccessed).TotalMinutes > 5)
toRemove.Add(kvp.Key);
}
regionFile.Flush();
// Unload idle chunks
foreach (var chunk in toRemove)
{
var c = Chunks[chunk];
Chunks.Remove(chunk);
c.Dispose();
} }
} }
} }

View File

@ -210,9 +210,13 @@ namespace TrueCraft.Commands
if (path == null) if (path == null)
{ {
client.SendMessage(ChatColor.Red + "It is impossible for this entity to reach you."); client.SendMessage(ChatColor.Red + "It is impossible for this entity to reach you.");
return;
} }
entity.CurrentPath = path; else
{
client.SendMessage(string.Format(ChatColor.Blue
+ "Executing path with {0} waypoints", path.Waypoints.Count()));
entity.CurrentPath = path;
}
}); });
} }
@ -222,6 +226,64 @@ namespace TrueCraft.Commands
} }
} }
public class EntityInfoCommand : Command
{
public override string Name
{
get { return "entity"; }
}
public override string Description
{
get { return "Provides information about an entity ID."; }
}
public override string[] Aliases
{
get { return new string[0]; }
}
public override void Handle(IRemoteClient client, string alias, string[] arguments)
{
if (arguments.Length != 1)
{
Help(client, alias, arguments);
return;
}
int id;
if (!int.TryParse(arguments[0], out id))
{
Help(client, alias, arguments);
return;
}
var manager = client.Server.GetEntityManagerForWorld(client.World);
var entity = manager.GetEntityByID(id);
if (entity == null)
{
client.SendMessage(ChatColor.Red + "An entity with that ID does not exist in this world.");
return;
}
client.SendMessage(string.Format(
"{0} {1}", entity.GetType().Name, entity.Position));
if (entity is MobEntity)
{
var mob = entity as MobEntity;
client.SendMessage(string.Format(
"{0}/{1} HP, {2} State, moving to to {3}",
mob.Health, mob.MaxHealth,
mob.CurrentState?.GetType().Name ?? "null",
mob.CurrentPath?.Waypoints.Last().ToString() ?? "null"));
}
}
public override void Help(IRemoteClient client, string alias, string[] arguments)
{
client.SendMessage("/entity [id]: Shows information about this entity.");
}
}
public class DestroyCommand : Command public class DestroyCommand : Command
{ {
public override string Name public override string Name

View File

@ -75,6 +75,7 @@ namespace TrueCraft
private TcpListener Listener; private TcpListener Listener;
private readonly PacketHandler[] PacketHandlers; private readonly PacketHandler[] PacketHandlers;
private IList<ILogProvider> LogProviders; private IList<ILogProvider> LogProviders;
private Stopwatch Time;
private ConcurrentBag<Tuple<IWorld, IChunk>> ChunksToSchedule; private ConcurrentBag<Tuple<IWorld, IChunk>> ChunksToSchedule;
internal object ClientLock = new object(); internal object ClientLock = new object();
@ -109,6 +110,7 @@ namespace TrueCraft
QueryProtocol = new TrueCraft.QueryProtocol(this); QueryProtocol = new TrueCraft.QueryProtocol(this);
WorldLighters = new List<WorldLighting>(); WorldLighters = new List<WorldLighting>();
ChunksToSchedule = new ConcurrentBag<Tuple<IWorld, IChunk>>(); ChunksToSchedule = new ConcurrentBag<Tuple<IWorld, IChunk>>();
Time = new Stopwatch();
AccessConfiguration = Configuration.LoadConfiguration<AccessConfiguration>("access.yaml"); AccessConfiguration = Configuration.LoadConfiguration<AccessConfiguration>("access.yaml");
@ -124,6 +126,8 @@ namespace TrueCraft
public void Start(IPEndPoint endPoint) public void Start(IPEndPoint endPoint)
{ {
ShuttingDown = false; ShuttingDown = false;
Time.Reset();
Time.Start();
Listener = new TcpListener(endPoint); Listener = new TcpListener(endPoint);
Listener.Start(); Listener.Start();
EndPoint = (IPEndPoint)Listener.LocalEndpoint; EndPoint = (IPEndPoint)Listener.LocalEndpoint;
@ -135,7 +139,7 @@ namespace TrueCraft
AcceptClient(this, args); AcceptClient(this, args);
Log(LogCategory.Notice, "Running TrueCraft server on {0}", EndPoint); Log(LogCategory.Notice, "Running TrueCraft server on {0}", EndPoint);
EnvironmentWorker.Change(MillisecondsPerTick, 0); EnvironmentWorker.Change(MillisecondsPerTick, Timeout.Infinite);
if(Program.ServerConfiguration.Query) if(Program.ServerConfiguration.Query)
QueryProtocol.Start(); QueryProtocol.Start();
} }
@ -152,6 +156,16 @@ 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);
@ -216,7 +230,7 @@ namespace TrueCraft
if (Program.ServerConfiguration.EnableLighting) if (Program.ServerConfiguration.EnableLighting)
{ {
var lighter = new WorldLighting(sender as IWorld, BlockRepository); var lighter = new WorldLighting(sender as IWorld, BlockRepository);
lighter.InitialLighting(e.Chunk); lighter.InitialLighting(e.Chunk, false);
} }
else else
{ {
@ -385,6 +399,8 @@ namespace TrueCraft
if (ShuttingDown) if (ShuttingDown)
return; return;
long start = Time.ElapsedMilliseconds;
long limit = Time.ElapsedMilliseconds + MillisecondsPerTick;
Profiler.Start("environment"); Profiler.Start("environment");
Scheduler.Update(); Scheduler.Update();
@ -396,15 +412,20 @@ namespace TrueCraft
} }
Profiler.Done(); Profiler.Done();
Profiler.Start("environment.lighting"); if (Program.ServerConfiguration.EnableLighting)
foreach (var lighter in WorldLighters)
{ {
int attempts = 500; Profiler.Start("environment.lighting");
while (attempts-- > 0 && lighter.TryLightNext()) foreach (var lighter in WorldLighters)
{ {
while (Time.ElapsedMilliseconds < limit && lighter.TryLightNext())
{
// This space intentionally left blank
}
if (Time.ElapsedMilliseconds >= limit)
Log(LogCategory.Warning, "Lighting queue is backed up");
} }
Profiler.Done();
} }
Profiler.Done();
Profiler.Start("environment.chunks"); Profiler.Start("environment.chunks");
Tuple<IWorld, IChunk> t; Tuple<IWorld, IChunk> t;
@ -413,8 +434,12 @@ namespace TrueCraft
Profiler.Done(); Profiler.Done();
Profiler.Done(MillisecondsPerTick); Profiler.Done(MillisecondsPerTick);
long end = Time.ElapsedMilliseconds;
EnvironmentWorker.Change(MillisecondsPerTick, 0); long next = MillisecondsPerTick - (end - start);
if (next < 0)
next = 0;
EnvironmentWorker.Change(next, Timeout.Infinite);
} }
public bool PlayerIsWhitelisted(string client) public bool PlayerIsWhitelisted(string client)

View File

@ -98,6 +98,11 @@ namespace TrueCraft
if (progress % 10 == 0) if (progress % 10 == 0)
Server.Log(LogCategory.Notice, "{0}% complete", progress + 10); Server.Log(LogCategory.Notice, "{0}% complete", progress + 10);
} }
Server.Log(LogCategory.Notice, "Lighting the world (this will take a moment)...");
foreach (var lighter in Server.WorldLighters)
{
while (lighter.TryLightNext()) ;
}
} }
world.Save(); world.Save();
CommandManager = new CommandManager(); CommandManager = new CommandManager();
@ -107,10 +112,14 @@ namespace TrueCraft
while (true) while (true)
{ {
Thread.Sleep(1000 * ServerConfiguration.WorldSaveInterval); Thread.Sleep(1000 * ServerConfiguration.WorldSaveInterval);
Server.Pause();
Server.Log(LogCategory.Notice, "Saving world...");
foreach (var w in Server.Worlds) foreach (var w in Server.Worlds)
{ {
w.Save(); w.Save();
} }
Server.Log(LogCategory.Notice, "Done.");
Server.Resume();
} }
} }