April 2015 plans

I’m back from my post-release vacation and excited to get back to work on Reassembly! These are the things I plan to focus on in the coming month.

New Features

Modding/Workshop support

Reassembly already supports a form of “User Generated Content” through the fleet sharing wormhole/agent system, and many users have had fun with the sandbox or by modifying cvars that control game behavior. I know for a fact that many Reassembly players are significantly better at building spaceships than I am. As a first pass at modding support, I want to expose and document the existing config files and let people publish their changes to the steam workshop, making it easy for other players to enjoy them. The initial mod workflow would be something like this:

  1. In game, convert an existing save slot containing ship designs into a “mod” for new-faction mods, or create an empty “mod”. A mod consists essentially of a number of text config files and spaceship files.
  2. Edit text files to define the mod. This includes adding new blocks or modifying existing blocks, adding or modifying spaceships, changing one of nearly 300 cvars that control various game behaviors, modifying the world generator config file, tutorial or message text, shaders, etc. You will be able to reload config files in-game and view relevant syntax errors.
  3. In game, upload the mod to the steam workshop.

Players will be able to download mods through the steam workshop and then turn them on or off and select precedence (if mods conflict) in-game. I’m excited to see what people come up with! I expect modding support to go through several iterations as we work with modders to expose more functionality and improve the workflow.

Modding support should allow the community to experiment with tons of new content much faster that I would be able to support myself.

Better Fleet AI controls

Better control over AI, both in tournament mode and in the main game, is probably the #1 requested feature. There is already a system in place for generating AI programs based on ship design and faction personality, so most of the work is in UI design. I’m planning to have per-design (this ship should only attack from long range, this ship should always rush, etc.) AI policy plus global fleet policy (everybody stop shooting, attack anything that moves, etc.).

Other Stuff

Localization

A significant fraction of our players are not native English speakers and translating the relatively small amount of in-game text and providing better default keybindings should improve their experience. The game already uses Unicode internally so code changes should be minimal. If anyone is interested in helping translate the game into their native language and/or setting up keybindings, please contact me.

Complete Gamepad Support

Likewise, adding gamepad support to the editor and menus would not be a huge amount of work and would be cool.

Wormhole server / feed

I recently added a search feature to the wormhole feed – there used to be a giant list of users at the bottom of the page but it was getting out of hand. I’ll continue to improve this system as well as the game server which processes wormhole uploads decides which agents populate new worlds.

Bugs / Polish

I’ve received several reports of black screen issues on certain just-barely-supported video cards. There are a few lingering general crashes, and glitches like agents spawning inside of asteroids. The tutorials, as always, need polishing. Some players are reporting trouble downloading agents when starting new games. There are always thousands of little changes that significantly improve gameplay but aren’t worth writing about individually.

Conclusion

I expect this work to take up most of April. Game development is an iterative, community-driven process and we’ll take another hard look at the game for June and see what needs to be done.

February 28th Tournament Results

Thanks to everyone that submitted ships and or tuned into the February 28th tournament! Special thanks to DeluksGaming for joining us!

There were some technical issues getting set up (and some exciting live-coding) so we were only able to run the first two categories. The seeding results are now online:

[Probe] [Interceptor]

Amazigh’s Funnel won the interceptor category and CmdrJohn’s Federation Run And Hide won the probe category.

The twitch streams are recorded on youtube: [Part 1] [Part2]

We will conclude the tournament on March 14th at 2PM PST.

Xcode/Clang gamedev tips

Reassembly was written primarily on OSX using Xcode for compiling/debugging and emacs for text editing. Xcode is clearly designed primarily for developing iOS “apps” and there are several non-obvious settings that vastly improve the desktop game development experience.

My general experience with Xcode has been fairly positive. I think Visual Studio is a slightly better debugger overall but not enough to make me abandon OSX. As a solo developer I need to spend a fair amount of time in cafes and other public places to avoid going insane and as far as I can tell Apple makes the best laptops.

My OSX builds are all 64 bit and have a deployment target of OSX 10.7. The following was written with reference to Xcode 6.1.1.

~/.lldbinit

This config file is read by lldb, the llvm debugger, which is the backend for the xcode debugger. Settings effect the xcode visual debugger window in addition to the console.

Print 64 bit unsigned values in hex and show a summary for vector types.

type format add -f hex uint64 "unsigned long long"
type summary add --inline-children --omit-names float2 double2 cpVect int2 float3

Print a summary for std::pair types (particularly useful for browsing std::map and std::unordered_map).

type summary add --inline-children --omit-names -x "^std::pair<.+>(( )?&)?$"

Print the address, “name” and “blocks” fields of custom type BlockCluster.

