TL;DR - Nothing to see here, more refactoring and fun with state machines.
Nothing new and interesting to show today, the UI looks the same as yesterday. The code on the other hand has been almost completely refactored (again), but I’m really happy with where it is now. I cleaned up all the test code and did a lot of restructuring. It will be a lot easier to support opening multiple rooms now (possibly using a tabbed interface) which was one of the main things I wanted to support.
Another major change was to the networking code. As I’ve mentioned before Screeps has two client APIs, a REST API for static data and a WebSocket API for live data (room entity updates, console update etc) both APIs are required to build a usable client.
I didn’t want any of the application data models or UI code to be concerned with having to build HTTP requests for the REST API, format data for the WebSocket API or parse JSON packets so I wrote a wrapper class for each API (
SocketNetworkManager) with interfaces and nicley packaged PODs that hid all the gory networking details from the upper layers.
But, once I completed the
SocketNetworkManager I realised, they’re not mutually exclusive … I need both of them available for the client to function and they both need to be authenticated, so how will the connect sequence work? Eventually the user will be presented with a login dialog to enter the server endpoint and authentication credential … but then what?
If the HTTP server hosting the rest API is up but the websocket endpoint isn’t working what do I do? What if the websocket connection is dropped while the client is running. I don’t really want to be juggling two different network managers. Also the two APIs are linked, before you can send any commands over the websocket API you need to send an authentication token … but you first have to get this authentication token by authenticating with the REST API…
So I needed to think about the connection sequence … enter a new level of abstraction …
NetworkModel. This new model acts as the interface between the UI and both API network managers and most importantly it manages the connection making sure that both APIs are available before the UI can make any requests.
To handle the connection sequence I implemented a state machine with the following states: -
Disconnected - Nothing interesting to see here, we can’t do anything yet. When
NetworkModel::openConnection() is called I attempt to open a connection to the websocket API. On success we move to the next state …
SocketConnected - We have an websocket connection and we know we at least have a working network connection and the websocket is available. But we can’t doing anything with it until we authenticate so we now need to check the HTTP endpoint for the REST API. Now I could just POST
/api/auth/signin to authenticate the user and get the authentication token but I just want to check if the HTTP endpoint is available and since there’s an endpoint to get information about the server which doesn’t required authentication, I figure I might as well just grab that now (it also returns some information about the server that I will need later anyway). So a call to
/api/version REST endpoint is made and on success we move to the next state.
RestConnected - Ok it’s not really a “connection” but now we know the REST endpoint is available, time to get that authentication token so a call to the
/api/auth/signin REST endpoint is made using the the users username and password … on success we move to the next state…
RestAuthenticated - Excellent … at this point we have received an authentication token form the REST API … time to get the websocket API authenticated so we send the “
auth [token]” command down the websocket … and on success … you guessed it … we move to the next state.
SocketAuthenticated - Technically both “connections” are in a state where the client can now make any requests or execute any commands but since I’ve just authenticated the user I figure this is as good a place as any to get information about the signed in users account so a call to the
/api/auth/me REST endpoint is made and … on success … we go to yet another state …
Connected - Yay! Finally! The was much rejoicing … as far as the UI is concerned we are now connected.
It looks like a lot when written down but from the UI layers point of view it’s all hidden. It’s also asynchronous, so now instead of juggling the two different network managers the UI only needs to make the following calls:
NetworkModel::openConnection([host], [port], [secure_flag])
And wait for the
NetworkModel::connected signal to be fired (or
NetworkModel::connectError if there’s any errors). I also added
NetworkModel::connectionStateChanged([oldState], [newState]) in case I want to show progress (everything happens pretty quickly though) or do anthing in intermediate states.
Anyway, that’s what I did today (took a bit more than an hour ) … sorry there’s no new UI or fancy graphics to look at yet, but I think this needed to be done before moving on.