The Tau Station Universe: Software
Previously we discussed the tech stack that Tau Station runs on, but today we thought that we’d give some in-depth examples of the software hurdles we face. There will be Perl code in this blog entry, but the concepts should be generally familiar to anyone with a software engineering background.
As we mentioned in the tech stack post, we use Catalyst for our Web framework. For those unfamiliar with Perl, you could think of Catalyst as “Ruby on Rails” for Perl, but that’s not really accurate. What makes Catalyst so powerful is that unlike other Web MVC (model-view-controller) frameworks, it doesn’t have strong preferences for how you implement the various components. You’re not forced to choose a particular ORM for your model — you can even skip an ORM entirely — and you can choose whatever tools you wish for rendering your view (typically, the stuff you see in a Web browser). As a result, you can choose exactly the tools you need for each component of your system.
MVC
First, we need a quick overview of “Web MVC” for those not familiar with it. “MVC” stands for “Model-View-Controller.” There are different ways of implementing MVC, so I’ll merely cover the way we have chosen to do so for Tau Station — a fairly straightforward Web MVC setup.
Model
The Model in MVC is your data model and the rules associated with it. For example, your data model might know that your character has 2,000 credits. You need to buy a replacement catalyzer for your ship, but it costs 2,100 credits. Can you borrow the money? If you’re buying the part from an NPC, would they be willing to sell it for less money? This is the sort of data and associated logic that belongs in the model. Needless to say, it gets complicated quickly and the bulk of the application is here.
View
The View in MVC is what the client sees. This could be an RSS feed, JSON API data from AJAX requests, or any of a variety of ways of sending information to clients. For the sake of this blog post, we’ll say that the “View” is the Web page you’re looking at when you play the game.
Tau Station’s view is straightforward. We have a strong focus on accessibility and amongst other things, that means we’re huge fans of progressive enhancement over graceful degradation, but we’ll cover this more in depth in a later post.
Controller
The Controller in MVC is, sadly, one of the most misunderstood pieces of the Web MVC pattern. The controller should just be receiving data from the view, sending it to the model, and sending the model’s response to the view (yes, this is an oversimplification). Unfortunately, far too many developers are prone to shove complex logic into their controller methods, making that logic hard to reuse and hard to test. When I’m consulting for clients, I routinely find controller methods with hundreds of lines of code. By contrast, here’s one of our controller methods:
sub purchase ( $self, $c ) : Local Args(0) {
$c->character->purchase_clone;
$c->send_to_index($self);
}
That’s in a controller named Veure::Controller::Area::Clonevat
(“Veure” was the codename of this project when it started). Because the above method is only dispatching and doesn’t contain any logic, you can probably figure out what it does. We’re fans of the thin controller/fat model style of development as the devs on the team have been bitten too many times working on projects which didn’t follow this model.
The Code
Now that we’ve explained the basics of MVC, let’s dive into some interesting bits of our code. For example, what does the actual $character->purchase_clone()
method we called above look like? It looks like this:
sub purchase_clone ($self) {
my $exchange = $self->new_exchange(
slug => 'purchase-clone',
success_message => 'You have purchased a new clone',
failure_message => 'You could not purchase a new clone',
steps => [
[Location => check_area => $self => 'clonevat'],
[Wallet => pay => $self => price('cloning')],
[Clone => gestate => $self => $self->station_area]
],
); return $exchange->attempt;
}
As you can see, this is very declarative in nature, despite it being “object oriented” code. We won’t explain the code line-by-line because if you read through it, you will understand it (even if you don’t know the Perl programming language):
- Make sure you’re at a clone vat.
- Make sure you can pay.
- Gestate a new clone.
Each of those steps depends on the step before it and if any step fails, the entire attempt fails and the player will receive an appropriate error message. All logging is handled behind the scenes, too, so that we can track what happens over time. Further, the various steps don’t touch the database unless we successfully complete the operation, making it easier to control the load on our database.
And if we needed to add a new step? For example, maybe you can’t get a clone unless your minimum reputation with the group who owns the station is high enough? There’s no complicated code. We would just add a new line and it would magically work:
sub purchase_clone ($self) {
my $exchange = $self->new_exchange(
slug => 'purchase-clone',
success_message => 'You have purchased a new clone',
failure_message => 'You could not purchase a new clone',
steps => [
[Location => check_area => $self => 'clonevat'],
[Reputation => minimum => $self => min_rep('cloning')],
[Wallet => pay => $self => price('cloning')],
[Clone => gestate => $self => $self->station_area]
],
);
return $exchange->attempt;
}
The above would require that we have an appropriate Reputation resource to check with, but we can then reuse this code easily, merely stating the rules required for any economic exchange.
Partial Classes
You might be wondering, however, about the “Character” class. For non-programmers it seems reasonable that a character can purchase a clone. For programmers, we might start worrying about our Character class turning into a god object where we shove all sorts of behavior, regardless of whether or not it belongs there. God objects are easy to create and unfortunately, make a huge mess of the code and drive up the cost of maintenance. So initially, we were trying to spread out the logic into separate classes. For example, how do you know if an NPC (non-player character) has a mission for your character? Our code looked like this:
if ( $missions->has_missions_for( $character, $npc ) ) {
...
}
But what does that even mean? It doesn’t read naturally and it’s very confusing. So we used basic linguistics to figure out how we wanted to write that and a touch of obscure computer science to fix that.
English is what linguists refer to as an SVO language. That means “subject, verb, object” (“Sally flies spaceships”) and that’s the order we’re used to seeing things. In the above code, the method call was structured as a VOS sentence (“Flies spaceships Sally”), something which is very unusual and thus hard to read.
Trivia note: English’s SVO structure is actually the second most common sentence structure on the planet. SOV languages (“Sally spaceships flies” are the most common).
But wait, this is an MMORPG. Your character drives everything. Your character moves. Your character goes on missions. Your character has to interact with their inventory. Your character has to do all sorts of things and artificially shoving those things into different classes just to avoid the “god object” makes for unclear code like the above. However, shoving all of that behavior into the Character class means it’s getting huge and unreadable.
Until now.
We don’t want to get too experimental with the programming of Tau Station. We want solid, proven concepts. However, we had hit a wall with the Character class when we were implementing missions. There was so much stuff going on that we needed a better organization and while trying to constrain missions to the smallest scope possible (you don’t want special cases for mission code littering your entire code base), we found ourselves pushing a number of mission-related methods into the growing Character class because, darn it, that’s where they belonged:
if ( $npc->has_missions_for($character) ) {
...
}
There is no ambiguity in the above code and despite the concerns about the maintainability of god objects, the above is easy to read.
If you’re familiar with the concept of a partial class, it’s a way of splitting a class’s implementation across several files. It’s usually created by the compiler auto-generating code so that the generator can write code to a partial class and it gets compiled into your class later. We decided to implement that ourselves using roles (also known as “traits”).
If you’re unfamiliar with roles, they’re like mixins, but the methods are composed directly into your class and if two or more methods share the same name, you get a compile-time error telling you to resolve the conflict. You can also state required methods and again get a compile-time error telling you that you’re missing some methods (this allows us to get some benefits of static languages in our dynamic language).
Or, if you’re from the Java world, think of roles as interfaces but with a defined implementation and a guarantee that your methods won’t conflict.
Or just watch this conference talk explaining how roles made code at the BBC much easier to maintain, or read this paper covering the same thing. If you take nothing from this blog post, you’ll want to learn about roles because they’re the most important advance in OO programming in five decades.
Roles let you safely share code across classes which are unrelated by inheritance. We didn’t want to share the Character code with different classes; we wanted to decompose it into logically related components. Now, near the top of the Character class, you’ll see something similar to the following (the following is an abbreviated list):
with qw(
Veure::PartialClass::Character::Inventory
Veure::PartialClass::Character::Mission
Veure::PartialClass::Character::Movement\
);
Each of those is implemented as a role, of course, but we call them “partial classes” instead of “roles” because that’s what they are. When the Character class is compiled, all of the methods from different partial classes are composed into the Character class, but that’s done behind the scenes.
Though this would probably be the most controversial part of our code base, this strange approach has shed many methods from the Character class and hundreds of lines of code. This has had several interesting benefits. It’s easy to understand what characters can do, it’s easy to find the code implementing that behavior, and it’s easy to follow the code in question. Had we not implemented separate character responsibilities as partial classes, Tau Station would have been much harder to work on. We keep waiting for us to find a flaw with this approach, but so far, it’s worked out stunningly well.
In a later post, we’ll detail some interesting features of our back end test suite that verifies the above works the way we want it to.
Originally published at taustation.space on December 15, 2016.