Skip to content

Commit 970ecbb

Browse files
Merge pull request #976 from FFXIV-CombatReborn/mergeWIP
perf improvements
2 parents 8ba9b1c + e0b854a commit 970ecbb

File tree

15 files changed

+375
-298
lines changed

15 files changed

+375
-298
lines changed

BossMod/BossModule/AIHints.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public readonly struct DamagePrediction(BitMask players, DateTime activation, Pr
5151
public readonly PredictedDamageType Type = type;
5252
}
5353

54-
public static readonly ArenaBounds DefaultBounds = new ArenaBoundsSquare(30f, AllowObstacleMap: true);
54+
public static readonly ArenaBounds DefaultBounds = new ArenaBoundsSquare(30f, allowObstacleMap: true);
5555

5656
// information needed to build base pathfinding map (onto which forbidden/goal zones are later rasterized), if needed (lazy, since it's somewhat expensive and not always needed)
5757
public WPos PathfindMapCenter;

BossMod/BossModule/AIHintsBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ private void CalculateAutoHints(AIHints hints, Actor player)
166166
originZ = Math.Max(originZ, e.ViewHeight);
167167
// TODO: consider quantizing even more, to reduce jittering when player moves?..
168168
hints.PathfindMapCenter = e.Origin + resolution * new WDir(originX, originZ);
169-
hints.PathfindMapBounds = new ArenaBoundsRect(e.ViewWidth * resolution, e.ViewHeight * resolution, MapResolution: resolution); // note: we don't bother caching these bounds, they are very lightweight
169+
hints.PathfindMapBounds = new ArenaBoundsRect(e.ViewWidth * resolution, e.ViewHeight * resolution, mapResolution: resolution); // note: we don't bother caching these bounds, they are very lightweight
170170
hints.PathfindMapObstacles = new(bitmap, new(originX - e.ViewWidth, originZ - e.ViewHeight, originX + e.ViewWidth, originZ + e.ViewHeight));
171171
}
172172
else

BossMod/BossModule/ArenaBounds.cs

Lines changed: 123 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22
// radius is the largest horizontal/vertical dimension: radius for circle, max of width/height for rect
33
// note: this class to represent *relative* arena bounds (relative to arena center) - the reason being that in some cases effective center moves every frame, and bounds caches a lot (clip poly & base map for pathfinding)
44
// note: if arena bounds are changed, new instance is recreated; max approx error can change without recreating the instance
5-
public abstract record class ArenaBounds(float Radius, float MapResolution, float ScaleFactor = 1f, bool AllowObstacleMap = false)
5+
public abstract class ArenaBounds(float radius, float mapResolution, float scaleFactor = 1f, bool allowObstacleMap = false)
66
{
7+
public readonly float Radius = radius;
8+
public readonly float MapResolution = mapResolution;
9+
public readonly float ScaleFactor = scaleFactor;
10+
public readonly bool AllowObstacleMap = allowObstacleMap;
11+
712
// fields below are used for clipping & drawing borders
813
public readonly PolygonClipper Clipper = new();
914
public float MaxApproxError;
@@ -142,7 +147,7 @@ public void AddToInstanceCache(object key, object value)
142147
}
143148
}
144149

145-
public sealed record class ArenaBoundsCircle(float Radius, float MapResolution = 0.5f, bool AllowObstacleMap = false) : ArenaBounds(Radius, MapResolution, AllowObstacleMap: AllowObstacleMap)
150+
public sealed class ArenaBoundsCircle(float Radius, float MapResolution = 0.5f, bool AllowObstacleMap = false) : ArenaBounds(Radius, MapResolution, allowObstacleMap: AllowObstacleMap)
146151
{
147152
private Pathfinding.Map? _cachedMap;
148153

@@ -159,7 +164,9 @@ public override WDir ClampToBounds(WDir offset)
159164
{
160165
var radius = Radius;
161166
if (offset.LengthSq() > radius * radius)
167+
{
162168
offset *= radius / offset.Length();
169+
}
163170
return offset;
164171
}
165172

@@ -187,20 +194,32 @@ private Pathfinding.Map BuildMap()
187194
var cx = Math.Abs(dx2) + 0.5f;
188195
if (cx * cx + cySq > threshold)
189196
{
190-
map.PixelMaxG[iCell] = -1f;
197+
map.PixelMaxG[iCell] = -1000f;
191198
}
192199
++iCell;
193200
}
194201
}
195202
return map;
196203
}
204+
205+
public override string ToString() => $"{nameof(ArenaBoundsCircle)}, Radius {Radius}, MapResolution: {MapResolution}";
197206
}
198207

