diff --git a/.idea/.idea.Minecraft/.idea/misc.xml b/.idea/.idea.Minecraft/.idea/misc.xml new file mode 100644 index 00000000..283b9b4d --- /dev/null +++ b/.idea/.idea.Minecraft/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/Assets/Resources/Prefabs/Player.prefab b/Assets/Resources/Prefabs/Player.prefab index 30b80376..1f3c2ea6 100644 --- a/Assets/Resources/Prefabs/Player.prefab +++ b/Assets/Resources/Prefabs/Player.prefab @@ -698,7 +698,7 @@ MonoBehaviour: isFlying: 0 drawBounds: 0 sensitivity: 5 - checkIncrement: 0.05 + checkIncrement: 0.1 reach: 4.5 cam: {fileID: 3114816152506633376} inventory: {fileID: 0} diff --git a/Assets/Scenes/MainMenu.unity b/Assets/Scenes/MainMenu.unity index b9c8f3b4..7c841328 100644 --- a/Assets/Scenes/MainMenu.unity +++ b/Assets/Scenes/MainMenu.unity @@ -1852,7 +1852,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 1} m_AnchorMax: {x: 0.5, y: 1} - m_AnchoredPosition: {x: -152.5, y: 215544.27} + m_AnchoredPosition: {x: -152.5, y: 248223.02} m_SizeDelta: {x: 305, y: 0} m_Pivot: {x: 0, y: 1} --- !u!114 &845042230 @@ -3799,7 +3799,7 @@ MonoBehaviour: m_HandleRect: {fileID: 236816974} m_Direction: 2 m_Value: 1 - m_Size: 0.4168831 + m_Size: 0.40985915 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -5757,7 +5757,7 @@ PrefabInstance: type: 3} propertyPath: transport value: - objectReference: {fileID: 2330286423484457791} + objectReference: {fileID: 2330286423484457793} - target: {fileID: 2330286422166840741, guid: ed098faebcb7c0a46a1b8cbc9753bb43, type: 3} propertyPath: m_Enabled @@ -5771,16 +5771,16 @@ PrefabInstance: m_RemovedComponents: - {fileID: 2330286422166840739, guid: ed098faebcb7c0a46a1b8cbc9753bb43, type: 3} m_SourcePrefab: {fileID: 100100000, guid: ed098faebcb7c0a46a1b8cbc9753bb43, type: 3} ---- !u!114 &2330286423484457791 stripped +--- !u!114 &2330286423484457793 stripped MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 2330286422166840743, guid: ed098faebcb7c0a46a1b8cbc9753bb43, + m_CorrespondingSourceObject: {fileID: 2330286422166840742, guid: ed098faebcb7c0a46a1b8cbc9753bb43, type: 3} m_PrefabInstance: {fileID: 2330286423484457790} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3} + m_Script: {fileID: 11500000, guid: 61b93ff779f4ef84da855aef42076949, type: 3} m_Name: m_EditorClassIdentifier: --- !u!224 &4834870413912905276 diff --git a/Assets/Scenes/World.unity b/Assets/Scenes/World.unity index c2529d29..ca8dcb78 100644 --- a/Assets/Scenes/World.unity +++ b/Assets/Scenes/World.unity @@ -3875,7 +3875,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e113280f9ab044168541f6fef84d3095, type: 3} m_Name: m_EditorClassIdentifier: - renderDistance: 2 + renderDistance: 3 chunkSize: 16 chunkHeight: 16 worldHeight: 96 @@ -3884,7 +3884,7 @@ MonoBehaviour: worldRenderer: {fileID: 1269315924} terrainGenerator: {fileID: 1658937128} mapSeedOffset: {x: -251, y: 150, z: 0} - GenerateMoreChunks: 1 + GenerateMoreChunks: 0 gamma: 0 skyLightMultiplier: 0.9 blockLightMultiplier: 1.18 diff --git a/Assets/TODO.md b/Assets/TODO.md index 808285bd..c7da2768 100644 --- a/Assets/TODO.md +++ b/Assets/TODO.md @@ -12,4 +12,6 @@ made by me - fix noise never going above 0.67 - also change the inventory on the server instead of only on the client (so that the server can save the inventory) + +- broke generating more chunks than the initial ones \ No newline at end of file diff --git a/Assets/ZeroFormatter.meta b/Assets/ZeroFormatter.meta deleted file mode 100644 index ed4f3d7c..00000000 --- a/Assets/ZeroFormatter.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9adf2c1a9f1d0734a9457a3bde392767 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Scripts/F3MenuManger.cs b/Assets/_Scripts/F3MenuManger.cs index 5ec84138..939a3c0a 100644 --- a/Assets/_Scripts/F3MenuManger.cs +++ b/Assets/_Scripts/F3MenuManger.cs @@ -15,6 +15,8 @@ public class F3MenuManger : MonoBehaviour public void Update() { + if(WorldServer.IsDedicated) return; + if (Input.GetKeyUp(KeyCode.F3)) { if (!GameManager.Instance.localPlayer.f3KeyComboUsed) diff --git a/Assets/_Scripts/Networking/MinecraftNetworkManager.cs b/Assets/_Scripts/Networking/MinecraftNetworkManager.cs index 4690966c..76b4d17c 100644 --- a/Assets/_Scripts/Networking/MinecraftNetworkManager.cs +++ b/Assets/_Scripts/Networking/MinecraftNetworkManager.cs @@ -19,7 +19,7 @@ public override void OnClientConnect() base.OnClientConnect(); NetworkClient.Send(new WorldServer.StartPlayerMessage(SteamClient.SteamId)); - // NetworkClient.Send(new WorldServer.SpawnPlayerMessage(SteamClient.SteamId)); + NetworkClient.Send(new WorldServer.ChunkRequestMessage(Vector3Int.zero, World.Instance.renderDistance)); } public override void OnStartServer() @@ -55,4 +55,10 @@ public override void OnServerDisconnect(NetworkConnectionToClient conn) base.OnServerDisconnect(conn); } + + public override void OnStopServer() + { + World.Instance.SaveWorld(); + base.OnStopServer(); + } } diff --git a/Assets/_Scripts/World/ChunkData.cs b/Assets/_Scripts/World/ChunkData.cs index dcca361a..d15eb460 100644 --- a/Assets/_Scripts/World/ChunkData.cs +++ b/Assets/_Scripts/World/ChunkData.cs @@ -17,7 +17,7 @@ public class ChunkData public bool modifiedAfterSave = true; public TreeData treeData = new TreeData(); - [CanBeNull] public ChunkRenderer renderer => WorldDataHelper.GetChunk(worldRef, worldPos); + [CanBeNull] public ChunkRenderer renderer => WorldDataHelper.GetChunk(worldPos); public readonly Queue blockLightUpdateQueue = new(); public readonly Queue blockLightRemoveQueue = new(); @@ -51,6 +51,7 @@ public static ChunkData Deserialize(ChunkSaveData saveData) } chunkData.sections = sections; chunkData.modifiedAfterSave = false; + chunkData.isGenerated = true; return chunkData; } diff --git a/Assets/_Scripts/World/World.cs b/Assets/_Scripts/World/World.cs index c1768256..4901ebff 100644 --- a/Assets/_Scripts/World/World.cs +++ b/Assets/_Scripts/World/World.cs @@ -7,12 +7,13 @@ using Mirror; using UnityEngine; using UnityEngine.Events; +using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; #if UNITY_EDITOR using UnityEditor; using UnityEditor.SceneManagement; +[InitializeOnLoad] #endif - public partial class World : MonoBehaviour { public static World Instance; @@ -55,12 +56,16 @@ public partial class World : MonoBehaviour public bool IsWorldCreated { get; private set; } private Stopwatch fullStopwatch = new Stopwatch(); - - public Dictionary blocksToPlaceAfterGeneration = new Dictionary(); public bool validateDone; [HideInInspector] public bool isInPlayMode; + #if UNITY_EDITOR + static World() + { + EditorApplication.update += EditorUpdate; + } + #endif private void Awake() { @@ -78,19 +83,25 @@ private void Start() NetworkClient.RegisterHandler(message => { StartWorld(); - GenerateWorld(message.position); + // GenerateWorld(message.position); + }); + + NetworkClient.RegisterHandler(message => + { + Debug.Log("Received chunk"); + + var chunkData = ChunkData.Deserialize(message.chunkSaveData); + + if (worldData.chunkDataDict.ContainsKey(chunkData.worldPos) && NetworkServer.active) + { + worldData.chunkDataDict[chunkData.worldPos] = chunkData; + } + else + { + worldData.chunkDataDict.TryAdd(chunkData.worldPos, chunkData); + } + dataToMeshQueue.Enqueue(chunkData); }); - - // NetworkClient.RegisterHandler(message => - // { - // Debug.Log("Received chunk"); - // - // var chunkData = ChunkData.Deserialize(message.chunkSaveData); - // - // worldData.chunkDataDict[chunkData.worldPos] = chunkData; - // - // GenerateOnlyMesh(); - // }); } private void OnValidate() @@ -100,7 +111,7 @@ private void OnValidate() { worldData = new WorldData { - chunkDataDict = new Dictionary(), + chunkDataDict = new ConcurrentDictionary(), chunkDict = new Dictionary(), worldName = worldName, }; @@ -116,7 +127,7 @@ private void OnValidate() Shader.SetGlobalVectorArray("lightColors", LightTextureCreator.lightColors); } - private WorldGenerationData GetPositionsInRenderDistance(Vector3Int playerPos) + public WorldGenerationData GetPositionsInRenderDistance(Vector3Int playerPos) { var allChunkPositionsNeeded = WorldDataHelper.GetChunkPositionsInRenderDistance(this, playerPos); var allChunkDataPositionsNeeded = WorldDataHelper.GetDataPositionsInRenderDistance(this, playerPos); @@ -196,7 +207,7 @@ public void SetBlock(Vector3 pos, BlockType blockType) public void SetBlock(Vector3Int blockPos, BlockType blockType) { var chunkPos = WorldDataHelper.GetChunkPosition(this, blockPos); - var chunk = WorldDataHelper.GetChunk(this, chunkPos); + var chunk = WorldDataHelper.GetChunk(chunkPos); if (chunk == null) return; chunk.ModifiedByPlayer = true; chunk.ChunkData.modifiedAfterSave = true; @@ -206,7 +217,7 @@ public void SetBlock(Vector3Int blockPos, BlockType blockType) public void SetBlocks(IEnumerable blockPoss, BlockType blockType) { var chunkPos = WorldDataHelper.GetChunkPosition(this, blockPoss.First()); - var chunk = WorldDataHelper.GetChunk(this, chunkPos); + var chunk = WorldDataHelper.GetChunk(chunkPos); SetBlocks(chunk, blockPoss, blockType); } @@ -232,7 +243,7 @@ public async void SetBlocks(ChunkRenderer chunk, IEnumerable blockPo { if(neighChunkData == null) continue; - ChunkRenderer neighbourChunk = WorldDataHelper.GetChunk(neighChunkData.worldRef, neighChunkData.worldPos); + ChunkRenderer neighbourChunk = WorldDataHelper.GetChunk(neighChunkData.worldPos); if(neighbourChunk != null) { neightBourUpdates.Add(neighbourChunk); @@ -301,17 +312,15 @@ public void LoadAdditionalChunks(GameObject localPlayer) if (!GenerateMoreChunks) return; // Debug.Log("Loading additional chunks"); - GenerateWorld(Vector3Int.RoundToInt(localPlayer.transform.position), () => - { - OnNewChunksGenerated?.Invoke(); - }); + GenerateWorld(Vector3Int.RoundToInt(localPlayer.transform.position)); + OnNewChunksGenerated?.Invoke(); } private void Clear() { worldData = new WorldData { - chunkDataDict = new Dictionary(), + chunkDataDict = new ConcurrentDictionary(), chunkDict = new Dictionary(), worldName = worldName }; @@ -320,8 +329,17 @@ private void Clear() worldRenderer.chunkPool?.Clear(); isSaving = false; + // Clear queues + doneDataQueue.Clear(); + dataToMeshQueue.Clear(); + meshToRenderQueue.Clear(); + + actionOnChunkDone.Clear(); + chunksToUpdate?.Clear(); + disabled = false; + MyNoise.noiseStopwatch.Reset(); MyNoise.noise3DStopwatch.Reset(); @@ -394,6 +412,6 @@ public StartWorldMessage(Vector3Int position) public struct WorldData { public string worldName; - public Dictionary chunkDataDict; + public ConcurrentDictionary chunkDataDict; public Dictionary chunkDict; } diff --git a/Assets/_Scripts/World/WorldDataHelper.cs b/Assets/_Scripts/World/WorldDataHelper.cs index c7f63adf..9fc7b8e1 100644 --- a/Assets/_Scripts/World/WorldDataHelper.cs +++ b/Assets/_Scripts/World/WorldDataHelper.cs @@ -24,7 +24,7 @@ public static List GetChunkPositionsInRenderDistance(World world, Ve var endX = playerPos.x + Mathf.Min(world.renderDistance, World.Instance.IsWorldCreated ? world.renderDistance : 8) * world.chunkSize; var endZ = playerPos.z + Mathf.Min(world.renderDistance, World.Instance.IsWorldCreated ? world.renderDistance : 8) * world.chunkSize; - return GetPositionsInRenderDistance(world,playerPos, startX, startZ, endX, endZ); + return GetPositionsInRenderDistance(world, startX, startZ, endX, endZ); } public static List GetDataPositionsInRenderDistance(World world, Vector3Int playerPos) @@ -34,10 +34,10 @@ public static List GetDataPositionsInRenderDistance(World world, Vec var endX = playerPos.x + (Mathf.Min(world.renderDistance, World.Instance.IsWorldCreated ? world.renderDistance : 8)+1) * world.chunkSize; var endZ = playerPos.z + (Mathf.Min(world.renderDistance, World.Instance.IsWorldCreated ? world.renderDistance : 8)+1) * world.chunkSize; - return GetPositionsInRenderDistance(world,playerPos, startX, startZ, endX, endZ); + return GetPositionsInRenderDistance(world, startX, startZ, endX, endZ); } - private static List GetPositionsInRenderDistance(World world, Vector3Int playerPos, int startX, + private static List GetPositionsInRenderDistance(World world, int startX, int startZ, int endX, int endZ) { var chunkPositionsToCreate = new List(); @@ -70,7 +70,7 @@ public static HashSet GetPositionsToCreate(WorldData worldData, List { return allChunkPositionsNeeded .Where(pos => !worldData.chunkDict.ContainsKey(pos) && !DoesChunkFileExist(worldData,pos)) - .OrderBy(pos => Vector3.Distance(playerPos, pos)).Take(World.Instance.IsWorldCreated ? World.Instance.chunksGenerationPerFrame : allChunkPositionsNeeded.Count) + .OrderBy(pos => Vector3.Distance(playerPos, pos))//.Take(World.Instance.IsWorldCreated ? World.Instance.chunksGenerationPerFrame : allChunkPositionsNeeded.Count) .ToHashSet(); } @@ -78,7 +78,7 @@ public static HashSet GetDataPositionsToCreate(WorldData worldData, { return allChunkDataPositionsNeeded .Where(pos => !worldData.chunkDataDict.ContainsKey(pos) && !DoesChunkFileExist(worldData,pos)) - .OrderBy(pos => Vector3.Distance(playerPos, pos)).Take(World.Instance.IsWorldCreated ? 9 + World.Instance.chunksGenerationPerFrame*3 : allChunkDataPositionsNeeded.Count) + .OrderBy(pos => Vector3.Distance(playerPos, pos))//.Take(World.Instance.IsWorldCreated ? 9 + World.Instance.chunksGenerationPerFrame*3 : allChunkDataPositionsNeeded.Count) .ToHashSet(); } @@ -86,7 +86,7 @@ public static HashSet GetPositionsToLoad(WorldData worldData, List !worldData.chunkDict.ContainsKey(pos) && DoesChunkFileExist(worldData,pos)) - .OrderBy(pos => Vector3.Distance(playerPos, pos)).Take(World.Instance.IsWorldCreated ? World.Instance.chunksGenerationPerFrame : allChunkPositionsNeeded.Count) + .OrderBy(pos => Vector3.Distance(playerPos, pos))//.Take(World.Instance.IsWorldCreated ? World.Instance.chunksGenerationPerFrame : allChunkPositionsNeeded.Count) .ToHashSet(); } @@ -94,7 +94,7 @@ public static HashSet GetDataPositionsToLoad(WorldData worldData, Li { return allChunkDataPositionsNeeded .Where(pos => !worldData.chunkDataDict.ContainsKey(pos) && DoesChunkFileExist(worldData,pos)) - .OrderBy(pos => Vector3.Distance(playerPos, pos)).Take(World.Instance.IsWorldCreated ? 9 + World.Instance.chunksGenerationPerFrame*3 : allChunkDataPositionsNeeded.Count) + .OrderBy(pos => Vector3.Distance(playerPos, pos))//.Take(World.Instance.IsWorldCreated ? 9 + World.Instance.chunksGenerationPerFrame*3 : allChunkDataPositionsNeeded.Count) .ToHashSet(); } @@ -134,15 +134,15 @@ public static void RemoveChunk(World world, Vector3Int pos) world.worldRenderer.RemoveChunk(chunk); world.worldData.chunkDict.Remove(pos); } - else - { - throw new Exception("Could not find chunk to remove"); - } + // else + // { + // throw new Exception("Could not find chunk to remove"); + // } } public static void RemoveChunkData(World world, Vector3Int pos) { - world.worldData.chunkDataDict.Remove(pos); + world.worldData.chunkDataDict.Remove(pos, out _); } public static void SetBlock(World world, Vector3Int worldBlockPos, BlockType blockType) @@ -156,13 +156,18 @@ public static void SetBlock(World world, Vector3Int worldBlockPos, BlockType blo } [CanBeNull] - public static ChunkRenderer GetChunk(World world, Vector3Int worldPos) + public static ChunkRenderer GetChunk(Vector3Int worldPos) { - if (world.worldData.chunkDict.ContainsKey(worldPos)) + try + { + World.Instance.worldData.chunkDict.TryGetValue(worldPos, out var chunk); + return chunk; + } + // This is so fucking stupid + // for some fucking reason trygetvalue null errors but that is not possible + catch(NullReferenceException) { - return world.worldData.chunkDict[worldPos]; } - return null; } diff --git a/Assets/_Scripts/World/WorldServer.cs b/Assets/_Scripts/World/WorldServer.cs index 95f04b70..679bb711 100644 --- a/Assets/_Scripts/World/WorldServer.cs +++ b/Assets/_Scripts/World/WorldServer.cs @@ -1,6 +1,9 @@ using System; using System.Collections; using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Cysharp.Threading.Tasks; using Mirror; using Steamworks; using Unity.VisualScripting; @@ -9,6 +12,8 @@ public class WorldServer : NetworkBehaviour { public static WorldServer instance; + + public static bool IsDedicated => !NetworkClient.active && NetworkServer.active; private void Start() { @@ -22,15 +27,94 @@ public override void OnStartServer() NetworkServer.RegisterHandler(SavePlayerMessageHandler, false); NetworkServer.RegisterHandler(SpawnPlayerMessageHandler, false); NetworkServer.RegisterHandler(StartPlayerMessageHandler, false); + NetworkServer.RegisterHandler(ChunkRequestMessageHandler, false); StartCoroutine(SaveLoop()); } + + #region Chunk Requests + + private async void ChunkRequestMessageHandler(NetworkConnectionToClient conn, ChunkRequestMessage message) + { + World.Instance.renderDistance = message.renderDistance; + var positions = WorldDataHelper.GetDataPositionsInRenderDistance(World.Instance, message.position); + //Sort by distance + positions = positions.OrderBy(x => Vector3.Distance(x, message.position)).ToList(); + + if (!World.Instance.worldData.chunkDataDict.ContainsKey(message.position)) + { + World.Instance.GenerateWorld(message.position); + + // Create chunk actions + foreach (var pos in positions) + { + if (World.Instance.actionOnChunkDone.ContainsKey(pos)) + { + World.Instance.actionOnChunkDone[pos] += () => { SendChunkAtPos(pos, conn);}; + } else + { + World.Instance.actionOnChunkDone.Add(pos, () => { SendChunkAtPos(pos, conn);}); + } + } + } + else + { + // Send the chunkData and all of its neighbors + + foreach (var pos in positions) + { + SendChunkAtPos(pos, conn); + await UniTask.Delay(100); + } + } + } + + private void SendChunkAtPos(Vector3Int pos, NetworkConnectionToClient conn) + { + if (World.Instance.worldData.chunkDataDict.TryGetValue(pos, out var chunkData)) + { + var chunkDataMessage = new ChunkDataMessage + { + chunkSaveData = ChunkSaveData.Serialize(chunkData) + }; + conn.Send(chunkDataMessage); + } else + { + Debug.LogError("Could not send chunk at " + pos); + } + } + + + public struct ChunkRequestMessage : NetworkMessage + { + public Vector3Int position; + public int renderDistance; + + public ChunkRequestMessage(Vector3Int pos, int renderDistance) + { + position = pos; + this.renderDistance = renderDistance; + } + } + + public struct ChunkDataMessage : NetworkMessage + { + public ChunkSaveData chunkSaveData; + + public ChunkDataMessage(ChunkSaveData saveData) + { + chunkSaveData = saveData; + } + } + + #endregion #region Block public void SetBlockMessageHandler(NetworkConnectionToClient conn, SetBlockMessage message) { RpcSetBlock(message.position, message.blockType); + WorldDataHelper.SetBlock(World.Instance, message.position, message.blockType); } [ClientRpc] diff --git a/Assets/_Scripts/World/World_Generation.cs b/Assets/_Scripts/World/World_Generation.cs index acae4242..45d87b89 100644 --- a/Assets/_Scripts/World/World_Generation.cs +++ b/Assets/_Scripts/World/World_Generation.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; -using Mirror; using Steamworks; using Unity.VisualScripting; using UnityEngine; @@ -16,6 +15,16 @@ public partial class World { + public readonly ConcurrentQueue doneDataQueue = new(); + public readonly ConcurrentQueue dataToMeshQueue = new(); + public readonly ConcurrentQueue> meshToRenderQueue = new(); + public readonly Dictionary actionOnChunkDone = new(); + + public readonly Stopwatch dataStopwatch = new(); + public readonly Stopwatch loadStopwatch = new(); + public readonly Stopwatch featureStopwatch = new(); + public readonly Stopwatch meshStopwatch = new(); + public void GenerateWorld() { StartWorld(); @@ -32,14 +41,12 @@ public void StartWorld() updateThread.Start(); } - public async void GenerateWorld(Vector3Int position, Action onGenerateNewChunks = null) + public async void GenerateWorld(Vector3Int position) { Profiler.BeginThreadProfiling("GenerateWorld", "GenerateWorld"); - if (!Application.isPlaying) - { - fullStopwatch.Start(); - } - + + fullStopwatch.Start(); + terrainGenerator.GenerateBiomePoints(position, renderDistance, chunkSize, mapSeedOffset); WorldGenerationData worldGenerationData = await Task.Run(() => GetPositionsInRenderDistance(position), taskTokenSource.Token); @@ -55,140 +62,16 @@ public async void GenerateWorld(Vector3Int position, Action onGenerateNewChunks } // Generate data chunks - ConcurrentDictionary dataDict = new ConcurrentDictionary(); - - var dataStopWatch = new Stopwatch(); - dataStopWatch.Start(); - - Profiler.BeginThreadProfiling("GenerateWorld", "GenerateData"); - - if (worldGenerationData.chunkPositionsToCreate.Count > 0) - { - try - { - dataDict.AddRange(await CalculateWorldChunkData(worldGenerationData.chunkDataPositionsToCreate)); - } - catch (OperationCanceledException) - { - Debug.Log("Task cancelled"); - return; - } - } - dataStopWatch.Stop(); - if (!Application.isPlaying) - { - Debug.Log($"Data generation took {dataStopWatch.ElapsedMilliseconds}ms"); - } - - var loadStopWatch = Stopwatch.StartNew(); - if (worldGenerationData.chunkDataPositionsToLoad.Count > 0) - { - try - { - dataDict.AddRange(await LoadChunksAsync(worldGenerationData.chunkDataPositionsToLoad, worldName)); - } - catch (OperationCanceledException) - { - Debug.Log("Task cancelled"); - return; - } - } - - loadStopWatch.Stop(); - if (!Application.isPlaying) - { - Debug.Log($"Loading took {loadStopWatch.ElapsedMilliseconds}ms"); - } - - foreach (var calculatedData in dataDict) - { - worldData.chunkDataDict.Add(calculatedData.Key, calculatedData.Value); - } - - Profiler.EndThreadProfiling(); - - - - var featureStopWatch = new Stopwatch(); - featureStopWatch.Start(); - - // Generate features like trees - await CalculateFeatures(worldGenerationData.chunkPositionsToCreate); - - - await Task.Run(() => - { - Parallel.ForEach(blocksToPlaceAfterGeneration, (block) => - { - WorldDataHelper.SetBlock(this, block.Key, block.Value); - }); - }); - - featureStopWatch.Stop(); - if (!Application.isPlaying) - { - Debug.Log($"Feature generation took {featureStopWatch.ElapsedMilliseconds}ms"); - } - - var visualStopWatch = new Stopwatch(); - visualStopWatch.Start(); - - // Generate visual chunks - // ConcurrentDictionary meshDataDict = - // await CalculateChunkMeshData(worldGenerationData.chunkPositionsToCreate); - ConcurrentDictionary meshDataDict; - - List dataToRender = worldData.chunkDataDict - .Where(x => worldGenerationData.chunkPositionsToCreate.Contains(x.Key) || worldGenerationData.chunkPositionsToLoad.Contains(x.Key)) - .Select(x => x.Value) - .ToList(); - - await CastLightFirstTime(dataToRender); - - try - { - meshDataDict = await CreateMeshDataAsync(dataToRender); - } - catch (OperationCanceledException) - { - Debug.Log("Task cancelled"); - return; - } - visualStopWatch.Stop(); - if (!Application.isPlaying) - { - Debug.Log($"Mesh generation took {visualStopWatch.ElapsedMilliseconds}ms"); - Debug.Log($"Time spent generating noise {MyNoise.noiseStopwatch.ElapsedMilliseconds}ms"); - Debug.Log($"Time spent generating 3D noise {MyNoise.noise3DStopwatch.ElapsedMilliseconds}ms"); - } - - StartCoroutine(ChunkCreationCoroutine(meshDataDict, position)); - - onGenerateNewChunks?.Invoke(); - - Profiler.EndThreadProfiling(); - } - - public async void GenerateOnlyData(Vector3Int position, Action onDone = null) - { - terrainGenerator.GenerateBiomePoints(position, renderDistance, chunkSize, mapSeedOffset); - - WorldGenerationData worldGenerationData = await Task.Run(() => GetPositionsInRenderDistance(position), taskTokenSource.Token); - - // Generate data chunks - ConcurrentDictionary dataDict = new ConcurrentDictionary(); - - var dataStopWatch = new Stopwatch(); - dataStopWatch.Start(); Profiler.BeginThreadProfiling("GenerateWorld", "GenerateData"); - if (worldGenerationData.chunkPositionsToCreate.Count > 0) + + if (worldGenerationData.chunkDataPositionsToCreate.Count > 0) { try { - dataDict.AddRange(await CalculateWorldChunkData(worldGenerationData.chunkDataPositionsToCreate)); + CalculateWorldChunkData(worldGenerationData.chunkDataPositionsToCreate); } catch (OperationCanceledException) { @@ -196,18 +79,13 @@ public async void GenerateOnlyData(Vector3Int position, Action onDone = null) return; } } - dataStopWatch.Stop(); - if (!Application.isPlaying) - { - Debug.Log($"Data generation took {dataStopWatch.ElapsedMilliseconds}ms"); - } - var loadStopWatch = Stopwatch.StartNew(); + if (worldGenerationData.chunkDataPositionsToLoad.Count > 0) { try { - dataDict.AddRange(await LoadChunksAsync(worldGenerationData.chunkDataPositionsToLoad, worldName)); + LoadChunksAsync(worldGenerationData.chunkDataPositionsToLoad, worldName); } catch (OperationCanceledException) { @@ -215,80 +93,12 @@ public async void GenerateOnlyData(Vector3Int position, Action onDone = null) return; } } - - loadStopWatch.Stop(); - if (!Application.isPlaying) - { - Debug.Log($"Loading took {loadStopWatch.ElapsedMilliseconds}ms"); - } - foreach (var calculatedData in dataDict) - { - worldData.chunkDataDict.Add(calculatedData.Key, calculatedData.Value); - } - Profiler.EndThreadProfiling(); - - - - var featureStopWatch = new Stopwatch(); - featureStopWatch.Start(); - - // Generate features like trees - await CalculateFeatures(worldGenerationData.chunkPositionsToCreate); - - - await Task.Run(() => - { - Parallel.ForEach(blocksToPlaceAfterGeneration, (block) => - { - WorldDataHelper.SetBlock(this, block.Key, block.Value); - }); - }); - - featureStopWatch.Stop(); - if (!Application.isPlaying) - { - Debug.Log($"Feature generation took {featureStopWatch.ElapsedMilliseconds}ms"); - } - onDone?.Invoke(); + Profiler.EndThreadProfiling(); } - // public async void GenerateOnlyMesh() - // { - // // Generate visual chunks - // // ConcurrentDictionary meshDataDict = - // // await CalculateChunkMeshData(worldGenerationData.chunkPositionsToCreate); - // ConcurrentDictionary meshDataDict; - // - // List dataToRender = worldData.chunkDataDict - // .Select(x => x.Value) - // .ToList(); - // - // await CastLightFirstTime(dataToRender); - // - // try - // { - // meshDataDict = await CreateMeshDataAsync(dataToRender); - // } - // catch (OperationCanceledException) - // { - // Debug.Log("Task cancelled"); - // return; - // } - // - // StartCoroutine(ChunkCreationCoroutine(meshDataDict, Vector3Int.zero)); - // } - - public UniTask CastLightFirstTime(List dataToCast) - { - return UniTask.RunOnThreadPool(() => - { - Parallel.ForEach(dataToCast, Lighting.RecastSunLightFirstTime); - }); - } - public UniTask> CreateMeshDataAsync(List dataToRender) { return UniTask.RunOnThreadPool(() => @@ -342,29 +152,18 @@ public UniTask> CreateMeshDataAsync(L }, true, taskTokenSource.Token); } - // private Task> CalculateChunkMeshData(List chunkPositionsToCreate) - // { - // return Task.Run(() => - // { - // var meshDataDict = new ConcurrentDictionary(); - // - // Parallel.ForEach(chunkPositionsToCreate, pos => - // { - // var data = worldData.chunkDataDict[pos]; - // var meshData = Chunk.GetChunkMeshData(data); - // meshDataDict.TryAdd(pos, meshData); - // }); - // - // return meshDataDict; - // }); - // } + public void CreateMeshData(ChunkData dataToRender) + { + var meshData = dataToRender.GetMeshData(); + meshToRenderQueue.Enqueue(new KeyValuePair(dataToRender.worldPos,meshData)); + } - private UniTask> CalculateWorldChunkData(HashSet chunkDataPositionsToCreate) + private UniTask CalculateWorldChunkData(HashSet chunkDataPositionsToCreate) { return UniTask.RunOnThreadPool(() => { Profiler.BeginThreadProfiling("MyThreads","CalculateWorldChunkData"); - var dataDict = new ConcurrentDictionary(); + // var dataDict = new ConcurrentDictionary(); // while (chunkDataPositionsToCreate.Count > 0) // { // if(chunkDataPositionsToCreate.Count < chunksGenerationPerFrame) @@ -388,8 +187,8 @@ private UniTask> CalculateWorldChunk // }); // UniTask.NextFrame(); // } - - Parallel.ForEach(chunkDataPositionsToCreate, pos => + dataStopwatch.Start(); + Parallel.ForEach(chunkDataPositionsToCreate,new ParallelOptions {MaxDegreeOfParallelism = 8}, pos => { if(taskTokenSource.IsCancellationRequested) { @@ -398,10 +197,17 @@ private UniTask> CalculateWorldChunk var data = new ChunkData(chunkSize, chunkHeight, this, pos); var newData = terrainGenerator.GenerateChunkData(data, mapSeedOffset); - dataDict.TryAdd(pos, newData); - + if (worldData.chunkDataDict.TryAdd(pos, newData)) + { + doneDataQueue.Enqueue(pos); + } + // else + // { + // Debug.LogError($"Failed to add chunk data '{pos}' to doneData"); + // } }); - + dataStopwatch.Stop(); + // foreach(var pos in chunkDataPositionsToCreate) // { // if(taskTokenSource.IsCancellationRequested) @@ -416,7 +222,6 @@ private UniTask> CalculateWorldChunk // }; Profiler.EndThreadProfiling(); - return dataDict; }, true, taskTokenSource.Token); } @@ -438,7 +243,20 @@ private UniTask CalculateFeatures(HashSet chunkDataPositionsToCreate },true, taskTokenSource.Token); } - + private void CalculateFeature(Vector3Int pos) + { + var data = worldData.chunkDataDict[pos]; + terrainGenerator.GenerateFeatures(data, mapSeedOffset); + data.isGenerated = true; + + actionOnChunkDone.TryGetValue(pos, out var action); + action?.Invoke(); + + if (!isInPlayMode) + { + dataToMeshQueue.Enqueue(data); + } + } IEnumerator ChunkCreationCoroutine(ConcurrentDictionary meshDataConDict, Vector3Int playerPos) { @@ -457,7 +275,7 @@ IEnumerator ChunkCreationCoroutine(ConcurrentDictionary me { IsWorldCreated = true; OnWorldCreated?.Invoke(); - NetworkClient.Send(new WorldServer.SpawnPlayerMessage(SteamClient.SteamId)); + // NetworkClient.Send(new WorldServer.SpawnPlayerMessage(SteamClient.SteamId)); } if (!Application.isPlaying) diff --git a/Assets/_Scripts/World/World_Serialization.cs b/Assets/_Scripts/World/World_Serialization.cs index 6b85b862..39aa5e7b 100644 --- a/Assets/_Scripts/World/World_Serialization.cs +++ b/Assets/_Scripts/World/World_Serialization.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; +using Steamworks; using UnityEngine; using Debug = UnityEngine.Debug; @@ -96,17 +97,19 @@ public void SavePlayer(WorldServer.SavePlayerMessage message) { var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".minecraftUnity/saves/" + worldName + $"/playerdata/{message.steamId}.json"); File.WriteAllText(path, JsonUtility.ToJson(message)); + // get the steam username from the id + Debug.Log($"Saved player {message.steamId}"); } - public UniTask> LoadChunksAsync(IEnumerable chunks, string worldName) + public UniTask LoadChunksAsync(IEnumerable chunks, string worldName) { return UniTask.RunOnThreadPool(() => { // TODO: for some reason when you use more than 1 thread it crashes because of type mismatch in chunkSection deserialize // this seems to be a bug in unity - var dict = new ConcurrentDictionary(); // Parallel.ForEach(chunks, chunkPos => // { + loadStopwatch.Start(); foreach (var chunkPos in chunks) { if(taskTokenSource.IsCancellationRequested) @@ -133,11 +136,24 @@ public UniTask> LoadChunksAsync(IEnu } var chunkSaveData = JsonUtility.FromJson(json); - dict.TryAdd(chunkPos, ChunkData.Deserialize(chunkSaveData)); + var chunkData = ChunkData.Deserialize(chunkSaveData); + if (worldData.chunkDataDict.TryAdd(chunkPos,chunkData)) + { + actionOnChunkDone.TryGetValue(chunkPos, out var action); + action?.Invoke(); + if (!isInPlayMode) + { + dataToMeshQueue.Enqueue(chunkData); + } + } + else + { + Debug.LogError($"Failed to add chunk data '{chunkPos}' to doneData"); + } } + loadStopwatch.Stop(); // }); - return dict; }, cancellationToken: taskTokenSource.Token); } diff --git a/Assets/_Scripts/World/World_UpdateLoop.cs b/Assets/_Scripts/World/World_UpdateLoop.cs index d89c909a..4d10a838 100644 --- a/Assets/_Scripts/World/World_UpdateLoop.cs +++ b/Assets/_Scripts/World/World_UpdateLoop.cs @@ -1,8 +1,14 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Cysharp.Threading.Tasks; using Mirror; +using Steamworks; +using Unity.VisualScripting; +using UnityEditor; using UnityEngine; public partial class World @@ -14,6 +20,18 @@ public partial class World public object chunkUpdateThreadLock = new object(); private Thread updateThread; + public readonly Vector3Int[] neighborOffsets = + { + new(-16, 0, -16), + new(-16, 0, +16), + new(+16, 0, -16), + new(+16, 0, +16), + new(-16, 0, 0), + new(+16, 0, 0), + new(0, 0, -16), + new(0, 0, +16) + }; + public void UpdateLoop() { while (!disabled) @@ -22,6 +40,55 @@ public void UpdateLoop() { UpdateChunks(); } + + // World generation queues + if (doneDataQueue.Count > 0) + { + try + { + // Parallel.For(0, doneDataQueue.Count, _ => + // { + doneDataQueue.TryDequeue(out var chunkPos); + + // Check if neighbor chunks are ready + if (neighborOffsets.All(o => worldData.chunkDataDict.ContainsKey(chunkPos + o))) + { + featureStopwatch.Start(); + CalculateFeature(chunkPos); + featureStopwatch.Stop(); + } + else + { + doneDataQueue.Enqueue(chunkPos); + } + // }); + } + catch (ThreadAbortException) + { + } + } + + if (dataToMeshQueue.Count > 0) + { + // Parallel.For(0, dataToMeshQueue.Count, _ => + // { + meshStopwatch.Start(); + dataToMeshQueue.TryDequeue(out var chunkData); + + if(neighborOffsets.All(o => worldData.chunkDataDict.ContainsKey(chunkData.worldPos + o) /*&& worldData.chunkDataDict[chunkData.worldPos + o].isGenerated*/)) + { + Lighting.RecastSunLightFirstTime(chunkData); + + CreateMeshData(chunkData); + } + else + { + dataToMeshQueue.Enqueue(chunkData); + } + + meshStopwatch.Stop(); + // }); + } } } @@ -53,21 +120,93 @@ public void AddChunkToUpdate(ChunkRenderer chunk, bool first = false) } } + #if UNITY_EDITOR + public static void EditorUpdate() + { + try + { + if (Application.isPlaying) return; + + var world = Instance; + // Debug.Log("Editor Update"); + if (world.meshToRenderQueue.Count > 0) + { + world.meshToRenderQueue.TryDequeue(out var data); + world.CreateChunk(world.worldData, data.Key, data.Value); + + if (!world.IsWorldCreated && world.worldData.chunkDict.Count == WorldDataHelper.GetChunkPositionsInRenderDistance(Instance, Vector3Int.zero).Count) + { + world.IsWorldCreated = true; + // if (Application.isPlaying) + // { + // world.OnWorldCreated?.Invoke(); + // NetworkClient.Send(new WorldServer.SpawnPlayerMessage(SteamClient.SteamId)); + // } + + Debug.Log($"Data generation took {world.dataStopwatch.ElapsedMilliseconds}ms"); + Debug.Log($"Loading took {world.loadStopwatch.ElapsedMilliseconds}ms"); + Debug.Log($"Feature generation took {world.featureStopwatch.ElapsedMilliseconds}ms"); + Debug.Log($"Mesh generation took {world.meshStopwatch.ElapsedMilliseconds}ms"); + Debug.Log($"Time spent generating noise {MyNoise.noiseStopwatch.ElapsedMilliseconds}ms"); + Debug.Log($"Time spent generating 3D noise {MyNoise.noise3DStopwatch.ElapsedMilliseconds}ms"); + Debug.Log($"World created in {world.fullStopwatch.ElapsedMilliseconds}ms"); + world.fullStopwatch.Stop(); + world.fullStopwatch.Reset(); + world.dataStopwatch.Stop(); + world.dataStopwatch.Reset(); + world.loadStopwatch.Stop(); + world.loadStopwatch.Reset(); + world.featureStopwatch.Stop(); + world.featureStopwatch.Reset(); + world.meshStopwatch.Stop(); + world.meshStopwatch.Reset(); + } + } + } + catch (NullReferenceException e) + { + Debug.LogError(e); + EditorApplication.update -= EditorUpdate; + } + } + #endif + private void Update() { + if(WorldServer.IsDedicated) return; + //TODO: remove this because it is slow Camera.main.backgroundColor = Color.Lerp(nightColor,dayColor , skyLightMultiplier); - // if (Input.GetKeyDown(KeyCode.L)) - // { - // WorldServer.instance.RequestChunk(Vector3Int.zero); - // } + if (Input.GetKeyDown(KeyCode.L)) + { + NetworkClient.Send(new WorldServer.ChunkRequestMessage(Vector3Int.zero, renderDistance)); + } if (chunksToUpdateMesh.Count > 0) { var data = chunksToUpdateMesh.Dequeue(); data.chunk.RenderMesh(data.meshData); } + + if (meshToRenderQueue.Count > 0) + { + meshToRenderQueue.TryDequeue(out var data); + WorldDataHelper.RemoveChunk(this,data.Key); + CreateChunk(worldData, data.Key, data.Value); + + if (!IsWorldCreated) + { + IsWorldCreated = true; + OnWorldCreated?.Invoke(); + NetworkClient.Send(new WorldServer.SpawnPlayerMessage(SteamClient.SteamId)); + + fullStopwatch.Stop(); + Debug.Log($"World created in {fullStopwatch.ElapsedMilliseconds}ms"); + fullStopwatch.Reset(); + } + UniTask.NextFrame(); + } } public void UpdateBlocks() diff --git a/Packages/manifest.json b/Packages/manifest.json index 6bf24f93..6b09155c 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -4,7 +4,7 @@ "com.unity.collab-proxy": "1.15.15", "com.unity.feature.2d": "1.0.0", "com.unity.feature.development": "1.0.1", - "com.unity.ide.rider": "3.0.13", + "com.unity.ide.rider": "3.0.14", "com.unity.ide.visualstudio": "2.0.14", "com.unity.ide.vscode": "1.2.5", "com.unity.probuilder": "5.0.4", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 30d4e99f..359186d0 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -153,7 +153,7 @@ } }, "com.unity.ide.rider": { - "version": "3.0.13", + "version": "3.0.14", "depth": 0, "source": "registry", "dependencies": { diff --git a/ProjectSettings/DynamicsManager.asset b/ProjectSettings/DynamicsManager.asset index abb382fc..ccbf361e 100644 --- a/ProjectSettings/DynamicsManager.asset +++ b/ProjectSettings/DynamicsManager.asset @@ -19,7 +19,7 @@ PhysicsManager: m_ClothInterCollisionStiffness: 0.2 m_ContactsGeneration: 1 m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - m_AutoSimulation: 1 + m_AutoSimulation: 0 m_AutoSyncTransforms: 0 m_ReuseCollisionCallbacks: 0 m_ClothInterCollisionSettingsToggle: 0 diff --git a/ProjectSettings/RiderScriptEditorPersistedState.asset b/ProjectSettings/RiderScriptEditorPersistedState.asset index 614f8565..0fffa934 100644 --- a/ProjectSettings/RiderScriptEditorPersistedState.asset +++ b/ProjectSettings/RiderScriptEditorPersistedState.asset @@ -12,4 +12,4 @@ MonoBehaviour: m_Script: {fileID: 0} m_Name: m_EditorClassIdentifier: Unity.Rider.Editor:Packages.Rider.Editor:RiderScriptEditorPersistedState - lastWriteTicks: -8585514039852941166 + lastWriteTicks: -8585509687508064223