✨ Multi threading world gen
This commit is contained in:
@@ -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<Action> _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<Vector2I>();
|
||||
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<Vector2I>();
|
||||
|
||||
// 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<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)
|
||||
@@ -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)
|
||||
|
Reference in New Issue
Block a user