Skip to content

Commit

Permalink
Merge pull request #308 from ethanmoffat/fix_map_entity_perf
Browse files Browse the repository at this point in the history
Map entity rendering performance enhancements and minor client bug fixes
  • Loading branch information
ethanmoffat authored May 14, 2023
2 parents ed65589 + d52b77d commit 10491f9
Show file tree
Hide file tree
Showing 51 changed files with 410 additions and 296 deletions.
2 changes: 1 addition & 1 deletion EOBot/Interpreter/BuiltInIdentifierConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private void Chat(string chatText)
var ms = DependencyMaster.TypeRegistry[_botIndex].Resolve<ICurrentMapStateProvider>();

var mapStateObj = new RuntimeEvaluatedMemberObjectVariable();
mapStateObj.SymbolTable["characters"] = (true, () => new ArrayVariable(ms.Characters.Values.Select(GetMapStateCharacter).ToList()));
mapStateObj.SymbolTable["characters"] = (true, () => new ArrayVariable(ms.Characters.Select(GetMapStateCharacter).ToList()));
mapStateObj.SymbolTable["npcs"] = (true, () => new ArrayVariable(ms.NPCs.Select(GetMapStateNPC).ToList()));
mapStateObj.SymbolTable["items"] = (true, () => new ArrayVariable(ms.MapItems.Select(GetMapStateItem).ToList()));

Expand Down
16 changes: 12 additions & 4 deletions EOBot/TrainerBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,27 @@ protected override async Task DoWorkAsync(CancellationToken ct)
ConsoleHelper.WriteMessage(ConsoleHelper.Type.Chat, $"{message.Who}: {message.Message}", ConsoleColor.Cyan);

_cachedChat = _chatProvider.AllChat[ChatTab.Local].ToHashSet();
if (OperatingSystem.IsWindows())
{
Console.Beep(261, 1500);
}
}

var character = _characterRepository.MainCharacter;
var charRenderProps = character.RenderProperties;

var currentPositionCellState = mapCellStateProvider.GetCellStateAt(charRenderProps.MapX, charRenderProps.MapY);

if (cachedPlayerCount != mapStateProvider.Characters.Count)
if (cachedPlayerCount != mapStateProvider.Characters.Count())
{
cachedPlayerCount = mapStateProvider.Characters.Count;
if (cachedPlayerCount > 0)
cachedPlayerCount = mapStateProvider.Characters.Count();
ConsoleHelper.WriteMessage(ConsoleHelper.Type.Warning, $"{cachedPlayerCount,7} - Players on map - You may not be able to train here", ConsoleColor.DarkYellow);

if (OperatingSystem.IsWindows())
{
ConsoleHelper.WriteMessage(ConsoleHelper.Type.Warning, $"{cachedPlayerCount,7} - Players on map - You may not be able to train here", ConsoleColor.DarkYellow);
Console.Beep(220, 500);
Console.Beep(247, 500);
Console.Beep(220, 500);
}
}

Expand Down
6 changes: 3 additions & 3 deletions EOLib/Domain/Login/LoginActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ public async Task<CharacterLoginReply> CompleteCharacterLogin(int sessionID)
_characterInventoryRepository.ItemInventory = new HashSet<InventoryItem>(data.CharacterItemInventory);
_characterInventoryRepository.SpellInventory = new HashSet<InventorySpell>(data.CharacterSpellInventory);

_currentMapStateRepository.Characters = data.MapCharacters.Except(new[] { mainCharacter }).ToDictionary(k => k.ID, v => v);
_currentMapStateRepository.NPCs = new HashSet<NPC.NPC>(data.MapNPCs);
_currentMapStateRepository.MapItems = new HashSet<MapItem>(data.MapItems);
_currentMapStateRepository.Characters = new MapEntityCollectionHashSet<Character.Character>(c => c.ID, c => new MapCoordinate(c.X, c.Y), data.MapCharacters.Except(new[] { mainCharacter }));
_currentMapStateRepository.NPCs = new MapEntityCollectionHashSet<NPC.NPC>(n => n.Index, n => new MapCoordinate(n.X, n.Y), data.MapNPCs);
_currentMapStateRepository.MapItems = new MapEntityCollectionHashSet<MapItem>(item => item.UniqueID, item => new MapCoordinate(item.X, item.Y), data.MapItems);

_playerInfoRepository.PlayerIsInGame = true;
_characterSessionRepository.ResetState();
Expand Down
30 changes: 15 additions & 15 deletions EOLib/Domain/Map/CurrentMapStateRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public interface ICurrentMapStateRepository

