Skip to content

Commit bcdcdde

Browse files
committedDec 19, 2016
Fixed connection supersede bug.
Refactored relationships between Connection, Player, and Instance.
1 parent 557903d commit bcdcdde

18 files changed

+349
-285
lines changed
 

‎Guncho.Api.Tests/FakePlayersService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using Guncho.Services;
5+
using System.Threading.Tasks;
56

67
namespace Guncho.Api.Tests
78
{
@@ -35,9 +36,9 @@ public bool IsValidNameChange(string oldName, string newName)
3536
return false;
3637
}
3738

38-
public bool TransactionalUpdate(Player player, Func<Player, bool> transaction)
39+
public Task<bool> TransactionalUpdateAsync(Player player, Func<Player, bool> transaction)
3940
{
40-
return transaction(player);
41+
return Task.FromResult(transaction(player));
4142
}
4243
}
4344
}

‎Guncho.Api.Tests/FakeRealmsService.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void Add(string realmName, string ownerName)
1515
//XXX
1616
}
1717

18-
public Realm CreateRealm(Player owner, string name, RealmFactory factory)
18+
public Task<Realm> CreateRealmAsync(Player owner, string name, RealmFactory factory)
1919
{
2020
throw new NotImplementedException();
2121
}
@@ -40,7 +40,7 @@ public bool IsValidNameChange(string oldName, string newName)
4040
throw new NotImplementedException();
4141
}
4242

43-
public bool TransactionalUpdate(Realm realm, Func<Realm, bool> transaction)
43+
public Task<bool> TransactionalUpdateAsync(Realm realm, Func<Realm, bool> transaction)
4444
{
4545
throw new NotImplementedException();
4646
}

