Skip to content

Commit

Permalink
RingBuffer primitive.
Browse files Browse the repository at this point in the history
  • Loading branch information
anvilvapre authored and PunkPun committed Aug 2, 2023
1 parent 09ba09f commit 1ce9161
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 31 deletions.
146 changes: 146 additions & 0 deletions OpenRA.Game/Primitives/RingBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion

using System;
using System.Collections.Generic;

namespace OpenRA.Primitives
{
/// <summary>Fixed size rorating buffer backed by an array.</summary>
public class RingBuffer<T> : ICollection<T>, IEnumerable<T>
{
readonly IComparer<T> comparer;
readonly T[] values;
int start;

public int Capacity => values.Length;
public int Count { get; private set; }
public bool IsReadOnly => false;

public RingBuffer(int capacity, IComparer<T> comparer)
{
this.comparer = comparer;
values = new T[capacity];
start = 0;
Count = 0;
}

public RingBuffer(int capacity)
: this(capacity, Comparer<T>.Default) { }

public void Add(T value)
{
values[(start + Count) % values.Length] = value;
if (Count < values.Length)
Count++;
else
start = (start + 1) % values.Length;
}

public void Clear()
{
Array.Clear(values, 0, values.Length);
start = 0;
Count = 0;
}

public bool Contains(T value)
{
var capacity = values.Length;
var end = start + Count;
for (var i = start; i < end; ++i)
if (comparer.Compare(values[i % capacity], value) == 0)
return true;

return false;
}

public void CopyTo(T[] array, int arrayIndex)
{
if (array == null)
throw new ArgumentNullException(nameof(array));

if (arrayIndex < 0)
throw new ArgumentNullException(nameof(arrayIndex));

if (arrayIndex + Count >= array.Length)
throw new ArgumentException("Invalid array capacity");

var destinationIndex = arrayIndex;
var end = start + Count;
var capacity = values.Length;
for (var i = start; i < end; ++i)
array[destinationIndex++] = values[i % capacity];
}

public bool Remove(T value)
{
var capacity = values.Length;
var end = start + Count;
for (var i = start; i < end; ++i)
{
if (comparer.Compare(values[i % capacity], value) == 0)
{
end--;
for (var j = i; j < end; ++j)
values[j % capacity] = values[(j + 1) % capacity];

Count--;
return true;
}
}

return false;
}

public T this[int pos]
{
get => values[(start + pos) % values.Length];

set
{
if (pos >= Count)
throw new ArgumentException($"Index out of bounds: {pos}");

values[(start + pos) % values.Length] = value;
}
}

public T First()
{
if (Count == 0)
throw new ArgumentException("Empty buffer");

return values[start];
}

public T Last()
{
if (Count == 0)
throw new ArgumentException("Empty buffer");

return values[(start + Count - 1) % values.Length];
}

public IEnumerator<T> GetEnumerator()
{
var initState = start + Count;
for (var i = 0; i < Count; i++)
{
if (start + Count != initState)
throw new InvalidOperationException("Collection was modified; enumeration operation may not execute");
yield return values[(start + i) % values.Length];
}
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
}
2 changes: 1 addition & 1 deletion OpenRA.Game/WAngle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public WAngle(int a)
public static WAngle operator -(WAngle a) { return new WAngle(-a.Angle); }

public static bool operator ==(WAngle me, WAngle other) { return me.Angle == other.Angle; }
public static bool operator !=(WAngle me, WAngle other) { return !(me == other); }
public static bool operator !=(WAngle me, WAngle other) { return me.Angle != other.Angle; }

public override int GetHashCode() { return Angle.GetHashCode(); }

Expand Down
36 changes: 11 additions & 25 deletions OpenRA.Mods.Common/Activities/Air/Fly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#endregion

using System.Collections.Generic;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
Expand All @@ -29,7 +28,7 @@ public class Fly : Activity
Target target;
Target lastVisibleTarget;
bool useLastVisibleTarget;
readonly List<WPos> positionBuffer = new();
readonly RingBuffer<WPos> previousPositions = new(5);

public Fly(Actor self, in Target t, WDist nearEnough, WPos? initialTargetPosition = null, Color? targetLineColor = null)
: this(self, t, initialTargetPosition, targetLineColor)
Expand Down Expand Up @@ -63,13 +62,10 @@ public Fly(Actor self, in Target t, WPos? initialTargetPosition = null, Color? t
public static void FlyTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, in WVec moveOverride, bool idleTurn = false)
{
var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition);
var move = aircraft.Info.CanSlide ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing);
if (moveOverride != WVec.Zero)
move = moveOverride;
var move = moveOverride != WVec.Zero ? moveOverride : (aircraft.Info.CanSlide ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing));