bool IsJail { get; }

Dictionary<int, Character.Character> Characters { get; set; }
MapEntityCollectionHashSet<Character.Character> Characters { get; set; }

HashSet<NPC.NPC> NPCs { get; set; }
MapEntityCollectionHashSet<NPC.NPC> NPCs { get; set; }

HashSet<MapItem> MapItems { get; set; }
MapEntityCollectionHashSet<MapItem> MapItems { get; set; }

HashSet<Warp> OpenDoors { get; set; }

Expand Down Expand Up @@ -50,11 +50,11 @@ public interface ICurrentMapStateProvider

bool IsJail { get; }

IReadOnlyDictionary<int, Character.Character> Characters { get; }
IReadOnlyMapEntityCollection<Character.Character> Characters { get; }

IReadOnlyCollection<NPC.NPC> NPCs { get; }
IReadOnlyMapEntityCollection<NPC.NPC> NPCs { get; }

IReadOnlyCollection<MapItem> MapItems { get; }
IReadOnlyMapEntityCollection<MapItem> MapItems { get; }

IReadOnlyCollection<Warp> OpenDoors { get; }

Expand Down Expand Up @@ -86,11 +86,11 @@ public class CurrentMapStateRepository : ICurrentMapStateRepository, ICurrentMap

public bool IsJail => JailMapID == CurrentMapID;

public Dictionary<int, Character.Character> Characters { get; set; }
public MapEntityCollectionHashSet<Character.Character> Characters { get; set; }

public HashSet<NPC.NPC> NPCs { get; set; }
public MapEntityCollectionHashSet<NPC.NPC> NPCs { get; set; }

public HashSet<MapItem> MapItems { get; set; }
public MapEntityCollectionHashSet<MapItem> MapItems { get; set; }

public HashSet<Warp> OpenDoors { get; set; }

Expand All @@ -110,11 +110,11 @@ public class CurrentMapStateRepository : ICurrentMapStateRepository, ICurrentMap

public HashSet<int> UnknownNPCIndexes { get; set; }

IReadOnlyDictionary<int, Character.Character> ICurrentMapStateProvider.Characters => Characters;
IReadOnlyMapEntityCollection<Character.Character> ICurrentMapStateProvider.Characters => Characters;

IReadOnlyCollection<NPC.NPC> ICurrentMapStateProvider.NPCs => NPCs;
IReadOnlyMapEntityCollection<NPC.NPC> ICurrentMapStateProvider.NPCs => NPCs;

IReadOnlyCollection<MapItem> ICurrentMapStateProvider.MapItems => MapItems;
IReadOnlyMapEntityCollection<MapItem> ICurrentMapStateProvider.MapItems => MapItems;

IReadOnlyCollection<Warp> ICurrentMapStateProvider.OpenDoors => OpenDoors;

Expand All @@ -133,9 +133,9 @@ public void ResetState()
ShowMiniMap = false;
JailMapID = 0;

Characters = new Dictionary<int, Character.Character>();
NPCs = new HashSet<NPC.NPC>();
MapItems = new HashSet<MapItem>();
Characters = new MapEntityCollectionHashSet<Character.Character>(x => x.ID, x => new MapCoordinate(x.X, x.Y));
NPCs = new MapEntityCollectionHashSet<NPC.NPC>(x => x.Index, x => new MapCoordinate(x.X, x.Y));
MapItems = new MapEntityCollectionHashSet<MapItem>(x => x.UniqueID, x => new MapCoordinate(x.X, x.Y));
OpenDoors = new HashSet<Warp>();
PendingDoors = new HashSet<Warp>();
VisibleSpikeTraps = new HashSet<MapCoordinate>();
Expand Down
34 changes: 13 additions & 21 deletions EOLib/Domain/Map/MapCellStateProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using EOLib.IO.Repositories;
using Optional;
using Optional.Collections;
using System.Collections.Generic;
using System.Linq;

namespace EOLib.Domain.Map
Expand Down Expand Up @@ -38,12 +39,17 @@ public IMapCellState GetCellStateAt(int x, int y)
var chest = CurrentMap.Chests.Where(c => c.X == x && c.Y == y && c.Key != ChestKey.None).Select(c => c.Key).FirstOrDefault();
var sign = CurrentMap.Signs.FirstOrDefault(s => s.X == x && s.Y == y);

