Skip to content

Commit

Permalink
Cache sprite data for NPCs. Reduces garbage generated from detecting …
Browse files Browse the repository at this point in the history
…the pixel below the mouse cursor.
  • Loading branch information
ethanmoffat committed May 8, 2023
1 parent 8df81f2 commit 232c38b
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 15 deletions.
32 changes: 17 additions & 15 deletions EndlessClient/Rendering/NPC/NPCRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using EndlessClient.Controllers;
using EndlessClient.GameExecution;
using EndlessClient.HUD.Spells;
using EndlessClient.GameExecution;
using EndlessClient.Input;
using EndlessClient.Rendering.Chat;
using EndlessClient.Rendering.Effects;
Expand All @@ -16,8 +14,8 @@
using Microsoft.Xna.Framework.Graphics;
using Optional;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Markup;
using XNAControls;

namespace EndlessClient.Rendering.NPC
Expand All @@ -29,6 +27,7 @@ public class NPCRenderer : DrawableGameComponent, INPCRenderer
private readonly IClientWindowSizeProvider _clientWindowSizeProvider;
private readonly IENFFileProvider _enfFileProvider;
private readonly INPCSpriteSheet _npcSpriteSheet;
private readonly INPCSpriteDataCache _npcSpriteDataCache;
private readonly IGridDrawCoordinateCalculator _gridDrawCoordinateCalculator;
private readonly IHealthBarRendererFactory _healthBarRendererFactory;
private readonly IChatBubbleFactory _chatBubbleFactory;
Expand Down Expand Up @@ -67,6 +66,7 @@ public NPCRenderer(IEndlessGameProvider endlessGameProvider,
IClientWindowSizeProvider clientWindowSizeProvider,
IENFFileProvider enfFileProvider,
INPCSpriteSheet npcSpriteSheet,
INPCSpriteDataCache npcSpriteDataCache,
IGridDrawCoordinateCalculator gridDrawCoordinateCalculator,
IHealthBarRendererFactory healthBarRendererFactory,
IChatBubbleFactory chatBubbleFactory,
Expand All @@ -80,6 +80,7 @@ public NPCRenderer(IEndlessGameProvider endlessGameProvider,
_clientWindowSizeProvider = clientWindowSizeProvider;
_enfFileProvider = enfFileProvider;
_npcSpriteSheet = npcSpriteSheet;
_npcSpriteDataCache = npcSpriteDataCache;
_gridDrawCoordinateCalculator = gridDrawCoordinateCalculator;
_healthBarRendererFactory = healthBarRendererFactory;
_chatBubbleFactory = chatBubbleFactory;
Expand Down Expand Up @@ -131,10 +132,9 @@ public override void Initialize()

_spriteBatch = new SpriteBatch(Game.GraphicsDevice);

var frameTexture = _npcSpriteSheet.GetNPCTexture(_enfFileProvider.ENFFile[NPC.ID].Graphic, NPC.Frame, NPC.Direction);
var data = new Color[frameTexture.Width * frameTexture.Height];
frameTexture.GetData(data);
_isBlankSprite = data.All(x => x.A == 0);
var graphic = _enfFileProvider.ENFFile[NPC.ID].Graphic;
_npcSpriteDataCache.Populate(graphic);
_isBlankSprite = _npcSpriteDataCache.IsBlankSprite(graphic);

base.Initialize();
}
Expand Down Expand Up @@ -169,16 +169,18 @@ public override void Update(GameTime gameTime)

public bool IsClickablePixel(Point currentMousePosition)
{
var currentFrame = _npcSpriteSheet.GetNPCTexture(_enfFileProvider.ENFFile[NPC.ID].Graphic, NPC.Frame, NPC.Direction);

var colorData = new Color[] { Color.FromNonPremultiplied(0, 0, 0, 255) };
if (currentFrame != null && !_isBlankSprite)
var cachedTexture = _npcSpriteDataCache.GetData(_enfFileProvider.ENFFile[NPC.ID].Graphic, NPC.Frame);
if (!_isBlankSprite && cachedTexture.Length > 0 && _npcRenderTarget.Bounds.Contains(currentMousePosition))
{
if (_npcRenderTarget.Bounds.Contains(currentMousePosition))
_npcRenderTarget.GetData(0, new Rectangle(currentMousePosition.X, currentMousePosition.Y, 1, 1), colorData, 0, 1);
var currentFrame = _npcSpriteSheet.GetNPCTexture(_enfFileProvider.ENFFile[NPC.ID].Graphic, NPC.Frame, NPC.Direction);

var adjustedPos = currentMousePosition - DrawArea.Location;
var pixel = cachedTexture[adjustedPos.Y * currentFrame.Width + adjustedPos.X];

return pixel.A > 0;
}

return _isBlankSprite || colorData[0].A > 0;
return true;
}

public void DrawToSpriteBatch(SpriteBatch spriteBatch)
Expand Down
4 changes: 4 additions & 0 deletions EndlessClient/Rendering/NPC/NPCRendererFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class NPCRendererFactory : INPCRendererFactory
private readonly IClientWindowSizeProvider _clientWindowSizeProvider;
private readonly IENFFileProvider _enfFileProvider;
private readonly INPCSpriteSheet _npcSpriteSheet;
private readonly INPCSpriteDataCache _npcSpriteDataCache;
private readonly IGridDrawCoordinateCalculator _gridDrawCoordinateCalculator;
private readonly IHealthBarRendererFactory _healthBarRendererFactory;
private readonly IChatBubbleFactory _chatBubbleFactory;
Expand All @@ -27,6 +28,7 @@ public NPCRendererFactory(IEndlessGameProvider endlessGameProvider,
IClientWindowSizeProvider clientWindowSizeProvider,
IENFFileProvider enfFileProvider,
INPCSpriteSheet npcSpriteSheet,
INPCSpriteDataCache npcSpriteDataCache,
IGridDrawCoordinateCalculator gridDrawCoordinateCalculator,
IHealthBarRendererFactory healthBarRendererFactory,
IChatBubbleFactory chatBubbleFactory,
Expand All @@ -38,6 +40,7 @@ public NPCRendererFactory(IEndlessGameProvider endlessGameProvider,
_clientWindowSizeProvider = clientWindowSizeProvider;
_enfFileProvider = enfFileProvider;
_npcSpriteSheet = npcSpriteSheet;
_npcSpriteDataCache = npcSpriteDataCache;
_gridDrawCoordinateCalculator = gridDrawCoordinateCalculator;
_healthBarRendererFactory = healthBarRendererFactory;
_chatBubbleFactory = chatBubbleFactory;
Expand All @@ -52,6 +55,7 @@ public INPCRenderer CreateRendererFor(EOLib.Domain.NPC.NPC npc)
_clientWindowSizeProvider,
_enfFileProvider,
_npcSpriteSheet,
_npcSpriteDataCache,
_gridDrawCoordinateCalculator,
_healthBarRendererFactory,
_chatBubbleFactory,
Expand Down
77 changes: 77 additions & 0 deletions EndlessClient/Rendering/Sprites/NPCSpriteDataCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using AutomaticTypeMapper;
using EOLib;
using EOLib.Domain.NPC;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;

namespace EndlessClient.Rendering.Sprites
{
[AutoMappedType(IsSingleton = true)]
public class NPCSpriteDataCache : INPCSpriteDataCache
{
private readonly INPCSpriteSheet _npcSpriteSheet;
private readonly Dictionary<int, Dictionary<NPCFrame, Memory<Color>>> _spriteData;

public NPCSpriteDataCache(INPCSpriteSheet npcSpriteSheet)
{
_npcSpriteSheet = npcSpriteSheet;
_spriteData = new Dictionary<int, Dictionary<NPCFrame, Memory<Color>>>();
}

public void Populate(int graphic)
{
if (_spriteData.ContainsKey(graphic))
return;

_spriteData[graphic] = new Dictionary<NPCFrame, Memory<Color>>();

foreach (NPCFrame frame in Enum.GetValues(typeof(NPCFrame)))
{
var text = _npcSpriteSheet.GetNPCTexture(graphic, frame, EODirection.Down);
var data = Array.Empty<Color>();

if (text != null)
{
data = new Color[text.Width * text.Height];
text.GetData(data);
}

_spriteData[graphic][frame] = data;
}

}

public ReadOnlySpan<Color> GetData(int graphic, NPCFrame frame)
{
if (!_spriteData.ContainsKey(graphic))
{
Populate(graphic);
}

return _spriteData[graphic][frame].Span;
}

public bool IsBlankSprite(int graphic)
{
if (!_spriteData.ContainsKey(graphic))
{
Populate(graphic);
}

return _spriteData[graphic][NPCFrame.Standing].Span.ToArray().Any(AlphaIsZero);
}

private static bool AlphaIsZero(Color input) => input.A == 0;
}

public interface INPCSpriteDataCache
{
void Populate(int graphic);

ReadOnlySpan<Color> GetData(int graphic, NPCFrame frame);

bool IsBlankSprite(int graphic);
}
}

0 comments on commit 232c38b

Please sign in to comment.