VoxelUnity/Assets/Scripts/ChunkManager.cs

391 lines
12 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
public class ChunkManager
{
private enum ChunkGeometryType
{
Mixed,
Full,
Empty
};
// chunks inside this radius will be actively loaded and visible
private Vector3Int m_min_view_radius;
// chunks within this radius stay active, but are not loaded actively. Chunks
// outside this radius are set inactive
private Vector3Int m_max_view_radius;
// chunks within this radius are not deleted
private Vector3Int m_cached_chunk_radius;
private Transform m_reference_transform;
private Vector3Int m_reference_chunk_position;
private Vector3Int m_srqd_min_view_radius;
private Vector3Int m_srqd_max_view_radius;
private Vector3Int m_srqd_cached_chunk_radius;
// chunks that are in our memory (either as active or inactive object in Unity)
private Dictionary<Vector3Int, Chunk> m_dict_loaded_chunks;
// active (visible chunks)
private Dictionary<Vector3Int, Chunk> m_dict_active_chunks;
// chunks that are requested from server but not received yet
private Dictionary<Vector3Int, bool> m_dict_chunk_requested;
// blocked chunks. (e.g. geometry is build in another thread) They will not be removed
private Dictionary<Vector3Int, bool> m_dict_chunk_blocked;
// dict of chunks that are empty. For them no geometry is build
private Dictionary<Vector3Int, bool> m_dict_empty_chunk;
// dict of chunks that are full of geometry. Since they probably stay invisible, they will also not drawed
// TODO: special case: empty blocks are directly located next to that chunk
private Dictionary<Vector3Int, bool> m_dict_full_chunk;
private WorldManager m_world_manager;
private NetworkThread m_network_thread;
private bool m_end_builder_threads = false;
private Queue<Chunk> m_empty_chunks_queue;
private Queue<MessageObject> m_message_queue;
// queue of chunk which are waiting for geometry to be applied
private Queue<Chunk> m_chunk_mesh_queue;
private object m_handle = new object();
private System.Threading.Thread[] m_threads = null;
public bool end_builder_threads
{
get
{
bool v;
lock(m_handle)
{
v = m_end_builder_threads;
}
return v;
}
set
{
lock(m_handle)
{
m_end_builder_threads = value;
}
}
}
public ChunkManager(Vector3Int min_view_radius,
Vector3Int max_view_radius,
Vector3Int cached_chunk_radius,
WorldManager world_manager,
NetworkThread network_thread,
Transform reference_transform,
int n_threads)
{
m_min_view_radius = min_view_radius;
m_max_view_radius = max_view_radius;
m_cached_chunk_radius = cached_chunk_radius;
m_srqd_min_view_radius = Vector3Int.Scale(min_view_radius, min_view_radius);
m_srqd_max_view_radius = Vector3Int.Scale(max_view_radius, max_view_radius);
m_srqd_cached_chunk_radius = Vector3Int.Scale(cached_chunk_radius, cached_chunk_radius);
m_world_manager = world_manager;
m_network_thread = network_thread;
m_reference_transform = reference_transform;
m_dict_active_chunks = new Dictionary<Vector3Int, Chunk>();
m_dict_loaded_chunks = new Dictionary<Vector3Int, Chunk>();
m_dict_chunk_requested = new Dictionary<Vector3Int, bool>();
m_dict_chunk_blocked = new Dictionary<Vector3Int, bool>();
m_dict_empty_chunk = new Dictionary<Vector3Int, bool>();
m_dict_full_chunk = new Dictionary<Vector3Int, bool>();
m_empty_chunks_queue = new Queue<Chunk>();
m_message_queue = new Queue<MessageObject>();
m_chunk_mesh_queue = new Queue<Chunk>();
m_threads = new System.Threading.Thread[n_threads];
for (int i = 0; i < n_threads; i++)
{
// Start chunk builder thread
m_threads[i] = new System.Threading.Thread(RunChunkBuilderThread);
m_threads[i].Start();
}
}
private ChunkGeometryType analyseData (int[] data)
{
int i = 0;
if (data[i] == 0)
{
// first block is air
while(i < data.Length)
{
if (data[i] != 0)
{
return ChunkGeometryType.Mixed;
}
i++;
}
return ChunkGeometryType.Empty;
}
else
{
// first block is something
while (i < data.Length)
{
if (data[i] == 0)
{
return ChunkGeometryType.Mixed;
}
i++;
}
return ChunkGeometryType.Full;
}
}
public void processServerMessage(MessageObject mo)
{
//TODO: check datatype
Vector3Int chunk_pos = new Vector3Int(mo.position[2], mo.position[1], mo.position[0]);
if (m_dict_chunk_requested.ContainsKey(chunk_pos))
{
ChunkGeometryType cg_type = analyseData(mo.data);
m_dict_chunk_requested.Remove(chunk_pos);
if (cg_type == ChunkGeometryType.Empty)
{
m_dict_empty_chunk[chunk_pos] = true;
return;
}
if (cg_type == ChunkGeometryType.Full)
{
m_dict_full_chunk[chunk_pos] = true;
return;
}
Vector3Int tmp = chunk_pos - m_reference_chunk_position;
tmp = Vector3Int.Scale(tmp,tmp);
if (!InRange(tmp, m_srqd_max_view_radius))
{
return;
}
m_dict_chunk_blocked[chunk_pos] = true;
Chunk c = m_world_manager.InstantiateNewChunk(chunk_pos);
lock(m_handle)
{
m_empty_chunks_queue.Enqueue(c);
m_message_queue.Enqueue(mo);
}
}
else
{
Debug.LogWarning("Received chunk we not asked for");
}
}
private void RunChunkBuilderThread()
{
int count;
Chunk c;
MessageObject mo;
while (!end_builder_threads)
{
lock(m_handle)
{
count = m_message_queue.Count;
}
if (count > 0)
{
lock(m_handle)
{
c = m_empty_chunks_queue.Dequeue();
mo = m_message_queue.Dequeue();
}
c.SetDataFromMessageObject(mo);
c.BuildMesh();
// TODO: connect neighbors
lock(m_handle)
{
m_chunk_mesh_queue.Enqueue(c);
}
}
System.Threading.Thread.Sleep(50);
}
Debug.Log("Closing Chunk Builder Thread");
}
private bool isChunkCached(Vector3Int chunk_pos)
{
return m_dict_loaded_chunks.ContainsKey(chunk_pos);
}
private bool isChunkActive(Vector3Int chunk_pos)
{
return m_dict_active_chunks.ContainsKey(chunk_pos);
}
public void requestChunkFromServer(Vector3Int chunk_pos)
{
if (!m_dict_chunk_requested.ContainsKey(chunk_pos) && !m_dict_loaded_chunks.ContainsKey(chunk_pos))
{
m_dict_chunk_requested[chunk_pos] = true;
//request chunk
// build a chunk request to server:
MessageObject mo = new MessageObject();
mo.datatype = "request_chunk";
// TODO: reverse z and x since unity uses a left handed coordinate system
mo.position = new int[] {chunk_pos.z, chunk_pos.y, chunk_pos.x};
m_network_thread.SendMessage(mo);
}
}
public void ApplyUpdates(int max_chunk_updates=1)
{
int i = max_chunk_updates;
int n;
Chunk c;
lock(m_handle)
{
n = m_chunk_mesh_queue.Count;
}
while(n > 0 && i > 0)
{
lock(m_handle)
{
c = m_chunk_mesh_queue.Dequeue();
}
c.AssignMesh();
i -= 1;
n -= 1;
m_dict_chunk_blocked.Remove(c.ChunkPositionVec);
m_dict_loaded_chunks[c.ChunkPositionVec] = c;
m_dict_active_chunks[c.ChunkPositionVec] = c;
}
}
private bool InRange(Vector3Int sqrd_dist, Vector3Int sqrd_range)
{
Vector3Int tmp = sqrd_range - sqrd_dist;
return tmp.x > 0 && tmp.y > 0 && tmp.z > 0;
}
public void UpdateViewRange()
{
Vector3Int current_chunk_position = Vector3Int.RoundToInt(m_reference_transform.position);
current_chunk_position.x /= Chunk.N_CHUNK_SIDELENGTH;
current_chunk_position.y /= Chunk.N_CHUNK_SIDELENGTH;
current_chunk_position.z /= Chunk.N_CHUNK_SIDELENGTH;
m_reference_chunk_position = current_chunk_position;
Stack<Vector3Int> to_remove = new Stack<Vector3Int>();
// check for chunks to remove:
foreach(KeyValuePair<Vector3Int, Chunk> entry in m_dict_loaded_chunks)
{
// do something with entry.Value or entry.Key
Vector3Int tmp = entry.Key - m_reference_chunk_position;
tmp = Vector3Int.Scale(tmp,tmp);
if (m_dict_chunk_blocked.ContainsKey(entry.Key))
{
continue;
}
if (!InRange(tmp, m_srqd_max_view_radius))
{
entry.Value.gameObject.SetActive(false);
m_dict_active_chunks.Remove(entry.Key);
//m_dict_loaded_chunks.Remove(entry.Key);
}
if (!InRange(tmp, m_srqd_cached_chunk_radius))
{
//m_dict_active_chunks.Remove(entry.Key);
to_remove.Push(entry.Key);
}
}
while (to_remove.Count > 0)
{
Vector3Int k = to_remove.Pop();
Chunk c = m_dict_loaded_chunks[k];
m_dict_loaded_chunks.Remove(k);
UnityEngine.Object.Destroy(c);
}
// reload view range:
for (int x=-m_min_view_radius.x; x < m_min_view_radius.x; x++)
{
for (int y=-m_min_view_radius.y; y < m_min_view_radius.y; y++)
{
for (int z=-m_min_view_radius.z; z < m_min_view_radius.z; z++)
{
Vector3Int p = new Vector3Int(x, y, z) + m_reference_chunk_position;
if (m_dict_active_chunks.ContainsKey(p)
|| m_dict_chunk_requested.ContainsKey(p)
|| m_dict_chunk_blocked.ContainsKey(p)
|| m_dict_empty_chunk.ContainsKey(p)
|| m_dict_full_chunk.ContainsKey(p))
{
// TODO: the if above is a hell of a mess!
continue;
}
if (m_dict_loaded_chunks.ContainsKey(p))
{
Chunk c = m_dict_loaded_chunks[p];
c.gameObject.SetActive(true);
m_dict_active_chunks[p] = c;
}
else if (!m_dict_chunk_requested.ContainsKey(p))
{
requestChunkFromServer(p);
}
}
}
}
}
}