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

removed mutable values and loops. Added railway oriented error handling #8

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

travis-leith
Copy link

No description provided.

Copy link
Owner

@matthid matthid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some questions and notes regarding the change in behaviour. Looks good overall let's see if we can get this in.

Thanks!

@@ -93,7 +100,8 @@ module Instance =

let createGame (instance:Sc2Instance) mapName (participants:Participant list) realTime = async {
let req = new RequestCreateGame()
for player in participants do
participants
|> List.iter (fun player ->
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if List.iter is that much better than just using a for loop...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind either way, happy to revert.

namespace Starcraft2

type ApplicationError =
|FailedToEstablishConnection of string
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okish with railway style as long as we properly forward error information (or logging it for that matter) instead of throwing everything away and only keeping the "Message" here.

"Standard" Exception handling has lots of features you now basically need to replicate manually.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

let stepResponse = response.Step
checkNullAndWarnings response stepResponse
return response.Status }
clientSock.ConnectAsync(fullAddress, tok) |> Async.AwaitTask |> Async.RunSynchronously
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the Sc2Connection constructor blocks and is no longer async. Another design might be

  • change the constructor itself to private
  • add an async static Create/Connect method
  • add the socket to the constructor parameters.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a connect function later on in the file, that is used to create a Sc2Connection

let connect address port timeout tok = async{
return Sc2Connection(address, port, timeout, tok)
}

Is this in line with your comment?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, RunSynchronously blocks the current thread which is not really required here. The problem why you are using it is because it is not possible to use async-code in the constructor. But you can workaround by just adding the connected socket to the constructor arguments and using an async static Connect/Create member and marking the constructor internal (as suggested by the initial comment)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IE move the getConnectedSocket() function call into connect and await it.

let sendRequest req = async{
return!
connectedSocket
|> Result.map getAgent
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you create a new agent every time? Are you sure that is what we want?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! this is not what we want. Will fix it.

@@ -0,0 +1,101 @@
namespace Rail

module Result =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should change the namespace and mark that module internal?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

|> Result.map Some
|> Result.map attachPart
|_ -> None |> attachPart |> Ok
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Previous code was parallel while this is sequentially.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix this.

Copy link
Owner

@matthid matthid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the one hand I see improvements and elegance in the handling of the protobuf types and the connection handling.

On the other side I feel like the game loop is harder to understand now. Maybe we can get the best of both worlds?

What do you think? (Sorry for the very late review :( )

x.Connection.Disconnect(exitInstance)
if (not(x.Process.WaitForExit(1000))) then
x.Process.Kill()
{Connection:ProtobufConnection.Sc2Connection; Process:System.Diagnostics.Process}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please restore spacing here

for clientPorts in ports.ClientPorts do
ports.ClientPorts
|>
List.iter (fun clientPorts ->
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'd prefer the for loop here as well :)

let stepResponse = response.Step
checkNullAndWarnings response stepResponse
return response.Status }
clientSock.ConnectAsync(fullAddress, tok) |> Async.AwaitTask |> Async.RunSynchronously
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, RunSynchronously blocks the current thread which is not really required here. The problem why you are using it is because it is not possible to use async-code in the constructor. But you can workaround by just adding the connected socket to the constructor arguments and using an async static Connect/Create member and marking the constructor internal (as suggested by the initial comment)

let stepResponse = response.Step
checkNullAndWarnings response stepResponse
return response.Status }
clientSock.ConnectAsync(fullAddress, tok) |> Async.AwaitTask |> Async.RunSynchronously
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IE move the getConnectedSocket() function call into connect and await it.

let private applyFieldCheckAndReturnFunction fieldCheck returnFunc (response:SC2APIProtocol.Response) =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename fieldCheck to getResponseField and returnFunc to getResult to match the naming below?

NewObservation = null
PlayerId = playerId
GameInfo = null }
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) >> Async.Parallel >> Async.RunSynchronously >> List.ofArray >> Result.listFold

let rec gameLoop playersResult =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe its just me but I feel the while loop is a lot more readable. Personally I feel like mutable state is acceptable at this level

open SC2APIProtocol

[<EntryPoint>]
let main argv =
let userSettings = Sc2SettingsFile.settingsFromUserDir()
//let userSettings = Sc2SettingsFile.settingsFromUserDir()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do we do here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants