391 lines
12 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|