199208
// if rotation is 0, half-width is along X and half-height is along Z
200-
public record class ArenaBoundsRect(float HalfWidth, float HalfHeight, Angle Rotation = default, float MapResolution = 0.5f, bool AllowObstacleMap = false) : ArenaBounds(Math.Max(HalfWidth, HalfHeight), MapResolution, Rotation != default ? CalculateScaleFactor(Rotation) : 1f, AllowObstacleMap)
209+
public abstract class ABRect : ArenaBounds
201210
{
211+
public ABRect(float halfWidth, float halfHeight, Angle rotation = default, float MapResolution = 0.5f, bool AllowObstacleMap = false) : base(Math.Max(halfWidth, halfHeight), MapResolution, rotation != default ? CalculateScaleFactor(rotation) : 1f, AllowObstacleMap)
212+
{
213+
HalfWidth = halfWidth;
214+
HalfHeight = halfHeight;
215+
Rotation = rotation;
216+
Orientation = Rotation.ToDirection();
217+
}
218+
public readonly float HalfWidth;
219+
public readonly float HalfHeight;
220+
public readonly Angle Rotation;
202221
private Pathfinding.Map? _cachedMap;
203-
public readonly WDir Orientation = Rotation.ToDirection();
222+
public readonly WDir Orientation;
204223

205224
private static float CalculateScaleFactor(Angle Rotation)
206225
{
@@ -210,13 +229,49 @@ private static float CalculateScaleFactor(Angle Rotation)
210229

211230
protected override PolygonClipper.Operand BuildClipPoly() => new(CurveApprox.Rect(Orientation, HalfWidth, HalfHeight));
212231
public override void PathfindMap(Pathfinding.Map map, WPos center) => map.Init(_cachedMap ??= BuildMap(), center);
232+
213233
private Pathfinding.Map BuildMap()
214234
{
215235
var halfWidth = HalfWidth;
216236
var halfHeight = HalfHeight;
217-
var rotation = Rotation;
218-
var map = new Pathfinding.Map(MapResolution, default, halfWidth + 0.5f, halfHeight + 0.5f, rotation);
219-
map.BlockPixelsInside(new SDInvertedRect(default, rotation, halfHeight, halfHeight, halfWidth), -1f, 0.49999f * MapResolution);
237+
var map = new Pathfinding.Map(MapResolution, default, halfWidth + 0.5f, halfHeight + 0.5f, Rotation); // +0.5 offset because otherwise the AI will not run back into the rectangle for some reason
238+
// pixels can be partially covered by the rectangle, so we need to rasterize it carefully
239+
var width = map.Width;
240+
var height = map.Height;
241+
var resolution = map.Resolution;
242+
243+
var dir = Rotation.ToDirection();
244+
var dirX = dir.X;
245+
var dirZ = dir.Z;
246+
var normal = dir.OrthoL();
247+
var normalX = normal.X;
248+
var normalZ = normal.Z;
249+
250+
var dx = normal * resolution;
251+
var dy = dir * resolution;
252+
var startPos = map.Center - ((width >> 1) - 0.5f) * dx - ((height >> 1) - 0.5f) * dy;
253+
var halfPixel = 0.5f * resolution;
254+
255+
for (var y = 0; y < height; ++y)
256+
{
257+
var posY = startPos + y * dy;
258+
var rowBase = y * width;
259+
for (var x = 0; x < width; ++x)
260+
{
261+
var pos = posY + x * dx;
262+
var pX = pos.X;
263+
var pZ = pos.Z;
264+
265+
var distParr = pX * dirX + pZ * dirZ;
266+
var distOrtho = pX * normalX + pZ * normalZ;
267+
268+
if (!((distParr - halfPixel) >= -halfHeight && (distParr + halfPixel) <= halfHeight) || !((distOrtho - halfPixel) >= -halfWidth && (distOrtho + halfPixel) <= halfWidth))
269+
{
270+
map.PixelMaxG[rowBase + x] = -1000f;
271+
}
272+
}
273+
}
274+
220275
return map;
221276
}
222277

@@ -231,22 +286,33 @@ public override WDir ClampToBounds(WDir offset)
231286
var offsetX = offset.Dot(orientation.OrthoL());
232287
var offsetY = offset.Dot(orientation);
233288
if (Math.Abs(offsetX) > halfWidth)
289+
{
234290
offsetX = Math.Sign(offsetX) * halfWidth;
291+
}
235292
if (Math.Abs(offsetY) > halfHeight)
293+
{
236294
offsetY = Math.Sign(offsetY) * halfHeight;
295+
}
237296
return orientation.OrthoL() * offsetX + orientation * offsetY;
238297
}
239298
}
240299

