✨ 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)
|
||||||
@@ -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)
|
||||||
|
Reference in New Issue
Block a user