Not Crying Over Old Code
source link: http://twistedoakstudios.com/blog/Post8000_not-crying-over-old-code
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Critiquing My Own
The oldest project I have on github is Tinker (a warcraft 3 hosting bot). Tinker was a hobby project, has about 35 thousand lines of code, and is written in VB.Net. Some would say those facts don’t bode well.
I have a surprisingly good memory for code. Especially when compared to my day to day memory. It’s been years, but I still remember how Tinker is laid out and which classes I had trouble splitting up. Let’s look at one of the classes I remember being okay with, though not particularly happy about: GameServer. It’s been at least a year since I touched it, and years since I made any major changes. Here’s the class’ current code:
Public NotInheritable Class GameServer
Inherits DisposableWithTask
Private Shared ReadOnly InitialConnectionTimeout As TimeSpan = 5.Seconds
Private ReadOnly inQueue As CallQueue = MakeTaskedCallQueue
Private ReadOnly outQueue As CallQueue = MakeTaskedCallQueue
Private ReadOnly _clock As IClock
Private ReadOnly _logger As Logger
Private ReadOnly _gameSets As New Dictionary(Of UInt32, GameSet)()
Private ReadOnly _viewGameSets As New ObservableCollection(Of GameSet)(outQueue:=outQueue)
Private ReadOnly _viewActiveGameSets As New ObservableCollection(Of GameSet)(outQueue:=outQueue)
Private ReadOnly _viewGames As New ObservableCollection(Of Tuple(Of GameSet, Game))(outQueue:=outQueue)
Private ReadOnly _viewPlayers As New ObservableCollection(Of Tuple(Of GameSet, Game, Player))(outQueue:=outQueue)
Public Event PlayerTalked(sender As GameServer, game As Game, player As Player, text As String)
Public Event PlayerLeft(sender As GameServer, game As Game, gameState As GameState, player As Player, reportedResult As Protocol.PlayerLeaveReason, reasonDescription As String)
Public Event PlayerSentData(sever As GameServer, game As Game, player As Player, data As Byte())
Public Event PlayerEntered(sender As GameServer, game As Game, player As Player)
Private Sub ObjectInvariant()
Contract.Invariant(_clock IsNot Nothing)
Contract.Invariant(_viewGames IsNot Nothing)
Contract.Invariant(_viewGameSets IsNot Nothing)
Contract.Invariant(_viewActiveGameSets IsNot Nothing)
Contract.Invariant(_viewPlayers IsNot Nothing)
Contract.Invariant(_gameSets IsNot Nothing)
Contract.Invariant(_logger IsNot Nothing)
Contract.Invariant(inQueue IsNot Nothing)
Contract.Invariant(outQueue IsNot Nothing)
End Sub
Public Sub New(clock As IClock,
Optional logger As Logger = Nothing)
Contract.Assume(clock IsNot Nothing)
Me._logger = If(logger, New Logger)
Me._clock = clock
End Sub
Public ReadOnly Property Logger As Logger
Get
Contract.Ensures(Contract.Result(Of Logger)() IsNot Nothing)
Return _logger
End Get
End Property
Public ReadOnly Property Clock As IClock
Get
Contract.Ensures(Contract.Result(Of IClock)() IsNot Nothing)
Return _clock
End Get
End Property
'''Handles new connections to the server.'
Private Async Sub AcceptSocket(socket As W3Socket)
Contract.Assume(socket IsNot Nothing)
_logger.Log("Connection from {0}.".Frmt(socket.Name), LogMessageType.Positive)
Dim socketHandled = New OnetimeLock()
'Setup initial timeout'
Call Async Sub()
Await _clock.Delay(InitialConnectionTimeout)
If Not socketHandled.TryAcquire Then Return
socket.Disconnect(expected:=True, reason:="Timeout")
End Sub
'Try to read Knock packet'
Try
Dim data = Await socket.AsyncReadPacket()
If Not socketHandled.TryAcquire Then Return
HandleFirstPacket(socket, data)
Catch ex As Exception
socket.Disconnect(expected:=False, reason:=ex.Summarize)
End Try
End Sub
Public Function QueueAcceptSocket(socket As W3Socket) As Task
Contract.Requires(socket IsNot Nothing)
Contract.Ensures(Contract.Result(Of Task)() IsNot Nothing)
Return inQueue.QueueAction(Sub() AcceptSocket(socket))
End Function
Private Sub HandleFirstPacket(socket As W3Socket, data As IRist(Of Byte))
Contract.Requires(socket IsNot Nothing)
Contract.Requires(data IsNot Nothing)
If data.Count < 4 OrElse data(0) <> Protocol.Packets.PacketPrefix OrElse data(1) <> Protocol.PacketId.Knock Then
Throw New IO.InvalidDataException("{0} was not a warcraft 3 player connection.".Frmt(socket.Name))
End If
'Parse'
Dim pickle = Protocol.ClientPackets.Knock.Jar.ParsePickle(data.SkipExact(4))
Dim knockData = pickle.Value
'Handle'
Dim oldSocketName = socket.Name
_logger.Log(Function() "{0} self-identified as {1} and wants to join game with id = {2}".Frmt(oldSocketName,
knockData.Name,
knockData.GameId), LogMessageType.Positive)
socket.Name = knockData.Name
_logger.Log(Function() "Received {0} from {1}".Frmt(Protocol.PacketId.Knock, oldSocketName), LogMessageType.DataEvent)
_logger.Log(Function() "Received {0} from {1}: {2}".Frmt(Protocol.PacketId.Knock, oldSocketName, pickle.Description), LogMessageType.DataParsed)
inQueue.QueueAction(Sub() OnPlayerIntroduction(knockData, socket))
End Sub
'''Handles connecting players that have sent their Knock packet.'
Private Async Sub OnPlayerIntroduction(knockData As Protocol.KnockData, socket As W3Socket)
Contract.Assume(knockData IsNot Nothing)
Contract.Assume(socket IsNot Nothing)
'Get players desired game set'
If Not _gameSets.ContainsKey(knockData.GameId) Then
_logger.Log("{0} specified an invalid game id ({1})".Frmt(knockData.Name, knockData.GameId), LogMessageType.Negative)
socket.SendPacket(Protocol.MakeReject(Protocol.RejectReason.GameNotFound))
socket.Disconnect(expected:=False, reason:="Invalid game id")
Return
End If
Dim entry = _gameSets(knockData.GameId)
Contract.Assume(entry IsNot Nothing)
'Send player to game set'
Try
Dim game = Await entry.QueueTryAcceptPlayer(knockData, socket)
_logger.Log("{0} entered {1}.".Frmt(knockData.Name, game.Name), LogMessageType.Positive)
Catch ex As Exception
_logger.Log("A game could not be found for {0}.".Frmt(knockData.Name), LogMessageType.Negative)
socket.SendPacket(Protocol.MakeReject(Protocol.RejectReason.GameFull))
socket.Disconnect(expected:=True, reason:="A game could not be found for {0}.".Frmt(knockData.Name))
End Try
End Sub
Private Function AddGameSet(gameSettings As GameSettings) As GameSet
Contract.Requires(gameSettings IsNot Nothing)
Contract.Ensures(Contract.Result(Of GameSet)() IsNot Nothing)
Dim id = gameSettings.GameDescription.GameId
If _gameSets.ContainsKey(id) Then Throw New InvalidOperationException("There is already a server entry with that game id.")
Dim gameSet = New GameSet(gameSettings, _clock)
_gameSets(id) = gameSet
_viewGameSets.Add(gameSet)
_viewActiveGameSets.Add(gameSet)
Dim activeAdder As WC3.GameSet.StateChangedEventHandler = Sub(sender, active) inQueue.QueueAction(
Sub()
If _viewActiveGameSets.Contains(sender) <> active Then
If active Then
_viewActiveGameSets.Add(sender)
Else
_viewActiveGameSets.Remove(sender)
End If
End If
End Sub)
AddHandler gameSet.StateChanged, activeAdder
Dim gameLink = gameSet.ObserveGames(
adder:=Sub(game) inQueue.QueueAction(Sub() _viewGames.Add(Tuple.Create(gameSet, game))),
remover:=Sub(game) inQueue.QueueAction(Sub() _viewGames.Remove(Tuple.Create(gameSet, game))))
Dim playerLink = gameSet.ObservePlayers(
adder:=Sub(game, player) inQueue.QueueAction(Sub() _viewPlayers.Add(Tuple.Create(gameSet, game, player))),
remover:=Sub(game, player) inQueue.QueueAction(Sub() _viewPlayers.Remove(Tuple.Create(gameSet, game, player))))
'Automatic removal'
Call Async Sub()
Await gameSet.DisposalTask
_gameSets.Remove(id)
_viewGameSets.Remove(gameSet)
_viewActiveGameSets.Remove(gameSet)
Call Async Sub() Await gameLink.DisposeAsync()
Call Async Sub() Await playerLink.DisposeAsync()
RemoveHandler gameSet.StateChanged, activeAdder
End Sub
Return gameSet
End Function
Public Function QueueAddGameSet(gameSettings As GameSettings) As Task(Of GameSet)
Contract.Requires(gameSettings IsNot Nothing)
Contract.Ensures(Contract.Result(Of Task(Of GameSet))() IsNot Nothing)
Return inQueue.QueueFunc(Function() AddGameSet(gameSettings))
End Function
Protected Overrides Function PerformDispose(finalizing As Boolean) As Task
If finalizing Then Return Nothing
Return inQueue.QueueAction(
Sub()
For Each entry In _gameSets.Values
entry.Dispose()
Next entry
End Sub)
End Function
Private Async Function AsyncFindPlayer(username As String) As Task(Of Player)
Contract.Assume(username IsNot Nothing)
'Contract.Ensures(Contract.Result(Of Task(Of Player))() IsNot Nothing)'
Dim findResults = Await Task.WhenAll(From entry In _gameSets.Values Select entry.QueueTryFindPlayer(username))
Return (From player In findResults Where player IsNot Nothing).FirstOrDefault
End Function
Public Function QueueFindPlayer(userName As String) As Task(Of Player)
Contract.Ensures(Contract.Result(Of Task(Of Player))() IsNot Nothing)
Return inQueue.QueueFunc(Function() AsyncFindPlayer(userName)).Unwrap.AssumeNotNull
End Function
Private Async Function AsyncFindPlayerGame(username As String) As Task(Of Game)
Contract.Assume(username IsNot Nothing)
'Contract.Ensures(Contract.Result(Of Task(Of Game))() IsNot Nothing)'
Dim findResults = Await Task.WhenAll(From entry In _gameSets.Values Select entry.QueueTryFindPlayerGame(username))
Return (From game In findResults Where game IsNot Nothing).FirstOrDefault
End Function
Public Function QueueFindPlayerGame(userName As String) As Task(Of Game)
Contract.Ensures(Contract.Result(Of Task(Of Game))() IsNot Nothing)
Return inQueue.QueueFunc(Function() AsyncFindPlayerGame(userName)).Unwrap.AssumeNotNull
End Function
Public Function ObserveGameSets(adder As Action(Of GameSet),
remover As Action(Of GameSet)) As Task(Of IDisposable)
Contract.Requires(adder IsNot Nothing)
Contract.Requires(remover IsNot Nothing)
Contract.Ensures(Contract.Result(Of Task(Of IDisposable))() IsNot Nothing)
Return inQueue.QueueFunc(Function() _viewGameSets.Observe(adder, remover))
End Function
Public Function ObserveActiveGameSets(adder As Action(Of GameSet),
remover As Action(Of GameSet)) As Task(Of IDisposable)
Contract.Requires(adder IsNot Nothing)
Contract.Requires(remover IsNot Nothing)
Contract.Ensures(Contract.Result(Of Task(Of IDisposable))() IsNot Nothing)
Return inQueue.QueueFunc(Function() _viewActiveGameSets.Observe(adder, remover))
End Function
Public Function ObserveGames(adder As Action(Of GameSet, Game),
remover As Action(Of GameSet, Game)) As Task(Of IDisposable)
Contract.Requires(adder IsNot Nothing)
Contract.Requires(remover IsNot Nothing)
Contract.Ensures(Contract.Result(Of Task(Of IDisposable))() IsNot Nothing)
Return inQueue.QueueFunc(Function() _viewGames.Observe(
adder:=Sub(item) adder(item.Item1, item.Item2),
remover:=Sub(item) remover(item.Item1, item.Item2)))
End Function
Public Function ObservePlayers(adder As Action(Of GameSet, Game, Player),
remover As Action(Of GameSet, Game, Player)) As Task(Of IDisposable)
Contract.Requires(adder IsNot Nothing)
Contract.Requires(remover IsNot Nothing)
Contract.Ensures(Contract.Result(Of Task(Of IDisposable))() IsNot Nothing)
Return inQueue.QueueFunc(Function() _viewPlayers.Observe(
adder:=Sub(item) adder(item.Item1, item.Item2, item.Item3),
remover:=Sub(item) remover(item.Item1, item.Item2, item.Item3)))
End Function
End Class()>
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK