Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimization (breaking): pool RcSpans. #63

Merged
merged 1 commit into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/DotRecast.Recast/RcCompacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,26 +91,25 @@ public static RcCompactHeightfield BuildCompactHeightfield(RcContext context, in
int numColumns = xSize * zSize;
for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex)
{
RcSpan span = heightfield.spans[columnIndex];
uint spanIndex = heightfield.spans[columnIndex];

// If there are no spans at this cell, just leave the data to index=0, count=0.
if (span == null)
continue;

int tmpIdx = currentCellIndex;
int tmpCount = 0;
for (; span != null; span = span.next)
while (spanIndex != 0)
{
ref var span = ref heightfield.Span(spanIndex);
if (span.area != RC_NULL_AREA)
{
int bot = span.smax;
int top = span.next != null ? (int)span.next.smin : MAX_HEIGHT;
int top = span.next != 0 ? heightfield.Span(span.next).smin : MAX_HEIGHT;
tempSpans[currentCellIndex].y = Math.Clamp(bot, 0, MAX_HEIGHT);
tempSpans[currentCellIndex].h = Math.Clamp(top - bot, 0, MAX_HEIGHT);
compactHeightfield.areas[currentCellIndex] = span.area;
currentCellIndex++;
tmpCount++;
}
spanIndex = span.next;
}

compactHeightfield.cells[columnIndex] = new RcCompactCell(tmpIdx, tmpCount);
Expand Down Expand Up @@ -191,9 +190,9 @@ private static int GetHeightFieldSpanCount(RcContext context, RcHeightfield heig
int spanCount = 0;
for (int columnIndex = 0; columnIndex < numCols; ++columnIndex)
{
for (RcSpan span = heightfield.spans[columnIndex]; span != null; span = span.next)
for (uint span = heightfield.spans[columnIndex]; span != 0; span = heightfield.Span(span).next)
{
if (span.area != RC_NULL_AREA)
if (heightfield.Span(span).area != RC_NULL_AREA)
{
spanCount++;
}
Expand Down
31 changes: 17 additions & 14 deletions src/DotRecast.Recast/RcFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,25 @@ public static void FilterLowHangingWalkableObstacles(RcContext context, int walk
{
for (int x = 0; x < xSize; ++x)
{
RcSpan previousSpan = null;
uint previousSpan = 0;
bool previousWasWalkable = false;
int previousAreaID = RC_NULL_AREA;

// For each span in the column...
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; previousSpan = span, span = span.next)
for (uint span = heightfield.spans[x + z * xSize]; span != 0; previousSpan = span, span = heightfield.Span(span).next)
{
bool walkable = span.area != RC_NULL_AREA;
bool walkable = heightfield.Span(span).area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable span just below it and the height difference
// is small enough for the agent to walk over, mark the current span as walkable too.
if (!walkable && previousWasWalkable && span.smax - previousSpan.smax <= walkableClimb)
if (!walkable && previousWasWalkable && heightfield.Span(span).smax - heightfield.Span(previousSpan).smax <= walkableClimb)
{
span.area = previousAreaID;
heightfield.Span(span).area = previousAreaID;
}

// Copy the original walkable value regardless of whether we changed it.
// This prevents multiple consecutive non-walkable spans from being erroneously marked as walkable.
previousWasWalkable = walkable;
previousAreaID = span.area;
previousAreaID = heightfield.Span(span).area;
}
}
}
Expand Down Expand Up @@ -110,16 +110,17 @@ public static void FilterLedgeSpans(RcContext context, int walkableHeight, int w
{
for (int x = 0; x < xSize; ++x)
{
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next)
for (uint spanIndex = heightfield.spans[x + z * xSize]; spanIndex != 0; spanIndex = heightfield.Span(spanIndex).next)
{
// Skip non-walkable spans.
ref var span = ref heightfield.Span(spanIndex);
if (span.area == RC_NULL_AREA)
{
continue;
}

int floor = (span.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT;
int ceiling = span.next != 0 ? heightfield.Span(span.next).smin : RC_SPAN_MAX_HEIGHT;

// The difference between this walkable area and the lowest neighbor walkable area.
// This is the difference between the current span and all neighbor spans that have
Expand All @@ -142,11 +143,11 @@ public static void FilterLedgeSpans(RcContext context, int walkableHeight, int w
break;
}

RcSpan neighborSpan = heightfield.spans[neighborX + neighborZ * xSize];
uint neighborSpanIndex = heightfield.spans[neighborX + neighborZ * xSize];

// The most we can step down to the neighbor is the walkableClimb distance.
// Start with the area under the neighbor span
int neighborCeiling = neighborSpan != null ? neighborSpan.smin : RC_SPAN_MAX_HEIGHT;
int neighborCeiling = neighborSpanIndex != 0 ? heightfield.Span(neighborSpanIndex).smin : RC_SPAN_MAX_HEIGHT;

// Skip neightbour if the gap between the spans is too small.
if (Math.Min(ceiling, neighborCeiling) - floor >= walkableHeight)
Expand All @@ -156,10 +157,11 @@ public static void FilterLedgeSpans(RcContext context, int walkableHeight, int w
}

// For each span in the neighboring column...
for (; neighborSpan != null; neighborSpan = neighborSpan.next)
for (; neighborSpanIndex != 0; neighborSpanIndex = heightfield.Span(neighborSpanIndex).next)
{
ref var neighborSpan = ref heightfield.Span(neighborSpanIndex);
int neighborFloor = neighborSpan.smax;
neighborCeiling = neighborSpan.next != null ? neighborSpan.next.smin : RC_SPAN_MAX_HEIGHT;
neighborCeiling = neighborSpan.next != 0 ? heightfield.Span(neighborSpan.next).smin : RC_SPAN_MAX_HEIGHT;

// Only consider neighboring areas that have enough overlap to be potentially traversable.
if (Math.Min(ceiling, neighborCeiling) - Math.Max(floor, neighborFloor) < walkableHeight)
Expand Down Expand Up @@ -232,10 +234,11 @@ public static void FilterWalkableLowHeightSpans(RcContext context, int walkableH
{
for (int x = 0; x < xSize; ++x)
{
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next)
for (uint spanIndex = heightfield.spans[x + z * xSize]; spanIndex != 0; spanIndex = heightfield.Span(spanIndex).next)
{
ref var span = ref heightfield.Span(spanIndex);
int floor = (span.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT;
int ceiling = span.next != 0 ? heightfield.Span(span.next).smin : RC_SPAN_MAX_HEIGHT;
if ((ceiling - floor) < walkableHeight)
{
span.area = RC_NULL_AREA;
Expand Down
12 changes: 9 additions & 3 deletions src/DotRecast.Recast/RcHeightfield.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ public class RcHeightfield
public readonly float ch;

/** Heightfield of spans (width*height). */
public readonly RcSpan[] spans;
public readonly uint[] spans;

/** Actual span storage. */
public readonly RcSpanPool spanPool;

/** Border size in cell units */
public readonly int borderSize;
Expand All @@ -58,7 +61,10 @@ public RcHeightfield(int width, int height, RcVec3f bmin, RcVec3f bmax, float cs
this.cs = cs;
this.ch = ch;
this.borderSize = borderSize;
spans = new RcSpan[width * height];
spans = new uint[width * height];
spanPool = new RcSpanPool();
}

public ref RcSpan Span(uint index) => ref spanPool.Span(index);
}
}
}
37 changes: 20 additions & 17 deletions src/DotRecast.Recast/RcRasterizations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,29 @@ private static bool OverlapBounds(RcVec3f aMin, RcVec3f aMax, RcVec3f bMin, RcVe
public static void AddSpan(RcHeightfield heightfield, int x, int z, int min, int max, int areaID, int flagMergeThreshold)
{
// Create the new span.
RcSpan newSpan = new RcSpan();
uint newSpanIndex = heightfield.spanPool.Alloc();
ref RcSpan newSpan = ref heightfield.Span(newSpanIndex);
newSpan.smin = min;
newSpan.smax = max;
newSpan.area = areaID;
newSpan.next = null;
newSpan.next = 0;

int columnIndex = x + z * heightfield.width;

// Empty cell, add the first span.
if (heightfield.spans[columnIndex] == null)
if (heightfield.spans[columnIndex] == 0)
{
heightfield.spans[columnIndex] = newSpan;
heightfield.spans[columnIndex] = newSpanIndex;
return;
}

RcSpan previousSpan = null;
RcSpan currentSpan = heightfield.spans[columnIndex];
uint previousSpanIndex = 0;
uint currentSpanIndex = heightfield.spans[columnIndex];

// Insert the new span, possibly merging it with existing spans.
while (currentSpan != null)
while (currentSpanIndex != 0)
{
ref var currentSpan = ref heightfield.Span(currentSpanIndex);
if (currentSpan.smin > newSpan.smax)
{
// Current span is further than the new span, break.
Expand All @@ -86,8 +88,8 @@ public static void AddSpan(RcHeightfield heightfield, int x, int z, int min, int
if (currentSpan.smax < newSpan.smin)
{
// Current span is completely before the new span. Keep going.
previousSpan = currentSpan;
currentSpan = currentSpan.next;
previousSpanIndex = currentSpanIndex;
currentSpanIndex = currentSpan.next;
}
else
{
Expand All @@ -111,31 +113,32 @@ public static void AddSpan(RcHeightfield heightfield, int x, int z, int min, int

// Remove the current span since it's now merged with newSpan.
// Keep going because there might be other overlapping spans that also need to be merged.
RcSpan next = currentSpan.next;
if (previousSpan != null)
uint next = currentSpan.next;
if (previousSpanIndex != 0)
{
previousSpan.next = next;
heightfield.Span(previousSpanIndex).next = next;
}
else
{
heightfield.spans[columnIndex] = next;
}
heightfield.spanPool.Free(currentSpanIndex);

currentSpan = next;
currentSpanIndex = next;
}
}

// Insert new span after prev
if (previousSpan != null)
if (previousSpanIndex != 0)
{
newSpan.next = previousSpan.next;
previousSpan.next = newSpan;
newSpan.next = heightfield.Span(previousSpanIndex).next;
heightfield.Span(previousSpanIndex).next = newSpanIndex;
}
else
{
// This span should go before the others in the list
newSpan.next = heightfield.spans[columnIndex];
heightfield.spans[columnIndex] = newSpan;
heightfield.spans[columnIndex] = newSpanIndex;
}
}

Expand Down
56 changes: 52 additions & 4 deletions src/DotRecast.Recast/RcSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

using System;

namespace DotRecast.Recast
{
/** Represents a span in a heightfield. */
public class RcSpan
public struct RcSpan
{
/** The lower limit of the span. [Limit: &lt; smax] */
public int smin;
Expand All @@ -32,7 +34,53 @@ public class RcSpan
/** The area id assigned to the span. */
public int area;

/** The next span higher up in column. */
public RcSpan next;
/** The index next span higher up in column. For span in the free list, this is the index of the next free span. */
public uint next;
}

/** A pool of spans. Index 0 is reserved to mean 'null'. */
public class RcSpanPool
{
private RcSpan[] storage = new RcSpan[64 * 1024];
private uint firstUnalloc = 1; // storage element with index >= this is not initialized

public RcSpanPool()
{
storage[0].next = firstUnalloc;
}

public ref RcSpan Span(uint index) => ref storage[index];

public uint Alloc()
{
var index = storage[0].next;
if (index < firstUnalloc)
{
// get span from the free list
storage[0].next = storage[index].next;
storage[index].next = 0;
return index;
}

// free list is empty
if (storage.Length == firstUnalloc)
{
// and storage is full => grow
var oldStorage = storage;
storage = new RcSpan[oldStorage.Length * 2];
Array.Copy(oldStorage, storage, oldStorage.Length);
}

// index == firstUnalloc => storage[index] == 0 (never initialized)
storage[0].next = ++firstUnalloc;
return index;
}

public void Free(uint index)
{
// push span to the head of the free list
storage[index].next = storage[0].next;
storage[0].next = index;
}
}
}
}
Loading