riversong code showcase
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IFailedPathCache))]
|
||||
public class FailedPathCacheSystem : GameSystem, IInitializable, IDisposable, IFailedPathCache
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
private HashSet<ulong> _failedPaths = new();
|
||||
|
||||
public FailedPathCacheSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<BuildingDeletedSignal>(InvalidateCacheOnSignal);
|
||||
_signalBus.Subscribe<RoadTileUpdatedSignal>(InvalidateCacheOnSignal);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<BuildingDeletedSignal>(InvalidateCacheOnSignal);
|
||||
_signalBus.Unsubscribe<RoadTileUpdatedSignal>(InvalidateCacheOnSignal);
|
||||
}
|
||||
|
||||
private void InvalidateCacheOnSignal<T>(T signal)
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void FailPath(int sourceId, int targetId)
|
||||
{
|
||||
_failedPaths.Add(MakeKey(sourceId, targetId));
|
||||
}
|
||||
|
||||
public bool IsKnownFailedPath(int sourceId, int targetId)
|
||||
{
|
||||
return _failedPaths.Contains(MakeKey(sourceId, targetId));
|
||||
}
|
||||
|
||||
public void Invalidate()
|
||||
{
|
||||
_failedPaths.Clear();
|
||||
}
|
||||
|
||||
private ulong MakeKey(int sourceId, int targetId)
|
||||
{
|
||||
return sourceId < targetId ? ((ulong)sourceId << 32) | (uint)targetId : ((ulong)targetId << 32) | (uint)sourceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Source/Riversong/Game/World/Pathfinding/IFailedPathCache.cs
Normal file
11
Source/Riversong/Game/World/Pathfinding/IFailedPathCache.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IFailedPathCache
|
||||
{
|
||||
void FailPath(int sourceId, int targetId);
|
||||
|
||||
bool IsKnownFailedPath(int sourceId, int targetId);
|
||||
|
||||
void Invalidate();
|
||||
}
|
||||
}
|
||||
9
Source/Riversong/Game/World/Pathfinding/IPathfinder.cs
Normal file
9
Source/Riversong/Game/World/Pathfinding/IPathfinder.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IPathfinder
|
||||
{
|
||||
int FindPath(in TilePath path, ref PathQuery query);
|
||||
|
||||
PathQueryResult CompletePath(int queryHandle);
|
||||
}
|
||||
}
|
||||
21
Source/Riversong/Game/World/Pathfinding/PathQuery.cs
Normal file
21
Source/Riversong/Game/World/Pathfinding/PathQuery.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct PathQuery
|
||||
{
|
||||
public int Handle;
|
||||
|
||||
public PathSearchType SearchType;
|
||||
|
||||
public int2 SourceTile;
|
||||
|
||||
public TileRect DestinationRect;
|
||||
|
||||
public int TargetId;
|
||||
|
||||
public PathTraversalRules TraversalRules;
|
||||
|
||||
public int MaxDistance;
|
||||
}
|
||||
}
|
||||
13
Source/Riversong/Game/World/Pathfinding/PathQueryResult.cs
Normal file
13
Source/Riversong/Game/World/Pathfinding/PathQueryResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum PathQueryResult
|
||||
{
|
||||
InvalidQuery,
|
||||
|
||||
InProgress,
|
||||
|
||||
Failure,
|
||||
|
||||
Success
|
||||
}
|
||||
}
|
||||
13
Source/Riversong/Game/World/Pathfinding/PathSearchType.cs
Normal file
13
Source/Riversong/Game/World/Pathfinding/PathSearchType.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum PathSearchType : byte
|
||||
{
|
||||
FindPath,
|
||||
|
||||
SearchResourceNode,
|
||||
|
||||
SearchFertileTile,
|
||||
|
||||
SearchCritterHerd
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Flags]
|
||||
public enum PathTraversalRules : byte
|
||||
{
|
||||
None = 0,
|
||||
|
||||
CardinalDirectionsOnly = 1,
|
||||
|
||||
RoadsOnly = 1 << 1,
|
||||
|
||||
UpdateFailedPathCache = 1 << 2,
|
||||
|
||||
FertileTilesOnly = 1 << 3,
|
||||
|
||||
GenericAgent = None,
|
||||
|
||||
TransportAgent = CardinalDirectionsOnly | RoadsOnly | UpdateFailedPathCache
|
||||
}
|
||||
}
|
||||
530
Source/Riversong/Game/World/Pathfinding/PathfindingSystem.cs
Normal file
530
Source/Riversong/Game/World/Pathfinding/PathfindingSystem.cs
Normal file
@@ -0,0 +1,530 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IPathfinder))]
|
||||
[BurstCompile]
|
||||
public unsafe class PathfindingSystem : GameSystem, IInitializable, IDisposable, IUpdatable, IPathfinder, IOnWorldGenerationCompletedCallback
|
||||
{
|
||||
private const int MaxConcurrentQueries = 8;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
private Queue<PathQuery> _pendingQueries = new();
|
||||
|
||||
private Dictionary<int, (TilePath, PathQueryResult)> _paths = new();
|
||||
|
||||
private int _nextQueryHandle;
|
||||
|
||||
private NativeArray<AStarNode> _nodeCache;
|
||||
|
||||
private NativeArray<PathQuery> _runningQueries;
|
||||
|
||||
private NativeArray<PathQueryResult> _results;
|
||||
|
||||
private JobHandle? _currentJob;
|
||||
|
||||
private NativeStream _pathStream;
|
||||
|
||||
private int _version;
|
||||
|
||||
private int _jobAge;
|
||||
|
||||
public PathfindingSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<WorldGenerationCompletedSignal>(OnWorldGenerationCompleted);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<WorldGenerationCompletedSignal>(OnWorldGenerationCompleted);
|
||||
|
||||
if (_nodeCache.IsCreated) _nodeCache.Dispose();
|
||||
if (_runningQueries.IsCreated) _runningQueries.Dispose();
|
||||
if (_results.IsCreated) _results.Dispose();
|
||||
}
|
||||
|
||||
public UniTask OnWorldGenerationCompletedAsync(World world)
|
||||
{
|
||||
var tileCount = _world.Size.x * _world.Size.y;
|
||||
_nodeCache = new NativeArray<AStarNode>(MaxConcurrentQueries * tileCount, Allocator.Persistent);
|
||||
for (var threadIndex = 0; threadIndex < MaxConcurrentQueries; threadIndex++)
|
||||
{
|
||||
var slice = threadIndex * tileCount;
|
||||
var tileIndex = 0;
|
||||
|
||||
for (var y = 0; y < _world.Size.y; y++)
|
||||
for (var x = 0; x < _world.Size.x; x++)
|
||||
_nodeCache[slice++] = new AStarNode
|
||||
{
|
||||
Index = tileIndex++,
|
||||
Tile = new int2(x, y)
|
||||
};
|
||||
}
|
||||
|
||||
_runningQueries = new NativeArray<PathQuery>(MaxConcurrentQueries, Allocator.Persistent);
|
||||
_results = new NativeArray<PathQueryResult>(MaxConcurrentQueries, Allocator.Persistent);
|
||||
|
||||
_version = 1;
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public int FindPath(in TilePath path, ref PathQuery query)
|
||||
{
|
||||
query.Handle = _nextQueryHandle++;
|
||||
|
||||
_pendingQueries.Enqueue(query);
|
||||
_paths.Add(query.Handle, (path, PathQueryResult.InProgress));
|
||||
|
||||
return query.Handle;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!CompleteCurrentJob()) return;
|
||||
|
||||
var queryCount = math.min(_pendingQueries.Count, MaxConcurrentQueries);
|
||||
if (queryCount <= 0) return;
|
||||
|
||||
for (var i = 0; i < queryCount; i++) _runningQueries[i] = _pendingQueries.Dequeue();
|
||||
|
||||
var blockMap = new NativeArray<BlockReason>(_world.BlockMap.GetNativeArray(), Allocator.TempJob);
|
||||
var fertility = new NativeArray<FertilityMapValue>(_world.Fertility.GetNativeArray(), Allocator.TempJob);
|
||||
var entityIdMap = new NativeArray<EntityIdMapValue>(_world.EntityIdMap.GetNativeArray(), Allocator.TempJob);
|
||||
CopyCritterIds(entityIdMap);
|
||||
|
||||
_pathStream = new NativeStream(queryCount, Allocator.TempJob);
|
||||
|
||||
_currentJob = new PathJob
|
||||
{
|
||||
Version = _version++,
|
||||
Queries = _runningQueries,
|
||||
Results = _results,
|
||||
NodeCache = _nodeCache,
|
||||
PathStream = _pathStream,
|
||||
BlockMap = blockMap,
|
||||
Fertility = fertility,
|
||||
EntityIdMap = entityIdMap,
|
||||
WorldSize = _world.Size
|
||||
}.Schedule(queryCount, 1);
|
||||
|
||||
blockMap.Dispose(_currentJob.Value);
|
||||
entityIdMap.Dispose(_currentJob.Value);
|
||||
fertility.Dispose(_currentJob.Value);
|
||||
|
||||
_jobAge = 1;
|
||||
}
|
||||
|
||||
private void OnWorldGenerationCompleted(WorldGenerationCompletedSignal worldGenerationCompleted)
|
||||
{
|
||||
worldGenerationCompleted.Callbacks.Add(this);
|
||||
}
|
||||
|
||||
private bool CompleteCurrentJob()
|
||||
{
|
||||
if (_currentJob == null) return true;
|
||||
|
||||
if (!_currentJob.Value.IsCompleted && ++_jobAge < 4) return false;
|
||||
|
||||
_currentJob.Value.Complete();
|
||||
_currentJob = null;
|
||||
|
||||
var reader = _pathStream.AsReader();
|
||||
|
||||
for (var i = 0; i < reader.ForEachCount; i++)
|
||||
{
|
||||
if (_results[i] != PathQueryResult.Success) continue;
|
||||
|
||||
var queryHandle = _runningQueries[i].Handle;
|
||||
var (path, _) = _paths[queryHandle];
|
||||
|
||||
reader.BeginForEachIndex(i);
|
||||
while (reader.RemainingItemCount > 0) path.Steps.Add(reader.Read<int2>());
|
||||
reader.EndForEachIndex();
|
||||
}
|
||||
|
||||
_pathStream.Dispose();
|
||||
_pathStream = default;
|
||||
|
||||
for (var i = 0; i < _runningQueries.Length; i++) _paths[_runningQueries[i].Handle] = (default, _results[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CopyCritterIds(NativeArray<EntityIdMapValue> entityIdMap)
|
||||
{
|
||||
foreach (var critter in _entityCache.GetCritterAgents())
|
||||
{
|
||||
ref var critterState = ref critter.GetCritterStateRW();
|
||||
if (critterState.LockId != Entity.InvalidId) continue;
|
||||
|
||||
var tile = _tileSpace.WorldToTile(critter.Position);
|
||||
var index = math.mad(tile.y, _world.Size.x, tile.x);
|
||||
|
||||
ref var value = ref UnsafeUtility.ArrayElementAsRef<EntityIdMapValue>(entityIdMap.GetUnsafePtr(), index);
|
||||
value.CritterHerdId = critterState.CritterDefinitionId;
|
||||
}
|
||||
}
|
||||
|
||||
public PathQueryResult CompletePath(int queryHandle)
|
||||
{
|
||||
if (!_paths.TryGetValue(queryHandle, out var result)) return PathQueryResult.InvalidQuery;
|
||||
|
||||
if (result.Item2 != PathQueryResult.InProgress) _paths.Remove(queryHandle);
|
||||
|
||||
return result.Item2;
|
||||
}
|
||||
|
||||
private struct AStarNode
|
||||
{
|
||||
public int Index;
|
||||
|
||||
public int2 Tile;
|
||||
|
||||
public int Version;
|
||||
|
||||
public int Parent;
|
||||
|
||||
public int G;
|
||||
|
||||
public bool IsClosed;
|
||||
|
||||
public void Clear(int version)
|
||||
{
|
||||
Version = version;
|
||||
Parent = -1;
|
||||
G = int.MaxValue;
|
||||
IsClosed = false;
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct PathJob : IJobParallelFor
|
||||
{
|
||||
private const int StraightStepCost = 10;
|
||||
|
||||
private const int DiagonalStepCost = 14;
|
||||
|
||||
public int Version;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<PathQuery> Queries;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeArray<PathQueryResult> Results;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<AStarNode> NodeCache;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeStream PathStream;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<BlockReason> BlockMap;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<FertilityMapValue> Fertility;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<EntityIdMapValue> EntityIdMap;
|
||||
|
||||
public int2 WorldSize;
|
||||
|
||||
[BurstCompile]
|
||||
public void Execute(int index)
|
||||
{
|
||||
var query = Queries[index];
|
||||
|
||||
var tileCount = WorldSize.x * WorldSize.y;
|
||||
var nodes = new NativeSlice<AStarNode>(NodeCache, tileCount * index, tileCount);
|
||||
|
||||
var sourceIndex = GetNodeIndex(query.SourceTile);
|
||||
ref var source = ref GetNodeRW(nodes, sourceIndex);
|
||||
if (IsGoal(query, source))
|
||||
{
|
||||
Results[index] = PathQueryResult.Success;
|
||||
return;
|
||||
}
|
||||
|
||||
source.Clear(Version);
|
||||
source.G = 0;
|
||||
var open = new NativePriorityQueue((int)(0.2f * tileCount), Allocator.Temp);
|
||||
open.Add(sourceIndex, GetHScore(query.SourceTile, query.DestinationRect, query.SearchType));
|
||||
|
||||
var directions = (query.TraversalRules & PathTraversalRules.CardinalDirectionsOnly) != 0 ? DirectionVectors.Directions4 : DirectionVectors.Directions8;
|
||||
var pathFound = false;
|
||||
|
||||
while (open.TryPop(out var currentIndex))
|
||||
{
|
||||
ref var current = ref GetNodeRW(nodes, currentIndex);
|
||||
|
||||
current.IsClosed = true;
|
||||
|
||||
foreach (var direction in directions)
|
||||
{
|
||||
var neighborTile = current.Tile + direction;
|
||||
if (!ValidateTile(query.SourceTile, neighborTile, query.MaxDistance)) continue;
|
||||
|
||||
ref var neighbor = ref GetNodeRW(nodes, neighborTile);
|
||||
if (neighbor.Version < Version) neighbor.Clear(Version);
|
||||
|
||||
if (neighbor.IsClosed) continue;
|
||||
|
||||
var moveScore = EvaluateMove(currentIndex, neighbor.Index, query.TargetId, query.TraversalRules);
|
||||
if (moveScore == int.MaxValue) continue;
|
||||
|
||||
if (IsGoal(query, neighbor))
|
||||
{
|
||||
neighbor.Parent = currentIndex;
|
||||
WritePath(index, query, nodes, neighbor);
|
||||
pathFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var tentativeG = current.G + moveScore;
|
||||
if (tentativeG >= neighbor.G) continue;
|
||||
|
||||
neighbor.Parent = currentIndex;
|
||||
neighbor.G = tentativeG;
|
||||
|
||||
var f = tentativeG + GetHScore(neighborTile, query.DestinationRect, query.SearchType);
|
||||
open.Add(neighbor.Index, f);
|
||||
}
|
||||
|
||||
if (pathFound) break;
|
||||
}
|
||||
|
||||
Results[index] = pathFound ? PathQueryResult.Success : PathQueryResult.Failure;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsGoal(in PathQuery query, in AStarNode node)
|
||||
{
|
||||
if (query.SearchType == PathSearchType.FindPath && query.DestinationRect.Contains(node.Tile)) return true;
|
||||
|
||||
var entityIdValue = EntityIdMap[node.Index];
|
||||
switch (query.SearchType)
|
||||
{
|
||||
case PathSearchType.SearchResourceNode:
|
||||
return entityIdValue.ResourceNodeId == query.TargetId;
|
||||
|
||||
case PathSearchType.SearchFertileTile:
|
||||
var fertility = Fertility[node.Index];
|
||||
return fertility.MaxFertility > 0 && fertility.CurrentFertility >= fertility.MaxFertility && fertility.LockId == Entity.InvalidId;
|
||||
|
||||
case PathSearchType.SearchCritterHerd:
|
||||
return entityIdValue.CritterHerdId == query.TargetId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool ValidateTile(int2 pathSource, int2 tile, int maxDistance)
|
||||
{
|
||||
return math.all(tile >= 0) && math.all(tile < WorldSize) && (maxDistance <= 0 || TileMath.StepCount(pathSource, tile) <= maxDistance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetNodeIndex(int2 tile)
|
||||
{
|
||||
return math.mad(tile.y, WorldSize.x, tile.x);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref AStarNode GetNodeRW(in NativeSlice<AStarNode> nodes, int index)
|
||||
{
|
||||
return ref UnsafeUtility.ArrayElementAsRef<AStarNode>(nodes.GetUnsafePtr(), index);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref AStarNode GetNodeRW(in NativeSlice<AStarNode> nodes, int2 tile)
|
||||
{
|
||||
return ref GetNodeRW(nodes, GetNodeIndex(tile));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int EvaluateMove(int leavingIndex, int enteringIndex, int targetId, PathTraversalRules rules)
|
||||
{
|
||||
var enteringBuildingId = EntityIdMap[enteringIndex].BuildingId;
|
||||
if (enteringBuildingId != Entity.InvalidId)
|
||||
{
|
||||
var leavingBuildingId = EntityIdMap[leavingIndex].BuildingId;
|
||||
if (enteringBuildingId == leavingBuildingId || (enteringBuildingId == targetId && leavingBuildingId == Entity.InvalidId)) return 0;
|
||||
}
|
||||
|
||||
var blockReason = BlockMap[enteringIndex];
|
||||
|
||||
var blocksAgents = (blockReason & BlockReason.BlocksAgents) != 0;
|
||||
if (blocksAgents) return int.MaxValue;
|
||||
|
||||
var roadsOnly = (rules & PathTraversalRules.RoadsOnly) != 0;
|
||||
var isRoad = (blockReason & BlockReason.Road) != 0;
|
||||
if (roadsOnly && !isRoad) return int.MaxValue;
|
||||
|
||||
var fertileTilesOnly = (rules & PathTraversalRules.FertileTilesOnly) != 0;
|
||||
if (fertileTilesOnly && Fertility[enteringIndex].MaxFertility <= 0) return int.MaxValue;
|
||||
|
||||
return StepCost(leavingIndex, enteringIndex);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int StepCost(int leavingIndex, int enteringIndex)
|
||||
{
|
||||
var delta = math.abs(leavingIndex - enteringIndex);
|
||||
return delta == 1 || delta == WorldSize.x ? StraightStepCost : DiagonalStepCost;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetHScore(int2 tile, TileRect rect, PathSearchType searchType)
|
||||
{
|
||||
return searchType switch
|
||||
{
|
||||
PathSearchType.FindPath => StraightStepCost * TileMath.StepCount(tile, rect),
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
private void WritePath(int threadIndex, in PathQuery query, in NativeSlice<AStarNode> nodes, in AStarNode current)
|
||||
{
|
||||
var temp = new NativeList<int2>(Allocator.Temp);
|
||||
while (true)
|
||||
{
|
||||
if ((BlockMap[current.Index] & BlockReason.BlocksAgents) == 0 || EntityIdMap[current.Index].BuildingId == query.TargetId) temp.Add(current.Tile);
|
||||
|
||||
if (math.all(current.Tile == query.SourceTile)) break;
|
||||
|
||||
current = ref GetNodeRW(nodes, current.Parent);
|
||||
}
|
||||
|
||||
var pathWriter = PathStream.AsWriter();
|
||||
pathWriter.BeginForEachIndex(threadIndex);
|
||||
for (var i = temp.Length - 1; i >= 0; i--) pathWriter.Write(temp[i]);
|
||||
pathWriter.EndForEachIndex();
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct NativePriorityQueue : IDisposable
|
||||
{
|
||||
private NativeList<Item> _items;
|
||||
|
||||
public NativePriorityQueue(int capacity, Allocator allocator)
|
||||
{
|
||||
_items = new NativeList<Item>(capacity, allocator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose()
|
||||
{
|
||||
_items.Dispose();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Clear()
|
||||
{
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(int index, int score)
|
||||
{
|
||||
var i = _items.Length;
|
||||
_items.Add(new Item(index, score));
|
||||
BubbleUp(i);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryPop(out int index)
|
||||
{
|
||||
var length = _items.Length;
|
||||
if (length == 0)
|
||||
{
|
||||
index = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
index = _items[0].Index;
|
||||
|
||||
var last = length - 1;
|
||||
_items[0] = _items[last];
|
||||
_items.RemoveAt(last);
|
||||
|
||||
if (_items.Length > 0) BubbleDown(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void BubbleUp(int i)
|
||||
{
|
||||
while (i > 0)
|
||||
{
|
||||
var parent = (i - 1) >> 1;
|
||||
if (_items[i].Score >= _items[parent].Score) break;
|
||||
(_items[i], _items[parent]) = (_items[parent], _items[i]);
|
||||
i = parent;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void BubbleDown(int i)
|
||||
{
|
||||
var count = _items.Length;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var left = (i << 1) + 1;
|
||||
var right = left + 1;
|
||||
var smallest = i;
|
||||
|
||||
if (left < count && _items[left].Score < _items[smallest].Score) smallest = left;
|
||||
if (right < count && _items[right].Score < _items[smallest].Score) smallest = right;
|
||||
|
||||
if (smallest == i) break;
|
||||
|
||||
(_items[i], _items[smallest]) = (_items[smallest], _items[i]);
|
||||
i = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
private struct Item
|
||||
{
|
||||
public int Index;
|
||||
|
||||
public int Score;
|
||||
|
||||
public Item(int index, int score)
|
||||
{
|
||||
Index = index;
|
||||
Score = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Source/Riversong/Game/World/Pathfinding/TilePath.cs
Normal file
29
Source/Riversong/Game/World/Pathfinding/TilePath.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct TilePath : IDisposable
|
||||
{
|
||||
private const int InitialCapacity = 50;
|
||||
|
||||
public NativeList<int2> Steps;
|
||||
|
||||
public int StepCount => Steps.Length;
|
||||
|
||||
public static TilePath Initialize(Allocator allocator = Allocator.Persistent)
|
||||
{
|
||||
return new TilePath { Steps = new NativeList<int2>(InitialCapacity, allocator) };
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Steps.IsCreated)
|
||||
{
|
||||
Steps.Dispose();
|
||||
Steps = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user