27 May 2011
This post is part of a series describing the Threephase project.
The development of Threephase followed the test-driven design philosophy. This method encourages that before any implementation, a test case is written that exercises a small piece of desired functionality. Initially the test will fail since nothing has been implemented. The implementation goal is to write just enough code to pass the test - providing some degree of certainty in correctness and making sure no more code than is necessary is written. The resulting collection of test cases (the test suite) is also a critical tool for making sure that contributions from the open source community don’t break existing features. Each test case also serves as live, runnable documentation of how a class or method is supposed to work.
At its core, Threephase is a web application using the Ruby on Rails framework. Most of the interesting logic is in the application’s models (i.e. the State, Generator, etc.), so there is relatively loose coupling to Rails itself. The test suite uses the RSpec testing framework for its human-readable test cases and strong integration with Rails. To test the standard request/response patterns of the game’s API, the project uses a custom set of RSpec feature groups. These can be called like methods in a test case to avoid duplication, e.g.:
describe StatesController do
before do
@game = Factory :game
end
context "as an admin" do
before do
login_as_admin
end
it_should_behave_like "standard GET show"
it_should_behave_like "standard PUT update"
end
end
This example makes use of the it_should_behave_like
feature group ability in
RSpec, allowing much of the test logic to be shared among controllers. Object
Factories Instead of test fixtures (preloaded database objects), Threephase uses
the Factory Girl framework to use
object factories in test cases. Object factories are simpler to maintain than
fixtures, and minimizes work during development to data models that are in flux.
Threephase originally intended to use the Javascript graphics library Processing.js to render the map, charts and graphs in the browser. Upon further investigation, the library seemed to lack helpful charting features that other libraries offered, and the odd support of syntax from the Java version of Processing made using the language somewhat unnatural (a pure Javascript API to Processing.js was discovered later).
Instead, Threephase uses two different Javascript graphics libraries: Raphaël for basic charting and mapping and Flot for the piecewise graph necessary for the average cost curve visualization. Raphaël also has an existing charting extension, gRaphaël which reduced the amount of boilerplate graph code that had to be written.
The performance of both libraries is very good in modern browsers (performance tested in Mozilla Firefox and Google Chrome). The bottleneck for rendering complex visualizations at the moment is the time in downloading the data required from the server in the background, not rendering.
The majority of the objects in Threephase are stored using the standard object-relational mapping provided by Rails, backed by a PostgreSQL database. The objects in Threephase (and the physical entities in the real world) are highly relational, so this is a good choice not only for the wide support of SQL databases, but because it fits the data model well.
The browser-based nature of Threephase rests on HTTP, the most basic web protocol. HTTP has no knowledge of long running processes, and the relationship between a client and the server is finished after a single request/response cycle. There is not an immediately clear way to mesh this with the very demanding interaction required by games.
In order to provide reasonable response times, so players don’t get impatient waiting for pages to load, the majority of the computation to update the game needs to happen outside of the normal player interaction cycle - whether updating one element or a thousand.
A desktop game may use different threads of execution to make sure a player is never waiting for a network packet to finish downloading, or for a texture to load from the hard drive. In web applications, the server can use asynchronous task queues to accomplish the same thing.
Whenever possible, computation is bundled up into a “task” and queued to run at a later time - ideally as soon as possible, so the player gets updated data, but with no guarantee that it will happen before the server returns a response to the client. If the job hasn’t completed, it may return cached data that is valid, but not completely up-to-date. There is a trade-off between performance and liveliness, and in this case player perception of the game’s speed is more important than absolutely current information. To accomplish this, Threephase uses the Resque background job library backed by a Redis database.
Examples of work to be done in tasks include:
Currently, Threephase only uses one simple task to update the game world. As more update logic is added, they will be done as tasks.
Continue to the next section, an evaluation of Threephase.