LoCo Day 21

Almost no progress today; I neglected to set aside enough time from doing other things. I wrote a little more code to satisfy the tests I was working on earlier, but that’s about it.

If I were doing NaNo this year, and allotting the same amount of time I have been to this, I bet I’d be several thousand words behind at this point.

Comments Off

LoCo Day 20

Today I cleaned up the test cases I created yesterday. I simplified some of the things I was checking for, and as a result most of the tests had common functionality that I was able to pull out into separate top-level functions, which makes the testing code a lot more readable and helps prevent the type checker from going too far down the garden path if there’s a type error somewhere.

That done, I then started implementing some of the actual game-related code. Well, OK, mostly the JSON conversion code, but tomorrow I’ll tackle the real game stuff. The first thing I’ll need to figure out is how to properly generate a starting configuration for a game, making sure players are reasonably spaced out. I don’t know offhand what the generally accepted algorithms are for that, but I bet I’ll be able to find out after a brief bit of searching.

Comments Off

LoCo Day 19

More work on the actual game part of the game. Actually, most of that work was on coming up with good QuickCheck tests to check the yet-to-be-written implementations of the functions that process player input and step the state of the game through time. The fact that most of the game operations live in the STM monad complicates things a little, but I’ve (mostly) gotten the hang of writing monadic QuickCheck properties.

Comments Off

LoCo Day 18

Today I implemented the fundamental movement and collision detection logic for the game. It’s fairly simple, but I shouldn’t need to elaborate on it too much more when I implement the actual game on top of it. I also implemented a transactional queue to hold a player’s moves before the game processes them; if a player sends multiple moves in rapid succession, it’s possible they’ll arrive at the server faster than the game can consume them, one move per time step. Using a TChan doesn’t work, since it would retry the transaction if the queue were empty, whereas a player not having a move queued up is perfectly valid, and in fact would be the normal situation. My transactional queue (implemented as a queue wrapped in a TVar) simply returns Nothing if the queue is empty, which the game will then interpret as being no change in the player’s movement direction.

Comments Off

LoCo Day 17

Today I started implementing the core types for the game itself. They’re not yet connected to the rest of the program yet, so they’re only being exercised by the QuickCheck test cases for them. It is nice to be working in the realm of pure code again, at least until I get to the point where I need to start worrying about user interaction again.

Comments Off

LoCo Day 16

Today I got starting and ending games working. Each member of a room can signal when they’re ready to begin playing. Once everyone in a room is ready (whether it be because the last non-ready member became ready, or the last non-ready member left), and the room isn’t empty (in which case the first condition would be vacuously true), a new game starts. When it’s over, everyone becomes not-ready and the cycle begins anew.

Granted, the “game” that’s currently implemented amount to “wait five seconds for the game to end”. Woo. Sure, it’s more like a hole where an actual game can be put, which is sort of the point.

Since the game I have in mind is real-time and not turn-based, starting a new game requires starting a new thread, which will be responsible for periodically advancing the game state and sending messages to the room if anything interesting happens. Right now that thread just sleeps for a few seconds before ending the game, but it’s there. During a real game, players would affect the game state by asynchronously POSTing their moves, which the game thread would then be able to read at the next time step.

For a program being written in Haskell, I sure do have an awful lot of code living in the IO monad. I don’t think there’s really a way around this, though, since the multicast channels underlying my long polling implementation are based on TChans (so that requests have something to block on while waiting for messages), and the list of rooms is carried around inside an MVar (so multiple server threads can access it simultaneously). Most of the code I’ve been writing lately is directly tied to sending messages and/or manipulating a room, which pretty much forces IO. The game itself will have a bit of IO when it comes to queueing players’ moves, but it ought to be possible to make most of the game logic pure.

Comments Off

LoCo Day 15

Another day where I didn’t set aside enough time to get much accomplished. I addressed the problem of a user inadvertently getting unsubscribed from a room’s messages upon reloading the page by always (re-)subscribing them whenever they try to fetch messages. At worst, they’ll miss out on messages sent while the reload was taking place, but that’s reasonable.

I then started adding hooks for actually running a game out of a room. I got mostly through the code for letting members of a room signal when they’re ready to play. Right now, since there’s no actual game implemented, there’ll be a game-started message immediately followed by a game-over message once everyone is ready.