‎Guncho.Api.Tests/ProfilesTests.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public void PUT_profile_name_Should_Update_Named_Profile()
146146
.Controller<ProfilesController>()
147147
.WithTestRig(rig)
148148
.WithAuthenticatedUser(rig.PeonUser())
149-
.Calling(c => c.PutProfileByNameAsync(
149+
.CallingAsync(c => c.PutProfileByNameAsync(
150150
"Peon",
151151
new ProfileDto()
152152
{
@@ -168,7 +168,7 @@ public void PUT_profile_name_Should_Reject_NonAdmin_Changing_Other_Player()
168168
.Controller<ProfilesController>()
169169
.WithTestRig(rig)
170170
.WithAuthenticatedUser(rig.PeonUser())
171-
.Calling(c => c.PutProfileByNameAsync(
171+
.CallingAsync(c => c.PutProfileByNameAsync(
172172
"Wizard",
173173
new ProfileDto() { Name = "Wizard" }))
174174
.ShouldReturn()
@@ -182,7 +182,7 @@ public void PUT_profile_name_Should_Allow_Admin_Changing_Other_Player()
182182
.Controller<ProfilesController>()
183183
.WithTestRig(rig)
184184
.WithAuthenticatedUser(rig.WizardUser())
185-
.Calling(c => c.PutProfileByNameAsync(
185+
.CallingAsync(c => c.PutProfileByNameAsync(
186186
"Peon",
187187
new ProfileDto() { Name = "Peon" }))
188188
.ShouldReturn()
@@ -196,7 +196,7 @@ public void PUT_profile_name_Should_Reject_Unknown_Attributes()
196196
.Controller<ProfilesController>()
197197
.WithTestRig(rig)
198198
.WithAuthenticatedUser(rig.PeonUser())
199-
.Calling(c => c.PutProfileByNameAsync(
199+
.CallingAsync(c => c.PutProfileByNameAsync(
200200
"Peon",
201201
new ProfileDto()
202202
{

‎Guncho.Core/Commands.cs

+68-66
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Guncho.Connections;
22
using System;
3+
using System.Linq;
34
using System.Threading.Tasks;
45

56
namespace Guncho
@@ -36,9 +37,7 @@ private async Task<HandleSystemCommandResult> HandleSystemCommandAsync(Connectio
3637
if (command.Length == 0)
3738
return result;
3839

39-
Player player;
40-
using (await conn.Lock.ReaderLockAsync())
41-
player = conn.Player;
40+
var player = conn.Player;
4241

4342
// check commands that can be used any time
4443
switch (command)
@@ -53,7 +52,8 @@ private async Task<HandleSystemCommandResult> HandleSystemCommandAsync(Connectio
5352
return result;
5453
}
5554

56-
if (player == null || player.Realm == null)
55+
IInstance instance;
56+
if (player == null || playerInstances.TryGetValue(player, out instance) == false)
5757
{
5858
// check out-of-realm commands
5959
switch (command)
@@ -119,18 +119,16 @@ private async Task<HandleSystemCommandResult> HandleSystemCommandAsync(Connectio
119119

120120
case "again":
121121
case "g":
122-
using (await player.Lock.ReaderLockAsync())
122+
var lastCmd = player.LastCommand;
123+
if (lastCmd == null)
123124
{
124-
if (player.LastCommand == null)
125-
{
126-
await conn.WriteLineAsync("No previous command to repeat.");
127-
return result;
128-
}
129-
130-
result.Handled = false;
131-
result.Line = player.LastCommand;
125+
await conn.WriteLineAsync("No previous command to repeat.");
132126
return result;
133127
}
128+
129+
result.Handled = false;
130+
result.Line = player.LastCommand;
131+
return result;
134132
}
135133

136134
// save command for 'again'
@@ -144,33 +142,34 @@ private async Task<HandleSystemCommandResult> HandleSystemCommandAsync(Connectio
144142

145143
private async Task LogInAsPlayerAsync(Connection conn, Player player)
146144
{
145+
logger.LogMessage(LogLevel.Spam, "Setting conn.Player");
146+
147+
var oldConns = openConnections.Keys.Where(c => c.Player == player).ToArray();
148+
147149
using (await conn.Lock.WriterLockAsync())
148150
conn.Player = player;
149151

150-
Connection oldConn;
151-
using (await player.Lock.ReaderLockAsync())
152-
oldConn = player.Connection;
153-
154-
if (oldConn != null)
152+
if (oldConns.Length > 0)
155153
{
156-
Func<Task> notifyOldConn = async () =>
157-
{
158-
await oldConn.WriteLineAsync("*** Connection superseded ***");
159-
await oldConn.TerminateAsync();
160-
};
161-
Func<Task> notifyNewConn = async () =>
154+
await Task.WhenAll(oldConns.Select(async c =>
162155
{
163-
await conn.WriteLineAsync("*** Connection resumed ***");
164-
await conn.FlushOutputAsync();
165-
};
166-
await Task.WhenAll(notifyOldConn(), notifyNewConn());
156+
logger.LogMessage(LogLevel.Spam, "notifyOldConn: Starting");
157+
await c.WriteLineAsync("*** Connection superseded ***");
158+
await c.TerminateAsync();
159+
logger.LogMessage(LogLevel.Spam, "notifyOldConn: Done");
160+
}));
161+
162+
logger.LogMessage(LogLevel.Spam, "notifyNewConn: Starting");
163+
await conn.WriteLineAsync("*** Connection resumed ***");
164+
await conn.FlushOutputAsync();
165+
logger.LogMessage(LogLevel.Spam, "notifyNewConn: Done");
167166
}
168167

169-
using (await player.Lock.WriterLockAsync())
170-
player.Connection = conn;
171-
168+
logger.LogMessage(LogLevel.Spam, "Sending MOTD");
172169
await SendTextFileAsync(conn, player.Name, Properties.Settings.Default.MotdFileName);
170+
logger.LogMessage(LogLevel.Spam, "Entering instance");
173171
await EnterInstanceAsync(player, await GetDefaultInstanceAsync(GetRealm(Properties.Settings.Default.StartRealmName)));
172+
logger.LogMessage(LogLevel.Spam, "Login complete");
174173
}
175174

176175
private async Task LogInAsGuestAsync(Connection conn)
@@ -187,7 +186,6 @@ private async Task LogInAsGuestAsync(Connection conn)
187186
} while (!players.TryAdd(key, null));
188187

189188
guest = new Player(-guestNum, guestName, false, true);
190-
guest.Connection = conn;
191189
players[key] = guest;
192190
playersById[-guestNum] = guest;
193191

@@ -241,40 +239,41 @@ private async Task CmdWallAsync(Connection conn, Player player, string args)
241239

242240
private async Task CmdTeleportAsync(Connection conn, Player player, string args)
243241
{
244-
Realm dest = GetRealm(args);
242+
var dest = GetInstance(args);
245243

246244
if (dest == null)
247245
{
248246
await conn.WriteLineAsync("No such realm.");
249247
return;
250248
}
251249

252-
using (await player.Lock.ReaderLockAsync())
250+
IInstance inst;
251+
252+
if (playerInstances.TryGetValue(player, out inst) && inst == dest)
253253
{
254-
if (player.Realm == dest)
255-
{
256-
await conn.WriteLineAsync("You're already in that realm.");
257-
return;
258-
}
254+
await conn.WriteLineAsync("You're already in that realm.");
255+
return;
259256
}
260257

261-
if (dest.GetAccessLevel(player) < RealmAccessLevel.Invited &&
258+
var realm = dest.Realm;
259+
260+
if (realm.GetAccessLevel(player) < RealmAccessLevel.Invited &&
262261
args.ToLower() != Properties.Settings.Default.StartRealmName.ToLower())
263262
{
264263
await conn.WriteLineAsync("Permission denied.");
265264
return;
266265
}
267266

268-
if (dest.IsCondemned)
267+
if (realm.IsCondemned)
269268
{
270269
await conn.WriteLineAsync("That realm has been condemned.");
271270
return;
272271
}
273272

274-
IInstance inst = await GetDefaultInstanceAsync(dest);
275273
await inst.ActivateAsync();
276274

277275
string check = await inst.SendAndGetAsync("$knock default");
276+
278277
switch (check)
279278
{
280279
case "ok":
@@ -315,38 +314,41 @@ private async Task CmdPageAsync(Connection conn, Player player, string args)
315314
return;
316315
}
317316

318-
if (msg.StartsWith(":"))
317+
if (msg.StartsWith(":", StringComparison.Ordinal))
319318
{
320319
msg = msg.Substring(1);
321-
using (await targetPlayer.Lock.ReaderLockAsync())
320+
321+
var sendPage = WithPlayerConnectionsAsync(targetPlayer, async c =>
322322
{
323-
if (targetPlayer.Connection != null)
324-
{
325-
await targetPlayer.Connection.WriteLineAsync(player.Name + " (paging you) " + msg);
326-
await targetPlayer.Connection.FlushOutputAsync();
327-
await conn.WriteLineAsync("You page-posed " + targetPlayer.Name + ": " +
328-
player.Name + " " + msg);
329-
}
330-
else
331-
{
332-
await conn.WriteLineAsync("That player is not connected.");
333-
}
323+
await c.WriteLineAsync(player.Name + " (paging you) " + msg);
324+
await c.FlushOutputAsync();
325+
});
326+
327+
if (await sendPage)
328+
{
329+
await conn.WriteLineAsync("You page-posed " + targetPlayer.Name + ": " +
330+
player.Name + " " + msg);
331+
}
332+
else
333+
{
334+
await conn.WriteLineAsync("That player is not connected.");
334335
}
335336
}
336337
else
337338
{
338-
using (await targetPlayer.Lock.ReaderLockAsync())
339+
var sendPage = WithPlayerConnectionsAsync(targetPlayer, async c =>
339340
{
340-
if (targetPlayer.Connection != null)
341-
{
342-
await targetPlayer.Connection.WriteLineAsync(player.Name + " pages: " + msg);
343-
await targetPlayer.Connection.FlushOutputAsync();
344-
await conn.WriteLineAsync("You paged " + targetPlayer.Name + ": " + msg);
345-
}
346-
else
347-
{
348-
await conn.WriteLineAsync("That player is not connected.");
349-
}
341+
await c.WriteLineAsync(player.Name + " pages: " + msg);
342+
await c.FlushOutputAsync();
343+
});
344+
345+
if (await sendPage)
346+
{
347+
await conn.WriteLineAsync("You paged " + targetPlayer.Name + ": " + msg);
348+
}
349+
else
350+
{
351+
await conn.WriteLineAsync("That player is not connected.");
350352
}
351353
}
352354
}

‎Guncho.Core/Connections/IConnectionManager.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
namespace Guncho.Connections
66
{
7-
public class ConnectionEventArgs<TConnection> : EventArgs where TConnection : Connection
7+
public class ConnectionEventArgs<TConnection> : EventArgs
8+
where TConnection : Connection
89
{
910
public TConnection Connection { get; set; }
1011
}
11-
12-
public class ConnectionAcceptedEventArgs<TConnection> : ConnectionEventArgs<TConnection> where TConnection : Connection
12+
13+
public class ConnectionAcceptedEventArgs<TConnection> : ConnectionEventArgs<TConnection>
14+
where TConnection : Connection
1315
{
1416
/// <summary>
1517
/// The name of the user if the user's identity is already authenticated, otherwise null.

‎Guncho.Core/Connections/SignalRConnectionManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void NotifyConnectionAccepted(string connectionId, string playerName = nu
7070
{
7171
var connection = new SignalRConnection(this, connectionId);
7272
connections[connectionId] = connection;
73-
ConnectionAccepted(this, new ConnectionAcceptedEventArgs<SignalRConnection>()
73+
ConnectionAccepted?.Invoke(this, new ConnectionAcceptedEventArgs<SignalRConnection>()
7474
{
7575
Connection = connection,
7676
AuthenticatedUserName = playerName ?? "Guest"
@@ -83,7 +83,7 @@ public void NotifyConnectionClosed(string connectionId)
8383
if (connections.TryRemove(connectionId, out connection))
8484
{
8585
connection.NotifyClosed();
86-
ConnectionClosed(this, new ConnectionEventArgs<SignalRConnection>() { Connection = connection });
86+
ConnectionClosed?.Invoke(this, new ConnectionEventArgs<SignalRConnection>() { Connection = connection });
8787
}
8888
}
8989
}

‎Guncho.Core/Connections/TcpConnection.cs

+8
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,15 @@ public async override Task<string> ReadLineAsync(CancellationToken cancellationT
5050
try
5151
{
5252
await FlushOutputAsync();
53+
5354
var str = await rdr.ReadLineAsync().WithCancellation(cancellationToken);
55+
56+
if (str == null)
57+
{
58+
whenClosed.TrySetResult(true);
59+
return null;
60+
}
61+
5462
LastActivity = DateTime.Now;
5563
return str;
5664
}

‎Guncho.Core/Connections/TcpConnectionManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ private async Task HandleTcpClientAsync(TcpClient tcpClient, int id, Cancellatio
5757
try
5858
{
5959
var tcpConnection = new TcpConnection(tcpClient);
60-
ConnectionAccepted(this, new ConnectionAcceptedEventArgs<TcpConnection> { Connection = tcpConnection, AuthenticatedUserName = null });
60+
ConnectionAccepted?.Invoke(this, new ConnectionAcceptedEventArgs<TcpConnection> { Connection = tcpConnection, AuthenticatedUserName = null });
6161

6262
try
6363
{
6464
await tcpConnection.WhenClosed();
6565
}
6666
finally
6767
{
68-
ConnectionClosed(this, new ConnectionEventArgs<TcpConnection> { Connection = tcpConnection });
68+
ConnectionClosed?.Invoke(this, new ConnectionEventArgs<TcpConnection> { Connection = tcpConnection });
6969
}
7070
}
7171
finally

0 commit comments

Comments
 (0)