type summary add --summary-string "{addr=${var%V} name=${var.name} ${var.blocks}}" BlockCluster

One warning – invalid summary strings often crash Xcode. Restarting the debugging sessions is enough to reload .lldbinit after changes.

Develop No-resource build

For rapid iteration with good performance, I created a “Develop NoResource” target. This build has optimizations enabled (-O3), but no linktime codegen (which takes a long time). While iterating on code remove the main data directory from the “Copy Bundle Resources” build phase to prevent it from being copied every time. As long as you don’t clean previously copied resources will remain in the app directory.

In general lldb is “OK” at debugging optimized builds. It frequently looses the “this” pointer and stack variables but is nonetheless usually useful enough to get the job done. This is one of the areas where Visual Studio really shines, particularly with the magic option /d2Zi+ – see this Random ASCII post. Unfortunately C++ Debug builds, which the debugger readily understands, are usually too slow to be useful.

I have not been able to get the lldb debugger to call inlined stl symbols (e.g. std::vector::size) from the command line in optimized builds. If anyone knows how to do this please tell me.

Platform layers

I haven’t been able to completely abstract OS details into a third party platform independent layer like SDL. Reassembly uses SDL for window and event handling on windows and linux but is pure Cocoa on OSX. The Cocoa programming model is different enough from SDL that some level of native-ness would have been lost, and the amount of code required is not prohibitively large.

In general the Cocoa/Mac programming model is vastly nicer than win32 or POSIX/Linux. It does not carry 30 years of baggage, never refers to 16 bit OSs in in the docs, and provides easy to use APIs for common operations. Using the native API does not take more than a day or two to setup and forever after allows exact control and the ability to debug platform-specific bugs.

Once exception is the OSX Gamepad APIs which are unforgivably convoluted. Reassembly uses SDL to handle gamepads on OSX.

Command Line Builds

Xcode allows building projects from the command line. This is frequently convenient from e.g. emacs or as part of a release script.

xcodebuild -scheme "Reassembly Develop NoResources" \
    -workspace Reassembly.xcodeproj/project.xcworkspace \
    -jobs 4 -parallelizeTargets ONLY_ACTIVE_ARCH=YES

Note that, unlike with visual studio, xcode command line builds do not build the same target as the IDE and so you can’t do a command line build then switch to the IDE for debugging without doing an IDE build. Set the command line config for the non-root project in Project Settings -> Info.

For better interoperability with non-Xcode tools you can set the output directory of the compile .app in Xcode settings (command-,) under the Locations tab (see Derived Data).

Nested Projects

Screen Shot 2015-01-25 at 4.19.36 AM
A little-known feature of Xcode is the ability to add .xcodeproj files as subprojects to your main project file. This is analogous to adding various visual studio projects to the solution file. I generally find it easier to integrate 3rd party code as source vs. using static libs, dlls, dylibs, .so, etc. Creating or adding a separate .xcodeproj per library allows me to specify different compile time options for this code.

Crash Handling

OSX is a POSIX compatible OS which means we can use unix signals to handle Segmentation Faults and other joyous occurrences. Automatically handling game crashes and uploading stack traces to my webserver has been a powerful tool in increasing Reassembly stability, and probably a topic for another article in and of itself. Most of my OSX crash handling code was shamelessly copied from this article on Atomic Objects. Make sure to disable the signal handlers in debug/develop builds or the debugger won’t break on crashes.

The standard backtrace and backtrace_symbols calls work from signal handlers on OSX as expected. backtrace_symbols works even on release binaries and unlike Linux does not require the -rdynamic linker option.

Here is code for printing out some relevant registers on OSX or Linux after catching a SIGSEGV or similar:

    const ucontext_t *ctx = (ucontext_t*)context;
    const mcontext_t &mcontext = ctx->uc_mcontext;

#if __APPLE__
    const uint ecode = mcontext->__es.__err;
#else
    const greg_t ecode = mcontext.gregs[REG_ERR];
#endif
    string msg0 = str_format("Invalid %s to %p", (ecode&4) ? "Exec" : (ecode&2) ? "Write" : "Read", siginfo->si_addr);
    ReportPOSIX(msg0);
    message += msg0 + "\n";

    string mmsg;
#if __APPLE__
#ifdef __LP64__
    mmsg = str_format("PC/RIP: %#llx SP/RSP: %#llx, FP/RBP: %#llx",
                      mcontext->__ss.__rip, mcontext->__ss.__rsp, mcontext->__ss.__rbp);
#else
    mmsg = str_format("PC/EIP: %#x SP/ESP: %#x, FP/EBP: %#x",
                      mcontext->__ss.__eip, mcontext->__ss.__esp, mcontext->__ss.__ebp);
