diff --git a/Scripts/System/NaturalResourceGenerator.cs b/Scripts/System/NaturalResourceGenerator.cs index d32e12d..5fb2d49 100644 --- a/Scripts/System/NaturalResourceGenerator.cs +++ b/Scripts/System/NaturalResourceGenerator.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Concurrent; using Godot; using System.Collections.Generic; +using System.Threading.Tasks; using AceFieldNewHorizon.Scripts.Entities; -using AceFieldNewHorizon.Scripts.Tiles; namespace AceFieldNewHorizon.Scripts.System; @@ -31,6 +32,10 @@ public partial class NaturalResourceGenerator : Node2D private Player _player; + private readonly ConcurrentQueue _mainThreadActions = new(); + private Task _generationTask; + private bool _isRunning = true; + public override void _Ready() { _rng = new RandomNumberGenerator(); @@ -39,6 +44,13 @@ public partial class NaturalResourceGenerator : Node2D 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; 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); + if (playerChunk == _lastPlayerChunk) return; _lastPlayerChunk = playerChunk; - // Unload chunks outside load distance - var chunksToRemove = new List(); - foreach (var chunkPos in _loadedChunks.Keys) + // Start generation in background task if not already running + if (_generationTask == null || _generationTask.IsCompleted) { - if (Mathf.Abs(chunkPos.X - playerChunk.X) > LoadDistance || - Mathf.Abs(chunkPos.Y - playerChunk.Y) > LoadDistance) - { - chunksToRemove.Add(chunkPos); - } + _generationTask = Task.Run(() => GenerateChunksAroundPlayer(playerChunk)); } + } - foreach (var chunkPos in chunksToRemove) - { - UnloadChunk(chunkPos); - } + private void GenerateChunksAroundPlayer(Vector2I playerChunk) + { + var chunksToLoad = new List(); - // Load chunks around player + // Determine which chunks to load/unload for (int x = -LoadDistance; x <= LoadDistance; x++) { 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); 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 tileList) + { + var placed = new HashSet(); + var queue = new Queue(); + 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) @@ -178,7 +275,7 @@ public partial class NaturalResourceGenerator : Node2D chunkPos.Y * ChunkSize ); } - + private bool IsInLoadedChunk(Vector2I cell) { // Check the chunk where the cell is located @@ -186,7 +283,7 @@ public partial class NaturalResourceGenerator : Node2D (int)Mathf.Floor((float)cell.X / ChunkSize), (int)Mathf.Floor((float)cell.Y / ChunkSize) ); - + // Also check adjacent chunks since veins can cross chunk boundaries for (var dx = -1; dx <= 1; dx++) { @@ -199,7 +296,7 @@ public partial class NaturalResourceGenerator : Node2D } } } - + return false; } @@ -223,19 +320,20 @@ public partial class NaturalResourceGenerator : Node2D GD.Print($"{LogPrefix} Skipping cell {cell} - already placed"); continue; } + if (!IsInLoadedChunk(cell)) { GD.Print($"{LogPrefix} Skipping cell {cell} - out of bounds"); continue; } - + // Try to place the tile if (PlaceTile(tileType, cell)) { tileList.Add(cell); placed.Add(cell); 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 var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left }; @@ -263,7 +361,7 @@ public partial class NaturalResourceGenerator : Node2D { // First, remove any existing tile at this position Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground); - + var building = Registry.GetBuilding(tileType); if (building == null) { @@ -289,7 +387,7 @@ public partial class NaturalResourceGenerator : Node2D instance.ZIndex = (int)building.Layer; AddChild(instance); 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; } catch (Exception e)