Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map entity rendering performance enhancements and minor client bug fixes #308

Merged
merged 7 commits into from
May 14, 2023
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