From da0f56a1f9099225a09cd4730e34f3913ba66a05 Mon Sep 17 00:00:00 2001 From: Andrew Gilewsky Date: Thu, 2 May 2024 20:33:25 +0100 Subject: [PATCH] Caching for clipping results. --- BossMod/BossModule/AOEShapes.cs | 2 +- BossMod/BossModule/MiniArena.cs | 79 +++++++++++++++---- BossMod/BossModule/TriangulationCache.cs | 50 ++++++++++++ .../DRS7StygimolochLord/AddPhaseArena.cs | 16 ++-- 4 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 BossMod/BossModule/TriangulationCache.cs diff --git a/BossMod/BossModule/AOEShapes.cs b/BossMod/BossModule/AOEShapes.cs index 82012d3fe0..83523c3bd8 100644 --- a/BossMod/BossModule/AOEShapes.cs +++ b/BossMod/BossModule/AOEShapes.cs @@ -108,7 +108,7 @@ public sealed record class AOEShapeCross(float Length, float HalfWidth, Angle Di { public override string ToString() => $"Cross: l={Length:f3}, w={HalfWidth * 2}, off={DirectionOffset}"; public override bool Check(WPos position, WPos origin, Angle rotation) => position.InRect(origin, rotation + DirectionOffset, Length, Length, HalfWidth) || position.InRect(origin, rotation + DirectionOffset, HalfWidth, HalfWidth, Length); - public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.ZonePoly(ContourPoints(origin, rotation), color); + public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.ZonePoly(HashCode.Combine(GetType(), Length, HalfWidth, DirectionOffset), ContourPoints(origin, rotation), color); public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.Danger) { diff --git a/BossMod/BossModule/MiniArena.cs b/BossMod/BossModule/MiniArena.cs index 0d0eb6e176..7aa40e03be 100644 --- a/BossMod/BossModule/MiniArena.cs +++ b/BossMod/BossModule/MiniArena.cs @@ -7,11 +7,38 @@ namespace BossMod; // rotation 0 corresponds to South, and increases counterclockwise (so East is +pi/2, North is pi, West is -pi/2) // - camera azimuth 0 correpsonds to camera looking North and increases counterclockwise // - screen coordinates - X points left to right, Y points top to bottom -public class MiniArena(BossModuleConfig config, WPos center, ArenaBounds bounds) +public sealed class MiniArena(BossModuleConfig config, WPos center, ArenaBounds bounds) { - public BossModuleConfig Config { get; init; } = config; - public WPos Center = center; - public ArenaBounds Bounds = bounds; + public readonly BossModuleConfig Config = config; + private WPos _center = center; + private ArenaBounds _bounds = bounds; + private readonly TriangulationCache _triCache = new(); + + public WPos Center + { + get => _center; + set + { + if (_center != value) + { + _center = value; + _triCache.Invalidate(); + } + } + } + + public ArenaBounds Bounds + { + get => _bounds; + set + { + if (!ReferenceEquals(_bounds, value)) + { + _bounds = value; + _triCache.Invalidate(); + } + } + } public float ScreenHalfSize => 150 * Config.ArenaScale; public float ScreenMarginSize => 20 * Config.ArenaScale; @@ -34,7 +61,15 @@ public void Begin(float cameraAzimuthRadians) var cursor = ImGui.GetCursorScreenPos(); ImGui.Dummy(fullSize); - Bounds.ScreenHalfSize = ScreenHalfSize; + if (Bounds.ScreenHalfSize != ScreenHalfSize) + { + Bounds.ScreenHalfSize = ScreenHalfSize; + _triCache.Invalidate(); + } + else + { + _triCache.NextFrame(); + } ScreenCenter = cursor + centerOffset; _cameraAzimuth = cameraAzimuthRadians; _cameraSinAzimuth = MathF.Sin(cameraAzimuthRadians); @@ -187,17 +222,29 @@ public void Zone(List triangulation, uint color) drawlist.Flags = restoreFlags; } - // draw zones - these are filled primitives clipped to various borders - public void ZoneCone(WPos center, float innerRadius, float outerRadius, Angle centerDirection, Angle halfAngle, uint color) => Zone(Bounds.ClipAndTriangulateCone(center - Center, innerRadius, outerRadius, centerDirection, halfAngle), color); - public void ZoneCircle(WPos center, float radius, uint color) => Zone(Bounds.ClipAndTriangulateCircle(center - Center, radius), color); - public void ZoneDonut(WPos center, float innerRadius, float outerRadius, uint color) => Zone(Bounds.ClipAndTriangulateDonut(center - Center, innerRadius, outerRadius), color); - public void ZoneTri(WPos a, WPos b, WPos c, uint color) => Zone(Bounds.ClipAndTriangulateTri(a - Center, b - Center, c - Center), color); - public void ZoneIsoscelesTri(WPos apex, WDir height, WDir halfBase, uint color) => Zone(Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, height, halfBase), color); - public void ZoneIsoscelesTri(WPos apex, Angle direction, Angle halfAngle, float height, uint color) => Zone(Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, direction, halfAngle, height), color); - public void ZoneRect(WPos origin, WDir direction, float lenFront, float lenBack, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color); - public void ZoneRect(WPos origin, Angle direction, float lenFront, float lenBack, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color); - public void ZoneRect(WPos start, WPos end, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(start - Center, end - Center, halfWidth), color); - public void ZonePoly(IEnumerable contour, uint color) => Zone(Bounds.ClipAndTriangulate(contour.Select(p => p - Center)), color); + // draw zones - these are filled primitives clipped to arena border; note that triangulation is cached + public void ZoneCone(WPos center, float innerRadius, float outerRadius, Angle centerDirection, Angle halfAngle, uint color) + => Zone(_triCache[HashCode.Combine(1, center, innerRadius, outerRadius, centerDirection, halfAngle)] ??= Bounds.ClipAndTriangulateCone(center - Center, innerRadius, outerRadius, centerDirection, halfAngle), color); + public void ZoneCircle(WPos center, float radius, uint color) + => Zone(_triCache[HashCode.Combine(2, center, radius)] ??= Bounds.ClipAndTriangulateCircle(center - Center, radius), color); + public void ZoneDonut(WPos center, float innerRadius, float outerRadius, uint color) + => Zone(_triCache[HashCode.Combine(3, center, innerRadius, outerRadius)] ??= Bounds.ClipAndTriangulateDonut(center - Center, innerRadius, outerRadius), color); + public void ZoneTri(WPos a, WPos b, WPos c, uint color) + => Zone(_triCache[HashCode.Combine(4, a, b, c)] ??= Bounds.ClipAndTriangulateTri(a - Center, b - Center, c - Center), color); + public void ZoneIsoscelesTri(WPos apex, WDir height, WDir halfBase, uint color) + => Zone(_triCache[HashCode.Combine(5, apex, height, halfBase)] ??= Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, height, halfBase), color); + public void ZoneIsoscelesTri(WPos apex, Angle direction, Angle halfAngle, float height, uint color) + => Zone(_triCache[HashCode.Combine(6, apex, direction, halfAngle, height)] ??= Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, direction, halfAngle, height), color); + public void ZoneRect(WPos origin, WDir direction, float lenFront, float lenBack, float halfWidth, uint color) + => Zone(_triCache[HashCode.Combine(7, origin, direction, lenFront, lenBack, halfWidth)] ??= Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color); + public void ZoneRect(WPos origin, Angle direction, float lenFront, float lenBack, float halfWidth, uint color) + => Zone(_triCache[HashCode.Combine(8, origin, direction, lenFront, lenBack, halfWidth)] ??= Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color); + public void ZoneRect(WPos start, WPos end, float halfWidth, uint color) + => Zone(_triCache[HashCode.Combine(9, start, end, halfWidth)] ??= Bounds.ClipAndTriangulateRect(start - Center, end - Center, halfWidth), color); + public void ZonePoly(int hash, IEnumerable contour, uint color) + => Zone(_triCache[HashCode.Combine(10, hash)] ??= Bounds.ClipAndTriangulate(contour.Select(p => p - Center)), color); + public void ZoneRelPoly(int hash, IEnumerable relContour, uint color) + => Zone(_triCache[HashCode.Combine(11, hash)] ??= Bounds.ClipAndTriangulate(relContour), color); public void TextScreen(Vector2 center, string text, uint color, float fontSize = 17) { diff --git a/BossMod/BossModule/TriangulationCache.cs b/BossMod/BossModule/TriangulationCache.cs new file mode 100644 index 0000000000..8706737fbc --- /dev/null +++ b/BossMod/BossModule/TriangulationCache.cs @@ -0,0 +1,50 @@ +namespace BossMod; + +// clipping shapes to bounds and triangulating them is a serious time sink, so we want to cache that +// to avoid requiring tracking cache lifetime by users, we use a heuristic - we assume that if something isn't drawn for a frame, it's no longer relevant +// for that, we keep two lists (prev/curr frame cache), every frame discard old entries, every time we retrieve entry from prev frame cache we move it to curr +public sealed class TriangulationCache +{ + private List<(int hash, List? triangulation)> _prev = []; + private List<(int hash, List? triangulation)> _curr = []; + + // the typical usage is: var triangulation = cache[hash] ??= BuildTriangulation(...) + public ref List? this[int hash] + { + get + { + var iCurr = _curr.FindIndex(kv => kv.hash == hash); + if (iCurr < 0) + { + List? entry = null; + + // see if there is entry in prev + var iPrev = _prev.FindIndex(kv => kv.hash == hash); + if (iPrev >= 0) + { + entry = _prev[iPrev].triangulation; + // swap-remove + if (iPrev + 1 < _prev.Count) + _prev[iPrev] = _prev[^1]; + _prev.RemoveAt(_prev.Count - 1); + } + + iCurr = _curr.Count; + _curr.Add((hash, entry)); + } + return ref _curr.Ref(iCurr).triangulation; + } + } + + public void NextFrame() + { + (_prev, _curr) = (_curr, _prev); + _curr.Clear(); + } + + public void Invalidate() + { + _prev.Clear(); + _curr.Clear(); + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/AddPhaseArena.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/AddPhaseArena.cs index 77c5082661..4dba7e1eca 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/AddPhaseArena.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/AddPhaseArena.cs @@ -2,17 +2,17 @@ class AddPhaseArena(BossModule module) : BossComponent(module) { - private readonly float _innerRingRadius = 14.5f; - private readonly float _outerRingRadius = 27.5f; - private readonly float _ringHalfWidth = 2.5f; - private readonly float _alcoveDepth = 1; - private readonly float _alcoveWidth = 2; + private const float _innerRingRadius = 14.5f; + private const float _outerRingRadius = 27.5f; + private const float _ringHalfWidth = 2.5f; + private const float _alcoveDepth = 1; + private const float _alcoveWidth = 2; public override void DrawArenaBackground(int pcSlot, Actor pc) { - Arena.Zone(Module.Bounds.ClipAndTriangulate(InDanger()), ArenaColor.AOE); - Arena.Zone(Module.Bounds.ClipAndTriangulate(MidDanger()), ArenaColor.AOE); - Arena.Zone(Module.Bounds.ClipAndTriangulate(OutDanger()), ArenaColor.AOE); + Arena.ZoneRelPoly(HashCode.Combine(GetType(), 0), InDanger(), ArenaColor.AOE); + Arena.ZoneRelPoly(HashCode.Combine(GetType(), 1), MidDanger(), ArenaColor.AOE); + Arena.ZoneRelPoly(HashCode.Combine(GetType(), 2), OutDanger(), ArenaColor.AOE); } private IEnumerable RingBorder(Angle centerOffset, float ringRadius, bool innerBorder)