Initial work on improving rendering; separated threading from mesh generation
This commit is contained in:
parent
f5f0958bf1
commit
341093384d
@ -30,6 +30,23 @@ namespace TrueCraft.Client.Rendering
|
|||||||
SetSubmesh(0, indices);
|
SetSubmesh(0, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chunk"></param>
|
||||||
|
/// <param name="device"></param>
|
||||||
|
/// <param name="vertices"></param>
|
||||||
|
/// <param name="opaqueIndices"></param>
|
||||||
|
/// <param name="transparentIndices"></param>
|
||||||
|
public ChunkMesh(ReadOnlyChunk chunk, GraphicsDevice device, VertexPositionNormalTexture[] vertices, int[] opaqueIndices, int[] transparentIndices)
|
||||||
|
: base(device, 2, true)
|
||||||
|
{
|
||||||
|
Chunk = chunk;
|
||||||
|
Vertices = vertices;
|
||||||
|
SetSubmesh(0, opaqueIndices);
|
||||||
|
SetSubmesh(1, transparentIndices);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -19,7 +19,7 @@ namespace TrueCraft.Client.Rendering
|
|||||||
/// A daemon of sorts that creates meshes from chunks.
|
/// A daemon of sorts that creates meshes from chunks.
|
||||||
/// Passing meshes back is NOT thread-safe.
|
/// Passing meshes back is NOT thread-safe.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChunkRenderer
|
public class ChunkRenderer : Renderer<ReadOnlyChunk>
|
||||||
{
|
{
|
||||||
public class ChunkSorter : Comparer<Mesh>
|
public class ChunkSorter : Comparer<Mesh>
|
||||||
{
|
{
|
||||||
@ -39,85 +39,16 @@ namespace TrueCraft.Client.Rendering
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MeshGeneratedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public bool Transparent { get; set; }
|
|
||||||
public ChunkMesh Mesh { get; set; }
|
|
||||||
|
|
||||||
public MeshGeneratedEventArgs(ChunkMesh mesh, bool transparent)
|
|
||||||
{
|
|
||||||
Transparent = transparent;
|
|
||||||
Mesh = mesh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler<MeshGeneratedEventArgs> MeshGenerated;
|
|
||||||
|
|
||||||
private ConcurrentQueue<ReadOnlyChunk> ChunkQueue { get; set; }
|
|
||||||
private GraphicsDevice GraphicsDevice { get; set; }
|
private GraphicsDevice GraphicsDevice { get; set; }
|
||||||
private IBlockRepository BlockRepository { get; set; }
|
private IBlockRepository BlockRepository { get; set; }
|
||||||
private List<Tuple<Thread, CancellationTokenSource>> RenderThreads { get; set; }
|
|
||||||
|
|
||||||
public ChunkRenderer(GraphicsDevice graphics, IBlockRepository blockRepository)
|
public ChunkRenderer(GraphicsDevice graphics, IBlockRepository blockRepository)
|
||||||
|
: base()
|
||||||
{
|
{
|
||||||
ChunkQueue = new ConcurrentQueue<ReadOnlyChunk>();
|
|
||||||
RenderThreads = new List<Tuple<Thread, CancellationTokenSource>>();
|
|
||||||
var threads = Environment.ProcessorCount - 2;
|
|
||||||
if (threads < 1)
|
|
||||||
threads = 1;
|
|
||||||
for (int i = 0; i < threads; i++)
|
|
||||||
{
|
|
||||||
var token = new CancellationTokenSource();
|
|
||||||
var worker = new Thread(() => DoChunks(token));
|
|
||||||
worker.IsBackground = true;
|
|
||||||
RenderThreads.Add(new Tuple<Thread, CancellationTokenSource>(worker, token));
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockRepository = blockRepository;
|
BlockRepository = blockRepository;
|
||||||
GraphicsDevice = graphics;
|
GraphicsDevice = graphics;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueChunk(ReadOnlyChunk chunk)
|
|
||||||
{
|
|
||||||
ChunkQueue.Enqueue(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void QueueHighPriorityChunk(ReadOnlyChunk chunk)
|
|
||||||
{
|
|
||||||
// TODO: Overwrite existing chunks
|
|
||||||
Task.Factory.StartNew(() =>
|
|
||||||
{
|
|
||||||
ProcessChunk(chunk, new RenderState());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
RenderThreads.ForEach(t => t.Item1.Start());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
RenderThreads.ForEach(t => t.Item2.Cancel());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoChunks(CancellationTokenSource token)
|
|
||||||
{
|
|
||||||
var state = new RenderState();
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (token.Token.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ReadOnlyChunk chunk;
|
|
||||||
if (ChunkQueue.TryDequeue(out chunk))
|
|
||||||
{
|
|
||||||
ProcessChunk(chunk, state);
|
|
||||||
}
|
|
||||||
Thread.Sleep(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Coordinates3D[] AdjacentCoordinates =
|
private static readonly Coordinates3D[] AdjacentCoordinates =
|
||||||
{
|
{
|
||||||
Coordinates3D.Up,
|
Coordinates3D.Up,
|
||||||
@ -128,20 +59,30 @@ namespace TrueCraft.Client.Rendering
|
|||||||
Coordinates3D.West
|
Coordinates3D.West
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected override bool TryRender(ReadOnlyChunk item, out Mesh result)
|
||||||
|
{
|
||||||
|
var state = new RenderState();
|
||||||
|
ProcessChunk(item, state);
|
||||||
|
|
||||||
|
result = new ChunkMesh(item, GraphicsDevice, state.Verticies.ToArray(),
|
||||||
|
state.OpaqueIndicies.ToArray(), state.TransparentIndicies.ToArray());
|
||||||
|
|
||||||
|
return (result != null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private class RenderState
|
private class RenderState
|
||||||
{
|
{
|
||||||
public readonly List<VertexPositionNormalTexture> OpaqueVerticies = new List<VertexPositionNormalTexture>();
|
public readonly List<VertexPositionNormalTexture> Verticies = new List<VertexPositionNormalTexture>();
|
||||||
public readonly List<int> OpaqueIndicies = new List<int>();
|
public readonly List<int> OpaqueIndicies = new List<int>();
|
||||||
public readonly List<VertexPositionNormalTexture> TransparentVerticies = new List<VertexPositionNormalTexture>();
|
|
||||||
public readonly List<int> TransparentIndicies = new List<int>();
|
public readonly List<int> TransparentIndicies = new List<int>();
|
||||||
public readonly HashSet<Coordinates3D> DrawableCoordinates = new HashSet<Coordinates3D>();
|
public readonly HashSet<Coordinates3D> DrawableCoordinates = new HashSet<Coordinates3D>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessChunk(ReadOnlyChunk chunk, RenderState state)
|
private void ProcessChunk(ReadOnlyChunk chunk, RenderState state)
|
||||||
{
|
{
|
||||||
state.OpaqueVerticies.Clear();
|
state.Verticies.Clear();
|
||||||
state.OpaqueIndicies.Clear();
|
state.OpaqueIndicies.Clear();
|
||||||
state.TransparentVerticies.Clear();
|
|
||||||
state.TransparentIndicies.Clear();
|
state.TransparentIndicies.Clear();
|
||||||
state.DrawableCoordinates.Clear();
|
state.DrawableCoordinates.Clear();
|
||||||
|
|
||||||
@ -198,8 +139,8 @@ namespace TrueCraft.Client.Rendering
|
|||||||
int[] i;
|
int[] i;
|
||||||
var v = BlockRenderer.RenderBlock(provider, descriptor,
|
var v = BlockRenderer.RenderBlock(provider, descriptor,
|
||||||
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
|
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
|
||||||
state.OpaqueVerticies.Count, out i);
|
state.Verticies.Count, out i);
|
||||||
state.OpaqueVerticies.AddRange(v);
|
state.Verticies.AddRange(v);
|
||||||
state.OpaqueIndicies.AddRange(i);
|
state.OpaqueIndicies.AddRange(i);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -207,18 +148,11 @@ namespace TrueCraft.Client.Rendering
|
|||||||
int[] i;
|
int[] i;
|
||||||
var v = BlockRenderer.RenderBlock(provider, descriptor,
|
var v = BlockRenderer.RenderBlock(provider, descriptor,
|
||||||
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
|
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
|
||||||
state.TransparentVerticies.Count, out i);
|
state.Verticies.Count, out i);
|
||||||
state.TransparentVerticies.AddRange(v);
|
state.Verticies.AddRange(v);
|
||||||
state.TransparentIndicies.AddRange(i);
|
state.TransparentIndicies.AddRange(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var opaque = new ChunkMesh(chunk, GraphicsDevice, state.OpaqueVerticies.ToArray(), state.OpaqueIndicies.ToArray());
|
|
||||||
var transparent = new ChunkMesh(chunk, GraphicsDevice, state.TransparentVerticies.ToArray(), state.TransparentIndicies.ToArray());
|
|
||||||
if (MeshGenerated != null)
|
|
||||||
{
|
|
||||||
MeshGenerated(this, new MeshGeneratedEventArgs(opaque, false));
|
|
||||||
MeshGenerated(this, new MeshGeneratedEventArgs(transparent, true));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
193
TrueCraft.Client/Rendering/Renderer.cs
Normal file
193
TrueCraft.Client/Rendering/Renderer.cs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace TrueCraft.Client.Rendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract base class for renderers of meshes.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The object to render into a mesh.</typeparam>
|
||||||
|
public abstract class Renderer<T> : IDisposable
|
||||||
|
{
|
||||||
|
private readonly object _syncLock =
|
||||||
|
new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<RendererEventArgs<T>> MeshCompleted;
|
||||||
|
|
||||||
|
private volatile bool _isRunning;
|
||||||
|
private Thread _rendererThread;
|
||||||
|
private ConcurrentQueue<T> _items, _priorityItems;
|
||||||
|
private volatile bool _isDisposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this renderer is running.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRunning
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
return _isRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this renderer is disposed of.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDisposed
|
||||||
|
{
|
||||||
|
get { return _isDisposed; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
protected Renderer()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_isRunning = false;
|
||||||
|
_rendererThread = new Thread(DoRendering);
|
||||||
|
_items = new ConcurrentQueue<T>(); _priorityItems = new ConcurrentQueue<T>();
|
||||||
|
_isDisposed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts this renderer.
|
||||||
|
/// </summary>
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
|
||||||
|
if (_isRunning) return;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_isRunning = true;
|
||||||
|
_rendererThread.Start(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
private void DoRendering(object obj)
|
||||||
|
{
|
||||||
|
while (_isRunning)
|
||||||
|
{
|
||||||
|
var item = default(T);
|
||||||
|
var result = default(Mesh);
|
||||||
|
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (_priorityItems.TryDequeue(out item) && TryRender(item, out result))
|
||||||
|
{
|
||||||
|
var args = new RendererEventArgs<T>(item, result, true);
|
||||||
|
if (MeshCompleted != null)
|
||||||
|
MeshCompleted(this, args);
|
||||||
|
}
|
||||||
|
else if (_items.TryDequeue(out item) && TryRender(item, out result))
|
||||||
|
{
|
||||||
|
var args = new RendererEventArgs<T>(item, result, false);
|
||||||
|
if (MeshCompleted != null)
|
||||||
|
MeshCompleted(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item == null) // We don't have any work, so sleep for a bit.
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="result"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected abstract bool TryRender(T item, out Mesh result);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops this renderer.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
|
||||||
|
if (!_isRunning) return;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_isRunning = false;
|
||||||
|
_rendererThread.Join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueues an item to this renderer for rendering.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="hasPriority"></param>
|
||||||
|
public void Enqueue(T item, bool hasPriority = false)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
|
||||||
|
if (!_isRunning) return;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (hasPriority)
|
||||||
|
_priorityItems.Enqueue(item);
|
||||||
|
else
|
||||||
|
_items.Enqueue(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of this renderer.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of this renderer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"></param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_rendererThread = null;
|
||||||
|
_items = null; _priorityItems = null;
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new NotSupportedException(); // We should 'encourage' developers to dispose of renderers properly.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes this renderer.
|
||||||
|
/// </summary>
|
||||||
|
~Renderer()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
TrueCraft.Client/Rendering/RendererEventArgs.cs
Normal file
39
TrueCraft.Client/Rendering/RendererEventArgs.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TrueCraft.Client.Rendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public sealed class RendererEventArgs<T> : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public T Item { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public Mesh Result { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPriority { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="result"></param>
|
||||||
|
/// <param name="isPriority"></param>
|
||||||
|
public RendererEventArgs(T item, Mesh result, bool isPriority)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
Result = result;
|
||||||
|
IsPriority = isPriority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -105,6 +105,8 @@
|
|||||||
<Compile Include="Rendering\Font.cs" />
|
<Compile Include="Rendering\Font.cs" />
|
||||||
<Compile Include="Rendering\FontRenderer.cs" />
|
<Compile Include="Rendering\FontRenderer.cs" />
|
||||||
<Compile Include="Rendering\FontStyle.cs" />
|
<Compile Include="Rendering\FontStyle.cs" />
|
||||||
|
<Compile Include="Rendering\Renderer.cs" />
|
||||||
|
<Compile Include="Rendering\RendererEventArgs.cs" />
|
||||||
<Compile Include="Rendering\TextureMapper.cs" />
|
<Compile Include="Rendering\TextureMapper.cs" />
|
||||||
<Compile Include="TrueCraftGame.cs" />
|
<Compile Include="TrueCraftGame.cs" />
|
||||||
<Compile Include="MultiplayerClient.cs" />
|
<Compile Include="MultiplayerClient.cs" />
|
||||||
|
@ -28,11 +28,9 @@ namespace TrueCraft.Client
|
|||||||
private IPEndPoint EndPoint { get; set; }
|
private IPEndPoint EndPoint { get; set; }
|
||||||
private ChunkRenderer ChunkConverter { get; set; }
|
private ChunkRenderer ChunkConverter { get; set; }
|
||||||
private DateTime NextPhysicsUpdate { get; set; }
|
private DateTime NextPhysicsUpdate { get; set; }
|
||||||
private List<ChunkMesh> ChunkMeshes { get; set; }
|
private List<Mesh> ChunkMeshes { get; set; }
|
||||||
private ConcurrentBag<Action> PendingMainThreadActions { get; set; }
|
private ConcurrentBag<Action> PendingMainThreadActions { get; set; }
|
||||||
private ConcurrentBag<ChunkMesh> IncomingChunks { get; set; }
|
private ConcurrentBag<Mesh> IncomingChunks { get; set; }
|
||||||
private ConcurrentBag<ChunkMesh> IncomingTransparentChunks { get; set; }
|
|
||||||
private List<ChunkMesh> TransparentChunkMeshes { get; set; }
|
|
||||||
public ChatInterface ChatInterface { get; set; }
|
public ChatInterface ChatInterface { get; set; }
|
||||||
private RenderTarget2D RenderTarget;
|
private RenderTarget2D RenderTarget;
|
||||||
private BoundingFrustum CameraView;
|
private BoundingFrustum CameraView;
|
||||||
@ -51,16 +49,15 @@ namespace TrueCraft.Client
|
|||||||
Window.Title = "TrueCraft";
|
Window.Title = "TrueCraft";
|
||||||
Content.RootDirectory = "Content";
|
Content.RootDirectory = "Content";
|
||||||
Graphics = new GraphicsDeviceManager(this);
|
Graphics = new GraphicsDeviceManager(this);
|
||||||
|
Graphics.SynchronizeWithVerticalRetrace = false;
|
||||||
Graphics.IsFullScreen = UserSettings.Local.IsFullscreen;
|
Graphics.IsFullScreen = UserSettings.Local.IsFullscreen;
|
||||||
Graphics.PreferredBackBufferWidth = UserSettings.Local.WindowResolution.Width;
|
Graphics.PreferredBackBufferWidth = UserSettings.Local.WindowResolution.Width;
|
||||||
Graphics.PreferredBackBufferHeight = UserSettings.Local.WindowResolution.Height;
|
Graphics.PreferredBackBufferHeight = UserSettings.Local.WindowResolution.Height;
|
||||||
Client = client;
|
Client = client;
|
||||||
EndPoint = endPoint;
|
EndPoint = endPoint;
|
||||||
NextPhysicsUpdate = DateTime.MinValue;
|
NextPhysicsUpdate = DateTime.MinValue;
|
||||||
ChunkMeshes = new List<ChunkMesh>();
|
ChunkMeshes = new List<Mesh>();
|
||||||
TransparentChunkMeshes = new List<ChunkMesh>();
|
IncomingChunks = new ConcurrentBag<Mesh>();
|
||||||
IncomingChunks = new ConcurrentBag<ChunkMesh>();
|
|
||||||
IncomingTransparentChunks = new ConcurrentBag<ChunkMesh>();
|
|
||||||
PendingMainThreadActions = new ConcurrentBag<Action>();
|
PendingMainThreadActions = new ConcurrentBag<Action>();
|
||||||
MouseCaptured = true;
|
MouseCaptured = true;
|
||||||
|
|
||||||
@ -79,9 +76,9 @@ namespace TrueCraft.Client
|
|||||||
SpriteBatch = new SpriteBatch(GraphicsDevice);
|
SpriteBatch = new SpriteBatch(GraphicsDevice);
|
||||||
base.Initialize(); // (calls LoadContent)
|
base.Initialize(); // (calls LoadContent)
|
||||||
ChunkConverter = new ChunkRenderer(Graphics.GraphicsDevice, Client.World.World.BlockRepository);
|
ChunkConverter = new ChunkRenderer(Graphics.GraphicsDevice, Client.World.World.BlockRepository);
|
||||||
Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
|
Client.ChunkLoaded += (sender, e) => ChunkConverter.Enqueue(e.Chunk);
|
||||||
Client.ChunkModified += (sender, e) => ChunkConverter.QueueHighPriorityChunk(e.Chunk);
|
Client.ChunkModified += (sender, e) => ChunkConverter.Enqueue(e.Chunk, true);
|
||||||
ChunkConverter.MeshGenerated += ChunkConverter_MeshGenerated;
|
ChunkConverter.MeshCompleted += ChunkConverter_MeshGenerated;
|
||||||
ChunkConverter.Start();
|
ChunkConverter.Start();
|
||||||
Client.PropertyChanged += HandleClientPropertyChanged;
|
Client.PropertyChanged += HandleClientPropertyChanged;
|
||||||
Client.Connect(EndPoint);
|
Client.Connect(EndPoint);
|
||||||
@ -103,12 +100,9 @@ namespace TrueCraft.Client
|
|||||||
false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
|
false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChunkConverter_MeshGenerated(object sender, ChunkRenderer.MeshGeneratedEventArgs e)
|
void ChunkConverter_MeshGenerated(object sender, RendererEventArgs<ReadOnlyChunk> e)
|
||||||
{
|
{
|
||||||
if (e.Transparent)
|
IncomingChunks.Add(e.Result);
|
||||||
IncomingTransparentChunks.Add(e.Mesh);
|
|
||||||
else
|
|
||||||
IncomingChunks.Add(e.Mesh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e)
|
void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
@ -119,7 +113,7 @@ namespace TrueCraft.Client
|
|||||||
UpdateCamera();
|
UpdateCamera();
|
||||||
var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D(
|
var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D(
|
||||||
(int)Client.Position.X, 0, (int)Client.Position.Z));
|
(int)Client.Position.X, 0, (int)Client.Position.Z));
|
||||||
PendingMainThreadActions.Add(() => TransparentChunkMeshes.Sort(sorter));
|
PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,16 +281,11 @@ namespace TrueCraft.Client
|
|||||||
i.Update(gameTime);
|
i.Update(gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkMesh mesh;
|
Mesh mesh;
|
||||||
if (IncomingChunks.TryTake(out mesh))
|
if (IncomingChunks.TryTake(out mesh))
|
||||||
{
|
{
|
||||||
var existing = ChunkMeshes.SingleOrDefault(m => m.Chunk.Chunk.Coordinates == mesh.Chunk.Chunk.Coordinates);
|
|
||||||
if (existing != null)
|
|
||||||
ChunkMeshes.Remove(existing);
|
|
||||||
ChunkMeshes.Add(mesh);
|
ChunkMeshes.Add(mesh);
|
||||||
}
|
}
|
||||||
if (IncomingTransparentChunks.TryTake(out mesh)) // TODO: re-render transparent meshes
|
|
||||||
TransparentChunkMeshes.Add(mesh);
|
|
||||||
Action action;
|
Action action;
|
||||||
if (PendingMainThreadActions.TryTake(out action))
|
if (PendingMainThreadActions.TryTake(out action))
|
||||||
action();
|
action();
|
||||||
@ -364,17 +353,17 @@ namespace TrueCraft.Client
|
|||||||
{
|
{
|
||||||
verticies += ChunkMeshes[i].GetTotalVertices();
|
verticies += ChunkMeshes[i].GetTotalVertices();
|
||||||
chunks++;
|
chunks++;
|
||||||
ChunkMeshes[i].Draw(OpaqueEffect);
|
ChunkMeshes[i].Draw(OpaqueEffect, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
|
GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
|
||||||
for (int i = 0; i < TransparentChunkMeshes.Count; i++)
|
for (int i = 0; i < ChunkMeshes.Count; i++)
|
||||||
{
|
{
|
||||||
if (CameraView.Intersects(TransparentChunkMeshes[i].BoundingBox))
|
if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
|
||||||
{
|
{
|
||||||
if (!TransparentChunkMeshes[i].IsDisposed)
|
if (!ChunkMeshes[i].IsDisposed)
|
||||||
verticies += TransparentChunkMeshes[i].GetTotalVertices();
|
verticies += ChunkMeshes[i].GetTotalVertices();
|
||||||
TransparentChunkMeshes[i].Draw(TransparentEffect);
|
ChunkMeshes[i].Draw(TransparentEffect, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
|
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 2010
|
# Visual Studio Express 2013 for Windows Desktop
|
||||||
|
VisualStudioVersion = 12.0.31101.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft", "TrueCraft\TrueCraft.csproj", "{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft", "TrueCraft\TrueCraft.csproj", "{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.API", "TrueCraft.API\TrueCraft.API.csproj", "{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.API", "TrueCraft.API\TrueCraft.API.csproj", "{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}"
|
||||||
@ -19,44 +21,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.Core.Test", "True
|
|||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|x86 = Debug|x86
|
|
||||||
Release|x86 = Release|x86
|
|
||||||
Debug|x64 = Debug|x64
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.ActiveCfg = Debug|x86
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.Build.0 = Debug|x86
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.ActiveCfg = Debug|x86
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.Build.0 = Debug|x86
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.ActiveCfg = Release|x86
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.Build.0 = Release|x86
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.ActiveCfg = Release|x86
|
|
||||||
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.Build.0 = Release|x86
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x64.ActiveCfg = Debug|x86
|
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x64.ActiveCfg = Debug|x86
|
||||||
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x64.Build.0 = Debug|x86
|
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x64.Build.0 = Debug|x86
|
||||||
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x86.ActiveCfg = Debug|x86
|
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
@ -65,14 +35,6 @@ Global
|
|||||||
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x64.Build.0 = Release|x86
|
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x64.Build.0 = Release|x86
|
||||||
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x86.ActiveCfg = Release|x86
|
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x86.ActiveCfg = Release|x86
|
||||||
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x86.Build.0 = Release|x86
|
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x86.Build.0 = Release|x86
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x64.Build.0 = Debug|Any CPU
|
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
@ -81,6 +43,49 @@ Global
|
|||||||
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x64.Build.0 = Release|Any CPU
|
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x86.ActiveCfg = Release|Any CPU
|
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x86.Build.0 = Release|Any CPU
|
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.ActiveCfg = Debug|x86
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.Build.0 = Debug|x86
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.ActiveCfg = Release|x86
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.Build.0 = Release|x86
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.Build.0 = Release|x86
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{BCFDCD93-C23E-49E6-9767-A887B3C2A709} = {6756B61E-5856-4CA7-90B5-6053763FE7BA}
|
{BCFDCD93-C23E-49E6-9767-A887B3C2A709} = {6756B61E-5856-4CA7-90B5-6053763FE7BA}
|
||||||
|
Reference in New Issue
Block a user