At some point I should write test cases for all the code that implements room-related stuff, but since it’s almost entirely within the IO monad, that makes coming up with good QuickCheck properties a nontrivial exercise. Doable, certainly — I did it for the multicast channel code, since there’d be all kinds of problems if that didn’t work correctly — but not nearly as easy as coming up with properties for pure code.

Comments Off

LoCo Day 14

Essentially no progress today, other than a little refactoring of the code I was working on yesterday. Clearly I failed to set aside enough time to work on things today.

Comments Off

LoCo Day 13

The overhaul of the room code is done, at least for now. Instead of there being only one room on the server, now any number of them can exist. Woo.

I think the subscribe/unsubscribe logic is going to need a change at some point, since even with the light amount of testing so far, I’ve uncovered some design problems:

  • If you reload the room page, there’s a race condition between the unsubscribe request sent by the unload handler, and the request for the actual page. Whether or not the browser gets re-subscribed for messages or not depends on the order the server processes the messages. Worse, since a non-subscribed session trying to fetch a room’s message always returns an empty list immediately (since there’s nothing to wait on), the browser starts hammering the server with long-poll requests. Subscribing needs to be made more robust; perhaps the act of trying to fetch a room’s messages should count as a subscription request too.
  • On Chromium, the page’s unload handler either doesn’t fire or doesn’t manage to send the unsubscribe request in time. Either way, the unsubscribe never happens. Presumably there’s a flaw in my JavaScript code that does this, but even then I need to automatically unsubscribe sessions from rooms if there isn’t any activity after a little while, which will be needed anyway in case a client’s browser drops off the net one way or another.

I’ll need to fix all that eventually, but for now I think I ought to work on the actual game part of this game I’m supposed to be writing.

Comments Off

LoCo Day 12

I restored the functionality I had before reworking the room code. The problem I mentioned yesterday was because I mistyped an identifier in the JavaScript code, and apparently using an undeclared and undefined identifier isn’t a problem as far as Firefox and Firebug are concerned. Contrast that with GHC, which not only flags that sort of thing as an error, but even offers suggestions as to which identifier you might have meant, sort of like suggestions in a word processor’s spell checker. Actually, it’s kind of surprising that GHC is the only compiler I’ve seen with that suggestion feature for undeclared identifiers. It seems rather obvious in hindsight.

I also fixed displaying of timestamps; now instead of showing an ugly complete UTC timestamp, the chat window just shows the time, converted to the browser’s time zone. Now that the polling fetches JSON objects instead of HTML fragments, that sort of thing is possible to do.

Now I’m working on adding support for having more than one room on the server. I expect this to occupy most of tomorrow’s effort too.

Comments Off

LoCo Day 9

Only incremental progress to report today. Shrinking the long-polling timeout from 60 seconds to 30 seconds seemed to solve the problem of Happstack killing handlers prematurely, and some of the Internals stuff in the Happstack documentation hints at being able to have some control over whether Happstack thinks a long-running handler needs to be killed or not. Worth looking into in the long run, but shortening my timeout is good enough for now.

Aside from that, most of today’s work was in formalizing the very hackish chat room code, converting it into a more general “room” concept that will better support layering a game on top of. The new code will also support having more than one room on the server. I hope to have that working tomorrow; the types are (mostly) implemented, but I still need to generate HTML from it and hook it into the handler logic.

Comments Off

LoCo Day 8

Long polling is working! I now have a very basic real-time web-based chat room implemented. JavaScript on the browser regularly polls for updates on the server, and adds them to the chat window if it finds any. The server thread handling the request blocks for a short while if no messages are available. It’s simple, but the messaging subsystem will be the foundation of client-server communications when the actual game is implemented.

The only problem with my implementation (other than the design limitations I mentioned in the previous post) is that sometimes the server returns a 500 error to the client’s long-poll request instead of a 200 or 204 based on whether any messages are available. The error seems to be getting spit out by Happstack itself, since the specific error message (“server error: thread killed”) isn’t anything I’ve written. My best guess at the moment is that Happstack imposes its own timeout on request handlers, and when it sees it hasn’t finished after a while, assumes that the handler has gotten hung and kills it. If this is the case, the solution would be to either shorting the long-poll timeout in my code, or see if there’s a way to adjust Happstack’s timeout.