var characters = _mapStateProvider.Characters.Values
.Concat(new[] { _characterProvider.MainCharacter })
.Where(c => CharacterAtCoordinates(c, x, y))
.ToList();
var npc = _mapStateProvider.NPCs.FirstOrNone(n => NPCAtCoordinates(n, x, y));
var items = _mapStateProvider.MapItems.Where(i => i.X == x && i.Y == y).OrderByDescending(i => i.UniqueID);
_mapStateProvider.Characters.TryGetValues(new MapCoordinate(x, y), out var characters);
if (_characterProvider.MainCharacter.X == x && _characterProvider.MainCharacter.Y == y)
characters.Add(_characterProvider.MainCharacter);

Option<NPC.NPC> npc = Option.None<NPC.NPC>();
if (_mapStateProvider.NPCs.TryGetValues(new MapCoordinate(x, y), out var npcs))
npc = npcs.FirstOrNone();

var items = _mapStateProvider.MapItems.TryGetValues(new MapCoordinate(x, y), out var mapItems)
? mapItems.OrderByDescending(i => i.UniqueID)
: Enumerable.Empty<MapItem>();

return new MapCellState
{
Expand All @@ -55,25 +61,11 @@ public IMapCellState GetCellStateAt(int x, int y)
ChestKey = chest.SomeNotNull(),
Sign = sign.SomeNotNull().Map(s => new Sign(s)),
Character = characters.FirstOrNone(),
Characters = characters,
Characters = characters.ToList(),
NPC = npc
};
}

private static bool CharacterAtCoordinates(Character.Character character, int x, int y)
{
return character.RenderProperties.IsActing(CharacterActionState.Walking)
? character.RenderProperties.GetDestinationX() == x && character.RenderProperties.GetDestinationY() == y
: character.RenderProperties.MapX == x && character.RenderProperties.MapY == y;
}

private static bool NPCAtCoordinates(NPC.NPC npc, int x, int y)
{
return npc.IsActing(NPCActionState.Walking)
? npc.GetDestinationX() == x && npc.GetDestinationY() == y
: npc.X == x && npc.Y == y;
}

private IMapFile CurrentMap => _mapFileProvider.MapFiles[_mapStateProvider.CurrentMapID];
}

