✨ Multi threading world gen
This commit is contained in:
		| @@ -1,8 +1,9 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Collections.Concurrent; | ||||||
| using Godot; | using Godot; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
|  | using System.Threading.Tasks; | ||||||
| using AceFieldNewHorizon.Scripts.Entities; | using AceFieldNewHorizon.Scripts.Entities; | ||||||
| using AceFieldNewHorizon.Scripts.Tiles; |  | ||||||
|  |  | ||||||
| namespace AceFieldNewHorizon.Scripts.System; | namespace AceFieldNewHorizon.Scripts.System; | ||||||
|  |  | ||||||
| @@ -31,6 +32,10 @@ public partial class NaturalResourceGenerator : Node2D | |||||||
|  |  | ||||||
|     private Player _player; |     private Player _player; | ||||||
|  |  | ||||||
|  |     private readonly ConcurrentQueue<Action> _mainThreadActions = new(); | ||||||
|  |     private Task _generationTask; | ||||||
|  |     private bool _isRunning = true; | ||||||
|  |  | ||||||
|     public override void _Ready() |     public override void _Ready() | ||||||
|     { |     { | ||||||
|         _rng = new RandomNumberGenerator(); |         _rng = new RandomNumberGenerator(); | ||||||
| @@ -39,6 +44,13 @@ public partial class NaturalResourceGenerator : Node2D | |||||||
|  |  | ||||||
|     public override void _Process(double delta) |     public override void _Process(double delta) | ||||||
|     { |     { | ||||||
|  |         // Process any pending main thread actions | ||||||
|  |         while (_mainThreadActions.TryDequeue(out var action)) | ||||||
|  |         { | ||||||
|  |             action.Invoke(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Rest of your existing _Process code | ||||||
|         _player = GetTree().GetFirstNodeInGroup(ChunkTrackerGroupName) as Player; |         _player = GetTree().GetFirstNodeInGroup(ChunkTrackerGroupName) as Player; | ||||||
|         if (_player != null) |         if (_player != null) | ||||||
|         { |         { | ||||||
| @@ -50,29 +62,25 @@ public partial class NaturalResourceGenerator : Node2D | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void UpdatePlayerPosition(Vector2 playerPosition) |     private void UpdatePlayerPosition(Vector2 playerPosition) | ||||||
|     { |     { | ||||||
|         var playerChunk = WorldToChunkCoords(playerPosition); |         var playerChunk = WorldToChunkCoords(playerPosition); | ||||||
|  |  | ||||||
|         if (playerChunk == _lastPlayerChunk) return; |         if (playerChunk == _lastPlayerChunk) return; | ||||||
|         _lastPlayerChunk = playerChunk; |         _lastPlayerChunk = playerChunk; | ||||||
|  |  | ||||||
|         // Unload chunks outside load distance |         // Start generation in background task if not already running | ||||||
|         var chunksToRemove = new List<Vector2I>(); |         if (_generationTask == null || _generationTask.IsCompleted) | ||||||
|         foreach (var chunkPos in _loadedChunks.Keys) |  | ||||||
|         { |         { | ||||||
|             if (Mathf.Abs(chunkPos.X - playerChunk.X) > LoadDistance || |             _generationTask = Task.Run(() => GenerateChunksAroundPlayer(playerChunk)); | ||||||
|                 Mathf.Abs(chunkPos.Y - playerChunk.Y) > LoadDistance) |  | ||||||
|             { |  | ||||||
|                 chunksToRemove.Add(chunkPos); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         foreach (var chunkPos in chunksToRemove) |     private void GenerateChunksAroundPlayer(Vector2I playerChunk) | ||||||
|     { |     { | ||||||
|             UnloadChunk(chunkPos); |         var chunksToLoad = new List<Vector2I>(); | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Load chunks around player |         // Determine which chunks to load/unload | ||||||
|         for (int x = -LoadDistance; x <= LoadDistance; x++) |         for (int x = -LoadDistance; x <= LoadDistance; x++) | ||||||
|         { |         { | ||||||
|             for (int y = -LoadDistance; y <= LoadDistance; y++) |             for (int y = -LoadDistance; y <= LoadDistance; y++) | ||||||
| @@ -80,10 +88,99 @@ public partial class NaturalResourceGenerator : Node2D | |||||||
|                 var chunkPos = new Vector2I(playerChunk.X + x, playerChunk.Y + y); |                 var chunkPos = new Vector2I(playerChunk.X + x, playerChunk.Y + y); | ||||||
|                 if (!_loadedChunks.ContainsKey(chunkPos)) |                 if (!_loadedChunks.ContainsKey(chunkPos)) | ||||||
|                 { |                 { | ||||||
|                     GenerateChunk(chunkPos); |                     chunksToLoad.Add(chunkPos); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Generate chunks in background | ||||||
|  |         foreach (var chunkPos in chunksToLoad) | ||||||
|  |         { | ||||||
|  |             if (!_isRunning) return; | ||||||
|  |             GenerateChunkInBackground(chunkPos); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void GenerateChunkInBackground(Vector2I chunkPos) | ||||||
|  |     { | ||||||
|  |         // Skip if chunk was loaded while we were waiting | ||||||
|  |         if (_loadedChunks.ContainsKey(chunkPos)) return; | ||||||
|  |  | ||||||
|  |         var chunkData = new ChunkData(); | ||||||
|  |         var chunkWorldPos = ChunkToWorldCoords(chunkPos); | ||||||
|  |  | ||||||
|  |         // Generate ground tiles | ||||||
|  |         for (int x = 0; x < ChunkSize; x++) | ||||||
|  |         { | ||||||
|  |             for (int y = 0; y < ChunkSize; y++) | ||||||
|  |             { | ||||||
|  |                 var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y); | ||||||
|  |                 _mainThreadActions.Enqueue(() => PlaceTile("ground", cell)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Generate stone veins | ||||||
|  |         for (int x = 0; x < ChunkSize; x++) | ||||||
|  |         { | ||||||
|  |             for (int y = 0; y < ChunkSize; y++) | ||||||
|  |             { | ||||||
|  |                 var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y); | ||||||
|  |                 if (_rng.Randf() < StoneDensity) | ||||||
|  |                 { | ||||||
|  |                     var veinSize = _rng.RandiRange(MinStoneVeinSize, MaxStoneVeinSize); | ||||||
|  |                     PlaceVeinInBackground(cell, "stone", veinSize, chunkData.StoneTiles); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Add chunk data to dictionary on main thread | ||||||
|  |         _mainThreadActions.Enqueue(() => { _loadedChunks[chunkPos] = chunkData; }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void PlaceVeinInBackground(Vector2I startCell, string tileType, int maxSize, List<Vector2I> tileList) | ||||||
|  |     { | ||||||
|  |         var placed = new HashSet<Vector2I>(); | ||||||
|  |         var queue = new Queue<Vector2I>(); | ||||||
|  |         queue.Enqueue(startCell); | ||||||
|  |  | ||||||
|  |         int placedCount = 0; | ||||||
|  |  | ||||||
|  |         while (queue.Count > 0 && placedCount < maxSize && _isRunning) | ||||||
|  |         { | ||||||
|  |             var cell = queue.Dequeue(); | ||||||
|  |             if (placed.Contains(cell)) continue; | ||||||
|  |  | ||||||
|  |             // Schedule tile placement on main thread | ||||||
|  |             _mainThreadActions.Enqueue(() => | ||||||
|  |             { | ||||||
|  |                 if (PlaceTile(tileType, cell)) | ||||||
|  |                 { | ||||||
|  |                     tileList.Add(cell); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             placed.Add(cell); | ||||||
|  |             placedCount++; | ||||||
|  |  | ||||||
|  |             // Add adjacent cells | ||||||
|  |             var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left }; | ||||||
|  |             foreach (var dir in directions) | ||||||
|  |             { | ||||||
|  |                 var newCell = cell + dir; | ||||||
|  |                 if (!placed.Contains(newCell)) | ||||||
|  |                 { | ||||||
|  |                     queue.Enqueue(newCell); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public override void _ExitTree() | ||||||
|  |     { | ||||||
|  |         // Clean up | ||||||
|  |         _isRunning = false; | ||||||
|  |         _generationTask?.Wait(); // Wait for current generation to finish | ||||||
|  |         base._ExitTree(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void GenerateChunk(Vector2I chunkPos) |     private void GenerateChunk(Vector2I chunkPos) | ||||||
| @@ -223,6 +320,7 @@ public partial class NaturalResourceGenerator : Node2D | |||||||
|                 GD.Print($"{LogPrefix} Skipping cell {cell} - already placed"); |                 GD.Print($"{LogPrefix} Skipping cell {cell} - already placed"); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (!IsInLoadedChunk(cell)) |             if (!IsInLoadedChunk(cell)) | ||||||
|             { |             { | ||||||
|                 GD.Print($"{LogPrefix} Skipping cell {cell} - out of bounds"); |                 GD.Print($"{LogPrefix} Skipping cell {cell} - out of bounds"); | ||||||
| @@ -235,7 +333,7 @@ public partial class NaturalResourceGenerator : Node2D | |||||||
|                 tileList.Add(cell); |                 tileList.Add(cell); | ||||||
|                 placed.Add(cell); |                 placed.Add(cell); | ||||||
|                 placedCount++; |                 placedCount++; | ||||||
|                 GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} ({placedCount}/{maxSize})"); |                 // GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} ({placedCount}/{maxSize})"); | ||||||
|  |  | ||||||
|                 // Add adjacent cells to queue |                 // Add adjacent cells to queue | ||||||
|                 var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left }; |                 var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left }; | ||||||
| @@ -289,7 +387,7 @@ public partial class NaturalResourceGenerator : Node2D | |||||||
|             instance.ZIndex = (int)building.Layer; |             instance.ZIndex = (int)building.Layer; | ||||||
|             AddChild(instance); |             AddChild(instance); | ||||||
|             Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer); |             Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer); | ||||||
|             GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell}"); |             // GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell}"); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|         catch (Exception e) |         catch (Exception e) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user