Developer experiences from the trenches

Developer experiences from the trenches

Start of a new post

Git-Svn Considered Harmful

Sun 31 May 2015 by Michael Labbe
tags code 

Git-svn is the bridge between Git and SVN. It is more dangerous than descending a ladder that into a pitch black bottomless pit. With the ladder, you would use the tactile response of your foot hitting thin air as a prompt to stop descending. With Git-Svn, you just sort of slip into the pit, and short of being Batman, you’re not getting back up and out.

There are plenty of pages on the Internet talking about how to use Git-svn, but not a lot of them explain when you should avoid it. Here are the major gotchas:

Be extremely careful when further cloning your git-svn repo

If you clone your Git-svn repo, say to another machine, know that it will be hopelessly out of sync once you run git svn dcommit. Running dcommit reorders all of the changes in the git repo, rewriting hashes in the process.

When pushing or pulling changes from the clone, Git will not be able to match hashes. This warning saves you from a late-stage manual re-commit of all your changes from the cloned machine.

Git rebase is destructive, and you’re going to use it.

Rebasing is systematic cherry picking. All of your pending changes are reapplied to the head of the git repo. In real world scenarios, this creates conflicts which must be resolved by manually merging.

Any time there is a manual merge, the integrity of the codebase is subject to the accuracy of your merge. People make mistakes — conflict resolution can bring bugs and make teams need to re-test the integrity of the build.

svn dcommit fails if changes conflict

This might seem obvious, but think of this in context with the previous admonishment. If developers are committing to SVN as you perform time consuming rebases, you are racing to finish a rebase so you can commit before you are out of date.

Getting in a rebase, dcommit, fail, rebase loop is a risk. Don’t hold on to too many changes, as continuously rebasing calls on you to manually re-merge.

When Git-SVN Works

Here are a handful of scenarios where git-svn comes in handy and sidesteps these problems:

Start of a new post

What I Learned From Sharing Native File Dialogs

Thu 27 November 2014 by Michael Labbe
tags code 

I open sourced Native File Dialog last night. It’s a lean C library that pops up a native file dialog on Linux, Mac and Windows. It seems to have scratched an itch that a lot of people have, as it’s been starred 50 times on Github overnight, and my announcement tweet has received 27 retweets and 52 favorites as of this morning.

I did learn a few things writing and releasing this. Here they are in no particular order.

Linux users are reticent to link against GTK+. Plenty of people don’t want the overhead of GTK+ just to get a few paths back from the user. This makes sense if your app benefits from staying small. In order to get GTK+ up, you have to link against quite a few libraries: Cairo, GLib and GTK+ itself, to name three.

Interestingly, there must be similar overhead in Windows, but it isn’t as visible; most of what you need is given to you through COM object initialization, not linking libraries. Linux has no official toolkit, and as a result, the cost is much more visible to developers.

Of course, you can release COM objects.

GTK+ has no uninitialization. Once you have paid the cost of initializing GTK+, it permanently allocates private memory in your process. This is a shame. At least I can release COM objects.

This has led people to suggest Zenity and pipes; actually launching the file dialog in a separate process! I have always been skeptical of this approach — what happens when the dialog opens in another workspace, or the main app crashes, or X remoting causes other idiosyncrasies?

At any rate, I would be happy to accept an nfd_zenity.c patch which allows people to trade off between these problems.

People Really Like to just add one file to their project. Native File Dialog builds with SCons, which, admittedly, is not something every developer has installed. STB has spoiled people — they really want to just drop a file in to their current build! I am going to provide instructions that help people bypass SCons.

Feedback is hard to come by when you work on a small team. If you open source something useful, devs will scrutinize it and provide feedback. It doesn’t have to be a large piece of code — actually, it’s probably better that it’s not! If NFD did ten things, the issues people raised would have been broader, and the criticisms received may not have been as beneficial to me.

Start of a new post

Patterns for Multiplayer Game Variety

Mon 28 April 2014 by Michael Labbe
tags code 

Game Rules

So you’ve made a super fun multiplayer game and you want to expand it by adding game modes, tweaks and so forth. Here are some possible do’s and don’ts that allow for rapid expansion and moddability.

The approach of branching your game logic by game modes makes your code unexpandable. Consider:

if ( gamemode == MODE_DEATHMATCH ) {
    DisplayFreeForAllScoreboard();
} else if ( gamemode == MODE_CTF || gamemode == MODE_TEAMDM ) {
    DisplayTeamScoreboard();
}

This works with a finite number of game modes, but it makes modding hard. It also makes expanding the number of game modes difficult. A better approach is to create an abstract game rules base class which is derived for each game mode.

class IGameRules
{
public:
    virtual int GetTeamCount( void ) const = 0;
};

class GameRulesDeathmatch : public IGameRules
{
public:
    int GetTeamCount( void ) const {
        return 1;
    }
};