var oldFacing = aircraft.Facing;
var turnSpeed = aircraft.GetTurnSpeed(idleTurn);
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, turnSpeed);
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.GetTurnSpeed(idleTurn));

var roll = idleTurn ? aircraft.Info.IdleRoll ?? aircraft.Info.Roll : aircraft.Info.Roll;
if (roll != WAngle.Zero)
Expand Down Expand Up @@ -104,22 +100,16 @@ public static void FlyTick(Actor self, Aircraft aircraft, WAngle desiredFacing,
// Should only be used for vertical-only movement, usually VTOL take-off or land. Terrain-induced altitude changes should always be handled by FlyTick.
public static bool VerticalTakeOffOrLandTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, bool idleTurn = false)
{
var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition);
var move = WVec.Zero;

var turnSpeed = idleTurn ? aircraft.IdleTurnSpeed ?? aircraft.TurnSpeed : aircraft.TurnSpeed;
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, turnSpeed);

if (dat != desiredAltitude)
{
var maxDelta = aircraft.Info.AltitudeVelocity.Length;
var deltaZ = (desiredAltitude.Length - dat.Length).Clamp(-maxDelta, maxDelta);
move += new WVec(0, 0, deltaZ);
}
else
var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition);
if (dat == desiredAltitude)
return false;

aircraft.SetPosition(self, aircraft.CenterPosition + move);
var maxDelta = aircraft.Info.AltitudeVelocity.Length;
var deltaZ = (desiredAltitude.Length - dat.Length).Clamp(-maxDelta, maxDelta);
aircraft.SetPosition(self, aircraft.CenterPosition + new WVec(0, 0, deltaZ));
return true;
}

Expand Down Expand Up @@ -203,8 +193,7 @@ public override bool Tick(Actor self)

// HACK: Consider ourselves blocked if we have moved by less than 64 WDist in the last five ticks
// Stop if we are blocked and close enough
if (positionBuffer.Count >= 5 && (positionBuffer.Last() - positionBuffer[0]).LengthSquared < 4096 &&
delta.HorizontalLengthSquared <= nearEnough.LengthSquared)
if (previousPositions.Count == previousPositions.Capacity && (previousPositions.First() - previousPositions.Last()).LengthSquared < 4096 && delta.HorizontalLengthSquared <= nearEnough.LengthSquared)
return true;

// The next move would overshoot, so consider it close enough or set final position if we CanSlide
Expand Down Expand Up @@ -253,11 +242,8 @@ public override bool Tick(Actor self)
desiredFacing = aircraft.Facing;
}

positionBuffer.Add(self.CenterPosition);
if (positionBuffer.Count > 5)
positionBuffer.RemoveAt(0);

FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude);
previousPositions.Add(self.CenterPosition);
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, WVec.Zero);

return false;
}
Expand Down
17 changes: 12 additions & 5 deletions OpenRA.Mods.Common/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ public static class Util
public static int TickFacing(int facing, int desiredFacing, int rot)
{
var leftTurn = (facing - desiredFacing) & 0xFF;
if (leftTurn < rot)
return desiredFacing & 0xFF;

var rightTurn = (desiredFacing - facing) & 0xFF;
if (Math.Min(leftTurn, rightTurn) < rot)
if (rightTurn < rot)
return desiredFacing & 0xFF;
else if (rightTurn < leftTurn)

if (rightTurn < leftTurn)
return (facing + rot) & 0xFF;
else
return (facing - rot) & 0xFF;

return (facing - rot) & 0xFF;
}

/// <summary>
Expand All @@ -46,8 +50,11 @@ public static int TickFacing(int facing, int desiredFacing, int rot)
public static WAngle TickFacing(WAngle facing, WAngle desiredFacing, WAngle step)
{
var leftTurn = (facing - desiredFacing).Angle;
if (leftTurn < step.Angle)
return desiredFacing;

var rightTurn = (desiredFacing - facing).Angle;
if (leftTurn < step.Angle || rightTurn < step.Angle)
if (rightTurn < step.Angle)
return desiredFacing;

return rightTurn < leftTurn ? facing + step : facing - step;
Expand Down

0 comments on commit 1ce9161

Please sign in to comment.