Expand Down
131 changes: 131 additions & 0 deletions EOLib/Domain/Map/MapEntityCollectionHashSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace EOLib.Domain.Map
{
public class MapEntityCollectionHashSet<TValue> : IReadOnlyMapEntityCollection<TValue>
{
private readonly Dictionary<int, int> _uniqueIdToHash;
private readonly Dictionary<MapCoordinate, HashSet<int>> _mapCoordinateToHashList;

private readonly Dictionary<int, TValue> _valueSet;

private readonly Func<TValue, int> _uniqueIdSelector;
private readonly Func<TValue, MapCoordinate> _mapCoordinateSelector;

public MapEntityCollectionHashSet(Func<TValue, int> uniqueIdSelector,
Func<TValue, MapCoordinate> mapCoordinateSelector)
{
_uniqueIdToHash = new Dictionary<int, int>();
_mapCoordinateToHashList = new Dictionary<MapCoordinate, HashSet<int>>();
_valueSet = new Dictionary<int, TValue>();

_uniqueIdSelector = uniqueIdSelector;
_mapCoordinateSelector = mapCoordinateSelector;
}

public MapEntityCollectionHashSet(Func<TValue, int> uniqueIdSelector,
Func<TValue, MapCoordinate> mapCoordinateSelector,
IEnumerable<TValue> values)
: this(uniqueIdSelector, mapCoordinateSelector)
{
foreach (var value in values)
{
Add(value);
}
}

public TValue this[int key1] => _valueSet[_uniqueIdToHash[key1]];

public HashSet<TValue> this[MapCoordinate key2] => new HashSet<TValue>(_mapCoordinateToHashList[key2].Select(x => _valueSet[x]));

public void Add(TValue value)
{
var key1 = _uniqueIdSelector.Invoke(value);

var hash = value.GetHashCode();
_uniqueIdToHash[key1] = hash;

var key2 = _mapCoordinateSelector.Invoke(value);
if (!_mapCoordinateToHashList.ContainsKey(key2))
_mapCoordinateToHashList.Add(key2, new HashSet<int>());

_mapCoordinateToHashList[key2].Add(hash);
_valueSet[hash] = value;
}

public void Update(TValue oldValue, TValue newValue)
{
Remove(oldValue);
Add(newValue);
}

public void Remove(TValue value)
{
var key1 = _uniqueIdSelector.Invoke(value);
var key2 = _mapCoordinateSelector.Invoke(value);
_uniqueIdToHash.Remove(key1);

var hash = value.GetHashCode();
_mapCoordinateToHashList[key2].Remove(hash);
if (_mapCoordinateToHashList[key2].Count == 0)
_mapCoordinateToHashList.Remove(key2);

_valueSet.Remove(hash);
}

public bool ContainsKey(int uniqueId) => _uniqueIdToHash.ContainsKey(uniqueId);

public bool ContainsKey(MapCoordinate coordinate) => _mapCoordinateToHashList.ContainsKey(coordinate);

public bool TryGetValue(int uniqueId, out TValue value)
{
value = default;

if (!_uniqueIdToHash.ContainsKey(uniqueId))
return false;

var hash = _uniqueIdToHash[uniqueId];
if (!_valueSet.ContainsKey(hash))
return false;

value = _valueSet[hash];

return true;
}

public bool TryGetValues(MapCoordinate mapCoordinate, out HashSet<TValue> values)
{
values = new HashSet<TValue>();

if (!_mapCoordinateToHashList.ContainsKey(mapCoordinate))
return false;

var hashes = _mapCoordinateToHashList[mapCoordinate];
if (!_valueSet.Any(x => hashes.Contains(x.Key)))
return false;

values = this[mapCoordinate];

return true;
}

public IEnumerator<TValue> GetEnumerator() => _valueSet.Values.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public interface IReadOnlyMapEntityCollection<TValue> : IEnumerable<TValue>
{
TValue this[int key1] { get; }
HashSet<TValue> this[MapCoordinate key2] { get; }

bool ContainsKey(int characterID);
bool ContainsKey(MapCoordinate mapCoordinate);

bool TryGetValue(int key1, out TValue value);
bool TryGetValues(MapCoordinate key2, out HashSet<TValue> values);
}
}
3 changes: 2 additions & 1 deletion EOLib/Domain/Map/MapItem.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using Amadevus.RecordGenerator;
using EOLib.IO.Map;
using Optional;
using System;

namespace EOLib.Domain.Map
{
[Record]
public sealed partial class MapItem
public sealed partial class MapItem : IMapEntity
{
public int UniqueID { get; }

Expand Down
6 changes: 2 additions & 4 deletions EOLib/PacketHandlers/AdminInteract/AdminInteractAgree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,10 @@ public override bool HandlePacket(IPacket packet)
_characterRepository.MainCharacter = Shown(_characterRepository.MainCharacter);
else
{
if (_currentMapStateRepository.Characters.ContainsKey(id))
if (_currentMapStateRepository.Characters.TryGetValue(id, out var character))
{
var character = _currentMapStateRepository.Characters[id];

var updatedCharacter = Shown(character);
_currentMapStateRepository.Characters[id] = updatedCharacter;
_currentMapStateRepository.Characters.Update(character, updatedCharacter);
}
else
{
Expand Down
6 changes: 2 additions & 4 deletions EOLib/PacketHandlers/AdminInteract/AdminInteractRemove.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,10 @@ public override bool HandlePacket(IPacket packet)
_characterRepository.MainCharacter = Hidden(_characterRepository.MainCharacter);
else
{
if (_currentMapStateRepository.Characters.ContainsKey(id))
if (_currentMapStateRepository.Characters.TryGetValue(id, out var character))
{
var character = _currentMapStateRepository.Characters[id];

var updatedCharacter = Hidden(character);
_currentMapStateRepository.Characters[id] = updatedCharacter;
_currentMapStateRepository.Characters.Update(character, updatedCharacter);
}
else
{
Expand Down
5 changes: 2 additions & 3 deletions EOLib/PacketHandlers/Attack/AttackPlayerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ public override bool HandlePacket(IPacket packet)
var playerID = packet.ReadShort();
var direction = (EODirection)packet.ReadChar();

if (_currentMapStateRepository.Characters.ContainsKey(playerID))
if (_currentMapStateRepository.Characters.TryGetValue(playerID, out var character))
{
var character = _currentMapStateRepository.Characters[playerID];
if (character.RenderProperties.Direction != direction)
{
var renderProperties = character.RenderProperties.WithDirection(direction);
_currentMapStateRepository.Characters[playerID] = character.WithRenderProperties(renderProperties);
_currentMapStateRepository.Characters.Update(character, character.WithRenderProperties(renderProperties));
}

foreach (var notifier in _otherCharacterAnimationNotifiers)
Expand Down
Loading

0 comments on commit 10491f9

Please sign in to comment.