Multi threading world gen

This commit is contained in:
2025-08-29 13:38:46 +08:00
parent 862f11d445
commit 8073ed23c0

View File

@@ -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)
@@ -178,7 +275,7 @@ public partial class NaturalResourceGenerator : Node2D
chunkPos.Y * ChunkSize chunkPos.Y * ChunkSize
); );
} }
private bool IsInLoadedChunk(Vector2I cell) private bool IsInLoadedChunk(Vector2I cell)
{ {
// Check the chunk where the cell is located // 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.X / ChunkSize),
(int)Mathf.Floor((float)cell.Y / ChunkSize) (int)Mathf.Floor((float)cell.Y / ChunkSize)
); );
// Also check adjacent chunks since veins can cross chunk boundaries // Also check adjacent chunks since veins can cross chunk boundaries
for (var dx = -1; dx <= 1; dx++) for (var dx = -1; dx <= 1; dx++)
{ {
@@ -199,7 +296,7 @@ public partial class NaturalResourceGenerator : Node2D
} }
} }
} }
return false; return false;
} }
@@ -223,19 +320,20 @@ 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");
continue; continue;
} }
// Try to place the tile // Try to place the tile
if (PlaceTile(tileType, cell)) if (PlaceTile(tileType, cell))
{ {
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 };
@@ -263,7 +361,7 @@ public partial class NaturalResourceGenerator : Node2D
{ {
// First, remove any existing tile at this position // First, remove any existing tile at this position
Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground); Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground);
var building = Registry.GetBuilding(tileType); var building = Registry.GetBuilding(tileType);
if (building == null) if (building == null)
{ {
@@ -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)