#endif
#else
#ifdef __LP64__
    mmsg = str_format("PC/RIP: %#llx SP/RSP: %#llx, FP/RBP: %#llx",
                      mcontext.gregs[REG_RIP], mcontext.gregs[REG_RSP], mcontext.gregs[REG_RBP]);
#else
    mmsg = str_format("PC/EIP: %#x SP/ESP: %#x, FP/EBP: %#x",
                      mcontext.gregs[REG_EIP], mcontext.gregs[REG_ESP], mcontext.gregs[REG_EBP]);
#endif
#endif
    ReportPOSIX(mmsg);

Threading

By default c++11 std::threads created under OSX have some ridiculously tiny amount of stack space. I use pthreads directly to create threads to overcome this – std::mutex and friends work fine with pthread threads (std::thread wraps pthreads anyway under OSX).

You can also set the current thread name such that it will show up in the Xcode thread view via a non-standard pthread API.

Creating Pthread with increased stack size

#if __APPLE__

typedef pthread_t OL_Thread;
#define THREAD_IS_SELF(B) (pthread_self() == (B))
#define THREAD_ALIVE(B) (B)

OL_Thread thread_create(void *(*start_routine)(void *), void *arg)
{
    int            err = 0;
    pthread_attr_t attr;
    pthread_t      thread;

    err = pthread_attr_init(&attr);
    if (err)
        ReportMessagef("pthread_attr_init error: %s", strerror(err));
    err = pthread_attr_setstacksize(&attr, 8 * 1024 * 1024);
    if (err)
        ReportMessagef("pthread_attr_setstacksize error: %s", strerror(err));
    err = pthread_create(&thread, &attr, start_routine, arg);
    if (err)
        ReportMessagef("pthread_create error: %s", strerror(err));
    return thread;
}


void thread_join(OL_Thread &thread)
{
    if (!thread)
        return;
    int status = pthread_join(thread, NULL);
    ASSERTF(status == 0, "pthread_join: %s", strerror(status));
}

#else

typedef std::thread OL_Thread;
#define THREAD_IS_SELF(B) (std::this_thread::get_id() == (B).get_id())
#define THREAD_ALIVE(B) ((B).joinable()) 

OL_Thread thread_create(void *(*start_routine)(void *), void *arg)
{
    return std::thread(start_routine, arg);
}

void thread_join(OL_Thread& thread)
{
    if (!thread.joinable())
        return;
    try {
        thread.join();
    } catch (std::exception &e) {
        ASSERT_FAILED("std::thread::join()", "%s", e.what());
    }
}

#endif

Naming threads

You can use the pthreads API to set the current thread name, and this name will show up in the Xcode debugger which is extremely helpful. Unfortunately the non-standard pthreads API for this is different across OSX and Linux (and of course windows) – code for all platforms follows, this works in GDB and Visual Studio.

#if _WIN32
//
// Usage: SetThreadName (-1, "MainThread");
//
const DWORD MS_VC_EXCEPTION = 0x406D1388;

#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
    DWORD dwType; // Must be 0x1000.
    LPCSTR szName; // Pointer to name (in user addr space).
    DWORD dwThreadID; // Thread ID (-1=caller thread).
    DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)

void SetThreadName(DWORD dwThreadID, const char* threadName)
{
    THREADNAME_INFO info;
    info.dwType = 0x1000;
    info.szName = threadName;
    info.dwThreadID = dwThreadID;
    info.dwFlags = 0;

    __try
    {
        RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }
}

#endif

void thread_setup(const char* name)
{
    uint64 tid = 0;
#if _WIN32
    tid = GetCurrentThreadId();
    SetThreadName(tid, name);
#elif __APPLE__
    pthread_threadid_np(pthread_self(), &tid);
    pthread_setname_np(name);
#else // linux
    tid = pthread_self();
    int status = 0;
    // 16 character maximum!
    if ((status = pthread_setname_np(pthread_self(), name)))
        ReportMessagef("pthread_setname_np(pthread_t, const char*) failed: %s", strerror(status));
#endif
}

Github

All of the code above is part of Reassembly and is available in context on my Outlaws core github project – see the os/osx directory for mac specific code.

Youtube

I, Arthur Danskin, the copyright holder of Reassembly, grant you, whoever you are, permission to post or stream footage of Reassembly on youtube, twitch, or similar video sharing sites, in the context of Let’s Plays, reviews, or for other purposes. You may also monetize this footage.

You may additionally include the game music in your video.

Including a link to purchase the game (store.steampowered.com/app/329130) and/or soundtrack (peakssound.bandcamp.com/album/reassembly) is encouraged but not required.

Also, thank you for posting videos. As an independent developer with a very limited advertising budget I rely on this kind of thing to let people know about my game.