241-
public sealed record class ArenaBoundsSquare(float Radius, Angle Rotation = default, float MapResolution = 0.5f, bool AllowObstacleMap = false) : ArenaBoundsRect(Radius, Radius, Rotation, MapResolution, AllowObstacleMap) { }
300+
public sealed class ArenaBoundsRect(float halfWidth, float halfHeight, Angle rotation = default, float mapResolution = 0.5f, bool allowObstacleMap = false) : ABRect(halfWidth, halfHeight, rotation, mapResolution, allowObstacleMap)
301+
{
302+
public override string ToString() => $"{nameof(ArenaBoundsRect)}, Radius {Radius}, HalfWidth: {HalfWidth}, HalfHeight: {HalfHeight}, MapResolution: {MapResolution}, ScaleFactor: {ScaleFactor}";
303+
}
304+
public sealed class ArenaBoundsSquare(float halfWidth, Angle rotation = default, float mapResolution = 0.5f, bool allowObstacleMap = false) : ABRect(halfWidth, halfWidth, rotation, mapResolution, allowObstacleMap)
305+
{
306+
public override string ToString() => $"{nameof(ArenaBoundsSquare)}, Radius {Radius}, HalfWidth: {HalfWidth}, MapResolution: {MapResolution}, ScaleFactor: {ScaleFactor}";
307+
}
242308

243309
// custom complex polygon bounds
244310
// for creating complex bounds by using arrays of shapes
245311
// first array contains platforms that will be united, second optional array contains shapes that will be subtracted
246312
// for convenience third array will optionally perform additional unions at the end
247313
// offset shrinks the pathfinding map only, for example if the edges of the arena are deadly and floating point errors cause the AI to fall of the map or problems like that
248314
// AdjustForHitbox adjusts both the visible map and the pathfinding map
249-
public sealed record class ArenaBoundsCustom : ArenaBounds
315+
public sealed class ArenaBoundsCustom : ArenaBounds
250316
{
251317
private Pathfinding.Map? _cachedMap;
252318
public readonly RelSimplifiedComplexPolygon Polygon;
@@ -265,20 +331,30 @@ public ArenaBoundsCustom(Shape[] UnionShapes, Shape[]? DifferenceShapes = null,
265331
Polygon = poly;
266332
offset = Offset;
267333

268-
var edgeList = new List<(WDir, WDir)>();
269334
var parts = Polygon.Parts;
270335
var count = parts.Count;
336+
var vertsCount = 0;
337+
for (var i = 0; i < count; ++i)
338+
{
339+
vertsCount += parts[i].VerticesCount;
340+
}
341+
var edgeArray = new (WDir, WDir)[vertsCount];
342+
var k = 0;
271343
for (var i = 0; i < count; ++i)
272344
{
273345
var part = parts[i];
274-
edgeList.AddRange(part.ExteriorEdges);
346+
var extSpan = part.ExteriorEdges;
347+
extSpan.CopyTo(edgeArray.AsSpan(k, extSpan.Length));
348+
k += extSpan.Length;
275349
var len = part.Holes.Length;
276350
for (var j = 0; j < len; ++j)
277351
{
278-
edgeList.AddRange(part.InteriorEdges(j));
352+
var intSpan = part.InteriorEdges(j);
353+
intSpan.CopyTo(edgeArray.AsSpan(k, intSpan.Length));
354+
k += intSpan.Length;
279355
}
280356
}
281-
edges = [.. edgeList];
357+
edges = edgeArray;
282358
}
283359

284360
private static float BuildBounds(Shape[] unionShapes, Shape[]? differenceShapes, Shape[]? additionalShapes, float scalefactor, bool adjustForHitbox, out RelSimplifiedComplexPolygon poly, out WPos center, out float halfWidth, out float halfHeight)
@@ -312,13 +388,21 @@ private static (WPos Center, float HalfWidth, float HalfHeight, float Radius, Re
312388
var vX = vertex.X;
313389
var vZ = vertex.Z;
314390
if (vX < minX)
391+
{
315392
minX = vX;
393+
}
316394
if (vX > maxX)
395+
{
317396
maxX = vX;
397+
}
318398
if (vZ < minZ)
399+
{
319400
minZ = vZ;
401+
}
320402
if (vZ > maxZ)
403+
{
321404
maxZ = vZ;
405+
}
322406
}
323407
}
324408

@@ -415,8 +499,7 @@ private Pathfinding.Map BuildMap()
415499
var height = map.Height;
416500
var resolution = map.Resolution;
417501

418-
map.BlockPixelsInside(new SDInvertedPolygonWithHoles(new(default, Polygon)), -1f, 0.49999f * resolution); // check inner circle of the pixel
419-
502+
var shape = new SDInvertedPolygonWithHoles(new(default, Polygon));
420503
// now check the corners
421504
var halfSample = resolution * 0.49999f; // tiny offset to account for floating point inaccuracies
422505

