Add chunk handling and stub out chunk worker

The chunk worker will be converting incoming chunks to renderable
meshes.
This commit is contained in:
Drew DeVault 2015-05-13 14:20:35 -06:00
parent 4ec032892b
commit 756a309f8b
9 changed files with 249 additions and 6 deletions

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Linq;
namespace TrueCraft.Client.Linux
{
/// <summary>
/// A daemon of sorts that creates meshes from chunks.
/// Passing meshes back is NOT thread-safe.
/// </summary>
public class ChunkConverter
{
private ConcurrentQueue<ReadOnlyChunk> ChunkQueue { get; set; }
private Thread ChunkWorker { get; set; }
public ChunkConverter()
{
ChunkQueue = new ConcurrentQueue<ReadOnlyChunk>();
ChunkWorker = new Thread(new ThreadStart(DoChunks));
}
public void QueueChunk(ReadOnlyChunk chunk)
{
ChunkQueue.Enqueue(chunk);
}
public void Start()
{
ChunkWorker.Start();
}
public void Stop()
{
ChunkWorker.Abort();
}
private void DoChunks()
{
bool idle = true;
while (true)
{
ReadOnlyChunk chunk;
if (ChunkQueue.Any())
{
while (!ChunkQueue.TryDequeue(out chunk)) { }
// TODO: Create verticies from chunk
Console.WriteLine("Chunk worker received chunk at {0}, {1}", chunk.X, chunk.Z);
}
if (idle)
Thread.Sleep(100);
}
}
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace TrueCraft.Client.Linux.Events
{
public class ChunkEventArgs
{
public ReadOnlyChunk Chunk { get; set; }
public ChunkEventArgs(ReadOnlyChunk chunk)
{
Chunk = chunk;
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using TrueCraft.API.Networking;
using TrueCraft.Core.Networking.Packets;
using TrueCraft.API;
using TrueCraft.Core.World;
using MonoGame.Utilities;
using TrueCraft.Client.Linux.Events;
namespace TrueCraft.Client.Linux.Handlers
{
internal static class ChunkHandler
{
public static void HandleChunkPreamble(IPacket _packet, MultiplayerClient client)
{
var packet = (ChunkPreamblePacket)_packet;
var coords = new Coordinates2D(packet.X, packet.Z);
client.World.SetChunk(coords, new Chunk(coords));
}
public static void HandleChunkData(IPacket _packet, MultiplayerClient client)
{
var packet = (ChunkDataPacket)_packet;
// TODO: Handle chunk data packets that only include a partial chunk
// This only works with TrueCraft servers unless we fix that
var data = ZlibStream.UncompressBuffer(packet.CompressedData);
var chunk = client.World.FindChunk(new Coordinates2D(packet.X, packet.Z));
Array.Copy(data, 0, chunk.Blocks, 0, chunk.Blocks.Length);
Array.Copy(data, chunk.Blocks.Length, chunk.Metadata.Data, 0, chunk.Metadata.Data.Length);
// TODO: Light
client.OnChunkLoaded(new ChunkEventArgs(new ReadOnlyChunk(chunk)));
}
}
}

View File

@ -12,6 +12,9 @@ namespace TrueCraft.Client.Linux.Handlers
{
client.RegisterPacketHandler(new HandshakeResponsePacket().ID, HandleHandshake);
client.RegisterPacketHandler(new ChatMessagePacket().ID, HandleChatMessage);
client.RegisterPacketHandler(new ChunkPreamblePacket().ID, ChunkHandler.HandleChunkPreamble);
client.RegisterPacketHandler(new ChunkDataPacket().ID, ChunkHandler.HandleChunkData);
}
public static void HandleChatMessage(IPacket _packet, MultiplayerClient client)

View File

@ -19,6 +19,10 @@ namespace TrueCraft.Client.Linux
public class MultiplayerClient
{
public event EventHandler<ChatMessageEventArgs> ChatMessage;
public event EventHandler<ChunkEventArgs> ChunkLoaded;
public event EventHandler<ChunkEventArgs> ChunkUnloaded;
public ReadOnlyWorld World { get; private set; }
private TcpClient Client { get; set; }
private IMinecraftStream Stream { get; set; }
@ -36,6 +40,7 @@ namespace TrueCraft.Client.Linux
NetworkWorker = new Thread(new ThreadStart(DoNetwork));
PacketHandlers = new PacketHandler[0x100];
Handlers.PacketHandlers.RegisterHandlers(this);
World = new ReadOnlyWorld();
}
public void RegisterPacketHandler(byte packetId, PacketHandler handler)
@ -67,14 +72,16 @@ namespace TrueCraft.Client.Linux
while (true)
{
IPacket packet;
while (Client.Available != 0)
DateTime limit = DateTime.Now.AddMilliseconds(500);
while (Client.Available != 0 && DateTime.Now < limit)
{
idle = false;
packet = PacketReader.ReadPacket(Stream, false);
if (PacketHandlers[packet.ID] != null)
PacketHandlers[packet.ID](packet, this);
}
while (PacketQueue.Any())
limit = DateTime.Now.AddMilliseconds(500);
while (PacketQueue.Any() && DateTime.Now < limit)
{
idle = false;
while (!PacketQueue.TryDequeue(out packet)) { }
@ -90,5 +97,15 @@ namespace TrueCraft.Client.Linux
{
if (ChatMessage != null) ChatMessage(this, e);
}
protected internal void OnChunkLoaded(ChunkEventArgs e)
{
if (ChunkLoaded != null) ChunkLoaded(this, e);
}
protected internal void OnChunkUnloaded(ChunkEventArgs e)
{
if (ChunkUnloaded != null) ChunkUnloaded(this, e);
}
}
}

View File

@ -11,8 +11,7 @@ namespace TrueCraft.Client.Linux
public static void Main(string[] args)
{
var client = new MultiplayerClient();
client.Connect(ParseEndPoint(args[0])); // TODO: Connect after starting up the MonoGame instance
var game = new TrueCraftGame(client);
var game = new TrueCraftGame(client, ParseEndPoint(args[0]));
game.Run();
}

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using TrueCraft.API.World;
using TrueCraft.Core.World;
using TrueCraft.API;
namespace TrueCraft.Client.Linux
{
public class ReadOnlyWorld
{
private bool UnloadChunks { get; set; }
internal World World { get; set; }
internal ReadOnlyWorld()
{
World = new World("default");
UnloadChunks = true;
}
public short GetBlockID(Coordinates3D coordinates)
{
return World.GetBlockID(coordinates);
}
internal void SetBlockID(Coordinates3D coordinates, byte value)
{
World.SetBlockID(coordinates, value);
}
internal void SetMetadata(Coordinates3D coordinates, byte value)
{
World.SetMetadata(coordinates, value);
}
public byte GetMetadata(Coordinates3D coordinates)
{
return World.GetMetadata(coordinates);
}
public byte GetSkyLight(Coordinates3D coordinates)
{
return World.GetSkyLight(coordinates);
}
internal IChunk FindChunk(Coordinates2D coordinates)
{
return World.FindChunk(new Coordinates3D(coordinates.X, 0, coordinates.Z));
}
public ReadOnlyChunk GetChunk(Coordinates2D coordinates)
{
return new ReadOnlyChunk(World.GetChunk(coordinates));
}
internal void SetChunk(Coordinates2D coordinates, Chunk chunk)
{
World.SetChunk(coordinates, chunk);
}
internal void RemoveChunk(Coordinates2D coordinates)
{
if (UnloadChunks)
World.UnloadChunk(coordinates);
}
}
public class ReadOnlyChunk
{
internal IChunk Chunk { get; set; }
internal ReadOnlyChunk(IChunk chunk)
{
Chunk = chunk;
}
public short GetBlockId(Coordinates3D coordinates)
{
return Chunk.GetBlockID(coordinates);
}
public byte GetMetadata(Coordinates3D coordinates)
{
return Chunk.GetMetadata(coordinates);
}
public byte GetSkyLight(Coordinates3D coordinates)
{
return Chunk.GetSkyLight(coordinates);
}
public byte GetBlockLight(Coordinates3D coordinates)
{
return Chunk.GetBlockLight(coordinates);
}
public int X { get { return Chunk.X; } }
public int Z { get { return Chunk.Z; } }
public ReadOnlyCollection<byte> Blocks { get { return Array.AsReadOnly(Chunk.Blocks); } }
public ReadOnlyNibbleArray Metadata { get { return new ReadOnlyNibbleArray(Chunk.Metadata); } }
public ReadOnlyNibbleArray BlockLight { get { return new ReadOnlyNibbleArray(Chunk.BlockLight); } }
public ReadOnlyNibbleArray SkyLight { get { return new ReadOnlyNibbleArray(Chunk.SkyLight); } }
}
}

View File

@ -67,6 +67,10 @@
<Compile Include="Interface\IGameInterface.cs" />
<Compile Include="BMFont.cs" />
<Compile Include="FontRenderer.cs" />
<Compile Include="Handlers\ChunkHandler.cs" />
<Compile Include="ReadOnlyWorld.cs" />
<Compile Include="Events\ChunkEventArgs.cs" />
<Compile Include="ChunkConverter.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

View File

@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using TrueCraft.Client.Linux.Interface;
using System.IO;
using System.Net;
namespace TrueCraft.Client.Linux
{
@ -15,8 +16,10 @@ namespace TrueCraft.Client.Linux
private List<IGameInterface> Interfaces { get; set; }
private FontRenderer DejaVu { get; set; }
private SpriteBatch SpriteBatch { get; set; }
private IPEndPoint EndPoint { get; set; }
private ChunkConverter ChunkConverter { get; set; }
public TrueCraftGame(MultiplayerClient client)
public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint)
{
Window.Title = "TrueCraft";
Content.RootDirectory = "Content";
@ -25,13 +28,18 @@ namespace TrueCraft.Client.Linux
Graphics.PreferredBackBufferWidth = 1280;
Graphics.PreferredBackBufferHeight = 720;
Client = client;
EndPoint = endPoint;
ChunkConverter = new ChunkConverter();
Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
}
protected override void Initialize()
{
Interfaces = new List<IGameInterface>();
SpriteBatch = new SpriteBatch(GraphicsDevice);
base.Initialize();
base.Initialize(); // (calls LoadContent)
ChunkConverter.Start();
Client.Connect(EndPoint);
}
protected override void LoadContent()