class GameRulesCTF : public IGameRules
{
public:
    int GetTeamCount( void ) const {
        return 2;
    }
};

Now you can simply write the game logic as:

if ( gamemode.GetTeamCount() == 1 ) {
    DisplayFreeForAllScoreboard();

} else if ( gamemode.GetTeamCount() == 2 ) {
    DisplayTeamScoreboard();
}

This approach lets you easily extend your game code to new game mode variants by simply deriving from the IGameRules hierarchy.

Modifiers

Unreal gets credit for being the first game to use modifiers. Modifiers are tweaks to tuning values — they are not holistic mods. This lets the player apply, for example, low gravity and instagib at the same time, by selecting two different mods. (Thereby ruining my vanilla experience, grr…)

This is pretty simple: apply modifiers from top-to-bottom order. Call out conflicts. Unreal did modifier selection with a complete UI in the late 90s.

Combine the Two

Consider exposing game rules methods as modifier tunable values. For example, if you have a game rule bool IsElimination(), which causes players to not respawn after they die, exposing this as a modifier value will allow a modder to go in and take an existing game mode, say, Team DM, and turn it into an elimination mode. Boom! A modder just recreated a simple Clan Arena mode with a text file and no need to learn a scripting language.

Start of a new post

Good Code Doesn’t Tolerate Bad Data

Mon 07 April 2014 by Michael Labbe
tags code 

When something is not right in your game’s simulation, complaining loudly to the developer-culprit as early as possible roots out issues. Production code that tolerates failure at a data processing level while producing a completely errant play experience saves no time.

If a developer commits errant content and time goes by, the cost of fixing it goes up. The developer may have forgotten the intricacies of their contribution, or worse, be on a different project. The content may need to be regenerated from source files in a manner that is either unclear or is not available to the person who deals with the bug. The symptoms of the bug may be disconnected from the problem. For example, I have seen a non-normative bitrate in an audio file corrupt a stack, becoming a time consuming issue to track down.

When you realize a subtle warning was added to a programmer-facing debug log that stated the issue, but was ignored by the developer who added the file, it is time to look for better solutions.

Throwing assert messages when an invalid programmatic state is hit is a common practice for trapping code logic errors. Extending this diagnostic trip-up to content issues for non-programmers is a useful tool for getting in developers faces at the right moment in time — when the developer is trying out their new content for the first time.

What can a content alert do for you?

  1. Provide validation that content is consistent with the engine’s expectations. For example, if a PNG has a corrupt header, there is nothing wrong with the PNG loader logic. It’s just dealing with questionable data. Sure, it could probably display something if the rest of the file is structured properly, but this is indicative of a bad file and you need to get this up in developer faces before they move on to other challenges.

  2. Test code that runs as soon as possible. If you have a cooking stage that runs over your content, throw your alerts then. If you do not, do it at level load. Validate everything.

  3. A way of passing a diagnostic message on to the content creator as soon as possible. Short circuit the QA/bug tracker loop for content creators (level designers, artists, audio engineers). This saves time by providing the opportunity for a specific diagnostic message that gets to the root of the issue. Bug reports from QA describe the symptom of the issue and usually lack direct diagnostic messages. This is much quicker.

Real-World Content Asserts

Resource r = LoadResource();
if ( !r.GetBPP() == 32 )
    ContentFail( "Resource has invalid bitdepth" );

ContentFail is a preprocessor macro which, in developer-friendly builds, accumulates a descriptive list of issues for the content creator.

How to implement ContentFail in C

You know which builds are going to developers and which are going to the end user. Use conditional compilation to optionally throw a message up in front of the user.

#if ENABLE_CONTENT_DIAGNOSTICS
#define ContentFail(msg) (void)(HandleContentFail( __FILE__, __LINE__, msg ) )
#else
#define ContentFail(msg) ((void)0)
#endif


void HandleContentFail(
     const char *file,
     int line,
     const char *failmsg )
{
    /* Append failmsg to diagnostic list here */
}

In this implementation, ENABLE_CONTENT_DIAGNOSTICS is on for all builds going to developers and, presumably, off for ship. You accumulate a list of issues and push them to a dialog box after a level load, treat them as compile warnings in a build process or purposely sound an alarm in the cook process.

One benefit of compiling out the content asserts in release builds is avoiding the fear that you are adding a ton of diagnostic strings to shipping code. Go nuts here — be as descriptive and as helpful as possible.

None of this is particularly fancy, complicated or tricky to implement in any language. It amounts to adopting a philosophy of enforcing correctness as early as possible in the design of your tools.

« Page 2 / 3 »

rss
We built Frogtoss Labs for creative developers and gamers. We give back to the community by sharing designs, code and tools, while telling the story about ongoing independent game development at Frogtoss.