Skip to content

Commit

Permalink
Merge pull request #50 from Archomeda/fix/additional-mumble-performan…
Browse files Browse the repository at this point in the history
…ce-improvements

Cache Mumble Link server address as well
  • Loading branch information
Archomeda authored Mar 21, 2020
2 parents 3e0de80 + f1fa12c commit 07a0163
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 26 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ For this release, Gw2Sharp has swapped its JSON dependency from Newtonsoft.Json
- `Gw2Sharp.WebApi.V2.Clients.IProfessionClient` now implements `Gw2Sharp.WebApi.V2.Clients.IBulkAliasExpandableClient` and supports requesting `GetAsync` and `ManyAsync` with `Gw2Sharp.Models.ProfessionType` as id type
- `Gw2Sharp.WebApi.V2.Clients.IRaceClient` now implements `Gw2Sharp.WebApi.V2.Clients.IBulkAliasExpandableClient` and supports requesting `GetAsync` and `ManyAsync` with `Gw2Sharp.Models.RaceType` as id type
- **Breaking:** The `Race` property in the Mumble Client is now of type `RaceType` instead of `string` ([#40](https://github.com/Archomeda/Gw2Sharp/issues/40), [#41](https://github.com/Archomeda/Gw2Sharp/pull/41))
- Repeatedly requesting Mumble identity fields (or just requesting multiple) should be a bit faster now ([#39](https://github.com/Archomeda/Gw2Sharp/issues/39), [#42](https://github.com/Archomeda/Gw2Sharp/pull/42), [#48](https://github.com/Archomeda/Gw2Sharp/pull/48))
- Repeatedly requesting Mumble identity fields (or just requesting multiple) should be a bit faster now ([#39](https://github.com/Archomeda/Gw2Sharp/issues/39), [#42](https://github.com/Archomeda/Gw2Sharp/pull/42), [#48](https://github.com/Archomeda/Gw2Sharp/pull/48), [#50](https://github.com/Archomeda/Gw2Sharp/pull/50))

### Refactoring
- **Breaking:** `Gw2Sharp.Models.Legend` has been renamed to `Gw2Sharp.Models.LegendType` ([#40](https://github.com/Archomeda/Gw2Sharp/issues/40), [#41](https://github.com/Archomeda/Gw2Sharp/pull/41))
Expand Down
5 changes: 5 additions & 0 deletions Gw2Sharp/Mumble/Gw2LinkedMem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ internal unsafe struct Gw2LinkedMem
[StructLayout(LayoutKind.Explicit)]
internal unsafe struct Gw2Context
{
public const int SOCKET_ADDRESS_SIZE = 28;

[FieldOffset(0)]
public fixed byte socketAddress[SOCKET_ADDRESS_SIZE];

[FieldOffset(0)]
private ushort _socketAddressFamily;
public AddressFamily socketAddressFamily => (AddressFamily)this._socketAddressFamily;
Expand Down
93 changes: 68 additions & 25 deletions Gw2Sharp/Mumble/Gw2MumbleClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class Gw2MumbleClient : BaseClient, IGw2MumbleClient
internal static readonly char[] mumbleLinkGameName = new[] { 'G', 'u', 'i', 'l', 'd', ' ', 'W', 'a', 'r', 's', ' ', '2', '\0' };
private const string EMPTY_IDENTITY = "{}";

private readonly object identityLock = new object();
private readonly object serverAddressLock = new object();

private readonly Lazy<MemoryMappedFile> memoryMappedFile;
private readonly Lazy<MemoryMappedViewAccessor> memoryMappedViewAccessor;

Expand All @@ -51,30 +54,38 @@ protected internal Gw2MumbleClient(IConnection connection, IGw2Client gw2Client)
() => this.memoryMappedFile.Value.CreateViewAccessor(), true);
}

private bool hasNewIdentity = false;
private string currentIdentityJson = EMPTY_IDENTITY;
private readonly char[] newIdentityData = new char[256];
private CharacterIdentity? identityObject;
private string identityCache = EMPTY_IDENTITY;
private CharacterIdentity? identity;
private unsafe CharacterIdentity? Identity
{
get
{
if (this.newIdentityData[0] != '\0')
{
var currentIdentitySpan = this.currentIdentityJson.AsSpan();
var newIdentitySpan = this.newIdentityData.AsSpan(0, this.currentIdentityJson.Length);
// Check if we're in the same frame update
if (this.linkedMem.identity[0] == '\0')
return this.identity;

if (!newIdentitySpan.SequenceEqual(currentIdentitySpan))
// Thread-safety
lock (this.identityLock)
{
// Check again
if (this.linkedMem.identity[0] != '\0')
{
fixed (char* newIdentityPtr = this.newIdentityData)
var cacheSpan = this.identityCache.AsSpan();
fixed (char* ptr = this.linkedMem.identity)
{
this.currentIdentityJson = new string(newIdentityPtr);
this.identityObject = JsonSerializer.Deserialize<CharacterIdentity>(this.currentIdentityJson, deserializerSettings);
// Check if the identity is different from last update
var span = new ReadOnlySpan<char>(ptr, this.identityCache.Length);
if (!span.SequenceEqual(cacheSpan))
{
// Update and parse JSON
this.identityCache = new string(ptr);
this.identity = JsonSerializer.Deserialize<CharacterIdentity>(this.identityCache, deserializerSettings);
}
}
this.linkedMem.identity[0] = '\0';
}
this.newIdentityData[0] = '\0';
}
return this.identityObject;
return this.identity;
}
}

Expand Down Expand Up @@ -109,6 +120,9 @@ private unsafe CharacterIdentity? Identity
public unsafe Coordinates3 CameraFront =>
this.IsAvailable ? new Coordinates3(this.linkedMem.fCameraFront[0], this.linkedMem.fCameraFront[1], this.linkedMem.fCameraFront[2]) : default;

private readonly byte[] serverAddressCache = new byte[Gw2Context.SOCKET_ADDRESS_SIZE];
private bool serverAddressCacheDirty = true;
private string? serverAddress;
/// <inheritdoc />
public unsafe string ServerAddress
{
Expand All @@ -117,17 +131,45 @@ public unsafe string ServerAddress
if (!this.IsAvailable)
return string.Empty;

var context = this.linkedMem.context;
switch (context.socketAddressFamily)
// Check if we're in the same frame update
if (!this.serverAddressCacheDirty)
return this.serverAddress ?? string.Empty;

// Thread-safety
lock (this.serverAddressLock)
{
case AddressFamily.InterNetwork:
return $"{context.socketAddress4[0]}.{context.socketAddress4[1]}.{context.socketAddress4[2]}.{context.socketAddress4[3]}";
case AddressFamily.InterNetworkV6:
return $"{context.socketAddress6[0]:X4}:{context.socketAddress6[1]:X4}:{context.socketAddress6[2]:X4}:{context.socketAddress6[3]:X4}:" +
$"{context.socketAddress6[4]:X4}:{context.socketAddress6[5]:X4}:{context.socketAddress6[6]:X4}:{context.socketAddress6[7]:X4}";
default:
return string.Empty;
var cacheSpan = this.serverAddressCache.AsSpan();
fixed (byte* ptr = this.linkedMem.context.socketAddress)
{
// Check if the server address is different from last update
var span = new ReadOnlySpan<byte>(ptr, Gw2Context.SOCKET_ADDRESS_SIZE);
if (!span.SequenceEqual(cacheSpan))
{
// Update server address
span.CopyTo(cacheSpan);
this.serverAddressCacheDirty = false;
this.serverAddress = this.linkedMem.context.socketAddressFamily switch
{
AddressFamily.InterNetwork =>
$"{this.linkedMem.context.socketAddress4[0]}." +
$"{this.linkedMem.context.socketAddress4[1]}." +
$"{this.linkedMem.context.socketAddress4[2]}." +
$"{this.linkedMem.context.socketAddress4[3]}",
AddressFamily.InterNetworkV6 =>
$"{this.linkedMem.context.socketAddress6[0]:X4}:" +
$"{this.linkedMem.context.socketAddress6[1]:X4}:" +
$"{this.linkedMem.context.socketAddress6[2]:X4}:" +
$"{this.linkedMem.context.socketAddress6[3]:X4}:" +
$"{this.linkedMem.context.socketAddress6[4]:X4}:" +
$"{this.linkedMem.context.socketAddress6[5]:X4}:" +
$"{this.linkedMem.context.socketAddress6[6]:X4}:" +
$"{this.linkedMem.context.socketAddress6[7]:X4}",
_ => string.Empty
};
}
}
}
return this.serverAddress ?? string.Empty;
}
}

Expand Down Expand Up @@ -239,6 +281,8 @@ public unsafe void Update()
if (this.isDisposed)
throw new ObjectDisposedException(nameof(Gw2MumbleClient));



this.memoryMappedViewAccessor.Value.Read<Gw2LinkedMem>(0, out var linkedMem);
int oldTick = this.Tick;

Expand All @@ -251,8 +295,7 @@ public unsafe void Update()
if (this.IsAvailable)
{
this.Name = MUMBLE_LINK_GAME_NAME_GUILD_WARS_2;
fixed (char* ptr = this.newIdentityData)
Buffer.MemoryCopy(linkedMem.identity, ptr, 512, 512);
this.serverAddressCacheDirty = true;
}
else
{
Expand Down

0 comments on commit 07a0163

Please sign in to comment.