I’ve started a new open-source project to make an ES designed specifically to be ported to different platforms and work the same way, with the same class/method names, on every platform:
Here’s some thoughts on “why” I’m doing this, and “how” I’m making it work…
I’ve written Entity Systems in a few different languages:
- C (2 times: both small)
- C++ (twice: one BIG, one “proof of concept”)
- Objective-C (3 times: two small, one a port)
- Java (3 times: two small, one big)
…and I’ve noticed some recurring issues.
What is “Cross Platform”?
In 2014 game-development, there are a few monolithic platforms where you can thrive while only knowing/mastering a single language:
- Unity: C# (any other options are an illusion ;))
- Unreal: C++ (with Unreal 4…)
- Mobile (Apple): ObjectiveC (unless you work at a mainstream games-studio, in which case: C++)
- Mobile (“other”): Java (only Android and iOS have big enough market share for most of us to do *single platform* games)
- Console (both TV and handheld): C++ (of course!)
Note: each of these represents tens of millions (usually: hundreds of millions) installed userbase. When you’re shipping a game, a hardware base of “200 million” is enough for you to not bother with multi-platform code!
If you want to go cross platform, you have a couple of options:
OPTION1: Use the Lingua Franca
C++ works almost everywhere, and it has good compiler support on most platforms (not perfect – e.g. Google seems to hate it, with their ADK for Android sucking particularly hard – but close).
Downside: requires you to use C++ for everything, even game projects that don’t “need” it. C++ code is somewhere between 20% and 50% more expensive to write and maintain on like-for-like problems than most other platforms (it’s super powerful, verbose, low-level, and prone to horrors of e.g. manual memory management, ..etc).
OPTION2: Use a cross-platform engine
Unity says Hai.
Downside: You have to use C#, which is popular but not super popular outside of MS/Windows – many devs don’t (yet) use it. Also, Unity is a relatively immature platform, with many outstanding bugs and missing features. I love Unity, but it’s the cheap equivalent of many of the other platforms.
OPTION3: Learn another language
This is often the easiest and cheapest approach – but the one that developers (especially Indies) find most terrifying. Learning a new spoken language is much harder than learning a new programming language; don’t be afraid!
Also … Senior programmers today are usually expert level in at least 2 distinct languages, and highly experienced in 2-4 more. There’s a high chance they already know the extra language they’ll need.
Downside: the API’s and libraries you’ve come to know and love: do they exist on the “other” platform?
A good cross-platform Entity System
Artemis is an early, simple ES design that’s very popular among small Indie game developers. Indies who’ve been publishing games for a long time usually have their own codebases already, but new ones tend to start with Artemis. I recently re-ported it to ObjectiveC, for use on iOS, and to learn how it’s put together (https://github.com/adamgit/ArtemisObjC).
I noticed a few things when porting it:
- Some features are “core” to the ES (e.g. Entity, Component), others are Artemis’s “special sauce” (e.g. Aspect, Bag), and the rest is “useful but optional extensions” (e.g. the different EntitySystem subclasses)
- Some constructs are impossible to port (e.g. Java allows two method names with the same name but different argument-types)
- Some constructs use standard libraries that need porting (e.g. Java’s BitSet class is easier to use than Apple’s CFBit / CFBitSet C-code (non OOP!))
- Some performance-tricks work almost identically on any platform (e.g. Artemis’s Bag class is very easy to port)
A few years back I wrote a couple of blog posts, and a couple of open-source GitHub projects, for an ultra simple ES that Just Works and is easy to port across platforms. The aim was to set a lowest-common-denominator, and to help those who learn best by reading source-code (I’m not one of them, but I know people who are).
They succeeded in that sense, but weren’t practical enough for use in production. Artemis came along and showed how you could make something much better, good enough for real games, with relatively little extra work.
So … my practical benchmarks:
- ABOVE ALL: when you write game-source-code using this library … is it short, sweet, and auto-completed by the IDE, or does it require lengthy typing and manual copy/paste?
- Every time I use a language feature: first check that feature can ‘easily’ be implemented on most/all other imperative/OOP languages
- When writing public interfaces: check that every method name, class name, etc is legal in most/all other languages
- When choosing data-structures: check there’s a “natural” implementation of that in other languages (e.g. “resizeable/dynamic List” exists everywhere; “ordered HashMap” does not. Sadly “struct” does not :( )
- When using an unusual standard-library (e.g. Java’s BitSet isn’t used often in normal apps): check it has direct counterparts in all languages – or indirect it via a custom class that wraps the platform-specific one.
- When making indirect pointers: think about the “efficient” implementation of this on a C-based language; is this compatible with zero-copy memory semantics? If not, it’ll destroy performance on some platforms
The Lowest Common Denominator: Objective-C
It’s not Obj-C’s fault: most of the problems come from C. C++ was invented largely to enable OOP coding in C … so it’s obvious that C will make modern coding (especially “library/API development”) difficult.
But Obj-C does add some interesting constraints of its own. It’s a superset of C, but adds its own form of “classes” that’s easy to use but not quite as robust or flexible as the ones we see in C++, Java, C#, etc. It has poor support for structs (a pity: that’s one of C’s strengths) … etc.
All in all: an excellent LCD for porting to other C-family languages.
THIS IS MAGIC: Generics in Objective-C
This is some serious voodoo. My first commit to Aliqua’s repository has a Unit test that does this:
Position* p1 = [[[Position alloc] init] autorelease];
p1.value = 42;
ALIListComponentInstances<Position>* positions = [ALIListOfPositions listOfPositions];
[positions set:p1 forEntity:0];
Position* fetchedPosition = [positions getPositionForEntity:0];
The first two lines are creating a new Component (the standard example: Position).
But the next three lines … whoa! That looks like Generics! And it’s somehow magically typesafe without casting! WTF?
I looked at this, went “WTF?” a few times. I thought about it, came back to it, tried it a few times. Then eventually figured out a way to make a Generic container for Component classes that’s automatically typesafe on iOS/ObjectiveC – using the ideas from Artemis’s Bag class.
The best bit of all?
Xcode5 auto-completes class and method names that are being auto-generated by C-style macros
Oh yeah! So all that stuff like “listOfPositions”, despite existing nowhere and being created on the fly (the Position.h and Position.m files have a one-liner to say “this is a Component, please DO YOUR VOODOO MAGIC”) … still auto-completes in Xcode. Yum.
I’m massively massively over-committed right now (7 x client apps to launch before the end of this month, plus a whole bunch of awesome stuff I’m trying to get funded and launched) … but I’m sneaking this library into my own projects now, and I’ll gradually add to it as I go.
I recommend Star’ing on GitHub: https://github.com/adamgit/Aliqua and following along.
As it stands, I probably won’t touch the other languages until Summer 2014, when I’m going to try and unify this with a Unity3D project (!) – giving me an excuse to try this design with C#. But anyone else who wants to port to other languages as we go along is welcome…