I think that after fixing that issue, I’ll migrate the chat room code into a more general “room” abstraction that’ll be amenable to building the game on top of. (The chat room code is a bit hackish at the moment.) I’ll then probably make it so you can have more than just one room in existence, which could make the main server logic a bit neater too, and give some more realistic URLs too.

Comments Off

LoCo Day 7

I think I have long-polling for chat messages mostly implemented. The one missing piece is retrieving messages on the server side. The client side can send and (try to) receive messages, and the server accepts new messages.

One obvious flaw in the current implementation is that the chat room queues messages for clients based on session ID, so if someone opened the page in two separate tabs in their browser, messages would only appear in one tab or the other, since the server wouldn’t be able to distinguish the two — each tab’s requests would be carrying the same session ID. When my implementation gets more sophisticated, I’ll either need to add another ID for a particular instance of having the page loaded, or just block attempts to have the same chat room (or game or whatever) opened multiple times by the same session.

Comments Off

LoCo Day 6

Some actual progress, now that I’ve abandoned trying to get Heist to do what I want it to and switched to Blaze for HTML generation.

Then I implemented site-wide CSRF prevention. Whenver the server receives a POST request, it checks that the csrfToken parameter matches the one stored in the session for that request. If not, it stops processing the request and spits out an error. Unfortunately, if the browser has cookies disabled, that’ll also trigger the error message, since the server won’t be able to associate the request with an existing session. Either that’ll need to be fixed somehow, or I’ll at least need to detail that cause in the error message. After all, if there really is a CSRF attack taking place, no one will actually be reading the page that gets returned anyway.

After that, I implemented a multicast messaging system that will serve as the innermost component for letting browsers poll efficiently for game-related events. Internally it’s built around transactional channels, with my code dealing with creating and destroying TChans as players enter or leave the game, and with retrieving all queued messages without blocking. It’s that last bit that requires using TChan instead of the slightly simpler Chan; the latter doesn’t have a reliable way to check whether the channel still has anything left in it.

Finally, I started working on a basic chat room mechanism, to operationally test the messaging system and to serve as a starting point for the JavaScript code that’ll run in the user’s browser. I haven’t had a chance to get very far with this piece yet, but it ought to be fairly straightforward.

It feels good to be making actual progress once again.

Comments Off

LoCo Day 5

Yet another day with little-to-no direct progress, but I think I did finally get to the heart of the problem I’ve been wrestling with. I’ve asked on Stack Overflow if there’s a way around it, in case I’m still missing something, but I don’t think I am.

The basic problem I ran into is this: In Heist, page templates can only run Haskell code by invoking splices, which are defined in the context of the program’s application stack. As a result, splices can’t see anything that doesn’t directly originate in that monad. However, when using web-routes for type-safe URLs, parameters passed via the URL path itself, instead of the query string, are inaccessible from the application monad; instead, they are accessed by pattern-matching against the type-safe URL. Likewise, the session object created before invoking the web-routes part of the code lives outside of the application monad. As a result, splices can’t get at URL path parameters or the session object, and there doesn’t seem to be a way around this inherent design limitation.

The problem doesn’t seem to arise when using Snap instead of Happstack, since Snap’s routing mechanism extracts URL path parameters, gives them names, and treats them as though they were other query string parameters, in which case they can be accessed via a Snap-based monad. I imagine something similar happens when working with sessions. Since Heist arose out of the Snap project, it’s not surprising its splices are limited to getting things out of the application monad, since in Snap that’s less of a limitation than it is in Happstack. No wonder that all the examples of Heist I’ve come across hand-wave getting request parameters by assuming there’s something in the application monad to do that.

I think that means I should give up on Heist (barring an answer to the aforementioned question that points out a way around this issue entirely) and use Blaze to generate HTML instead, at least for now. Although I’m stuck with Blaze’s quasi-monadic syntax instead of something that looks like actual HTML, at least I get some extra type-safety guarantees by having the HTML templates be Haskell code instead of standalone files loaded at runtime.

I did effectively lose a couple days wrestling with this, but at least I’m coming out of it with a better understanding of what these libraries can and can’t do.