@@ -431,34 +514,38 @@ private Pathfinding.Map BuildMap()
431514
var dx = new WDir(resolution, default);
432515
var dy = new WDir(default, resolution);
433516
var startPos = map.Center - ((width >> 1) - 0.5f) * dx - ((height >> 1) - 0.5f) * dy;
517+
var partitioner = Partitioner.Create(0, height);
434518

435-
Parallel.For(0, height, y =>
519+
Parallel.ForEach(partitioner, range =>
436520
{
437-
var rowOffset = y * width;
438-
var posY = startPos + y * dy;
439-
for (var x = 0; x < width; ++x)
521+
var ys = range.Item1;
522+
var ye = range.Item2;
523+
for (var y = ys; y < ye; ++y)
440524
{
441-
var offset = rowOffset + x;
442-
443-
if (pixels[offset] == -1f)
525+
var rowOffset = y * width;
526+
var posY = startPos + y * dy;
527+
for (var x = 0; x < width; ++x)
444528
{
445-
continue;
446-
}
447-
var pos = posY + x * dx;
448-
449-
var relativeCenter = new WDir(pos.X, pos.Z);
529+
var offset = rowOffset + x;
530+
var pos = posY + x * dx;
531+
if (shape.Distance(pos) <= halfSample) // inner circle of the pixel
532+
{
533+
pixels[offset] = -1000f; // no reason to check more points of the cell
534+
continue;
535+
}
536+
var relativeCenter = new WDir(pos.X, pos.Z);
450537

451-
for (var i = 0; i < 4; ++i)
452-
{
453-
if (!polygon.Contains(relativeCenter + sampleOffsets[i]))
538+
for (var i = 0; i < 4; ++i)
454539
{
455-
pixels[offset] = -1f;
456-
break;
540+
if (!polygon.Contains(relativeCenter + sampleOffsets[i]))
541+
{
542+
pixels[offset] = -1000f;
543+
break;
544+
}
457545
}
458546
}
459547
}
460548
});
461-
462549
return map;
463550
}
464551

@@ -493,4 +580,6 @@ private static RelSimplifiedComplexPolygon CombinePolygons(RelSimplifiedComplexP
493580

494581
return combinedShape;
495582
}
583+
584+
public override string ToString() => $"{nameof(ArenaBoundsCustom)}, Radius {Radius}, HalfWidth: {HalfWidth}, HalfHeight: {HalfHeight}, MapResolution: {MapResolution}, Pathfinding offset: {offset}, Vertices: {edges.Length}, ScaleFactor: {ScaleFactor}";
496585
}

BossMod/Modules/Dawntrail/Alliance/A21FaithboundKirin/A21FaithboundKirin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ sealed class ArmOfPurgatory(BossModule module) : Components.SimpleAOEs(module, (
1313
sealed class GloamingGleam(BossModule module) : Components.SimpleAOEs(module, (uint)AID.GloamingGleam, new AOEShapeRect(50f, 6f));
1414
sealed class RazorFang(BossModule module) : Components.SimpleAOEs(module, (uint)AID.RazorFang, 20f);
1515

16-
[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", PrimaryActorOID = (uint)OID.FaithboundKirin, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1058u, NameID = 14053u, Category = BossModuleInfo.Category.Alliance, Expansion = BossModuleInfo.Expansion.Dawntrail, SortOrder = 2)]
16+
[ModuleInfo(BossModuleInfo.Maturity.AISupport, Contributors = "The Combat Reborn Team (Malediktus)", PrimaryActorOID = (uint)OID.FaithboundKirin, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1058u, NameID = 14053u, Category = BossModuleInfo.Category.Alliance, Expansion = BossModuleInfo.Expansion.Dawntrail, SortOrder = 2)]
1717
public sealed class A21FaithboundKirin(WorldState ws, Actor primary) : BossModule(ws, primary, DefaultCenter, DefaultArena)
1818
{
1919
public static readonly WPos DefaultCenter = new(-850f, 780f);

BossMod/Modules/Dawntrail/Alliance/A22UltimaOmega/Crash.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
1515
{
1616
var center = Arena.Center;
1717
var dir = -25f * c.Direction.ToDirection();
18-
hints.AddForbiddenZone(new SDKnockbackInAABBRectFixedDirection(center, dir, 19f, 23.5f)); // rect intentionally slightly smaller to prevent sus knockback
18+
hints.AddForbiddenZone(new SDKnockbackInAABBRectFixedDirection(center, dir, 19f, 23.5f), act); // rect intentionally slightly smaller to prevent sus knockback
1919
}
2020
}
2121
}

0 commit comments

Comments
 (0)