Checkers: Development panel with tweaks

·

I created a development panel for my game. It has two purposes:

In this post I will focus on the panel, but first let me mention other things that I’ve done since the previous update.

Since the previous update I have:

Development panel structure

First, please watch the whole video from the top of this post, if you haven’t already.

When designing my own tweaks, my thinking was directed mostly by annoyances that I’ve experienced when using Facebook Tweaks in Objective-C. Don’t get me wrong, they are not awesome but they are OK and you should totally use them if you don’t want to roll your own solution. Especially if you’re in the dark land of objc where there is only sorrow and eternal sadness…

I set myself the following goals for the end-user API:

I will start with the last one.

I decided to use the Service Locator pattern after reading about it in Robert Nystrom’s Game Programming Patterns book (which I recommend). The brief summary of this pattern is that instead of many singletons you use one, the service locator, with “the other singletons” exposed as its properties. The key part is that these properties can change and that access to them should always happen through the service locator. This allows you to, for example, disable services for release builds, when running tests, or in some other conditions, at runtime.

You might think it’s inelegant (“singleton, gah!”) but this pattern is a good fit for tweaks. A quote from the Service Locator chapter:

It is an ambient property of the environment, so plumbing it through ten layers of methods just so one deeply nested call can get to it is adding needless complexity to your code.

In my implementation of the Service Locator, I create the services singleton with a dummy developmentPanel property:

class Services {
    var developmentPanel = DevelopmentPanel(dummy: true)
}

let services = Services()

When the app launches, I switch to the real (not dummy) development panel in an app delegate:

if !application.isRunningTests {
    services.developmentPanel = DevelopmentPanel()
}

As I mentioned, the panel table has an arbitrarily nested structure:

I’m experimenting with Paper‘s Think Kit + Cosmonaut combo.

To take advantage of code completion, I assign sections to properties of nested classes. Apart from nesting, I also want to specify the order of sections and items. Unfortunately, bare properties don’t give me the ability to express this. Therefore, I create the structure separately and then assign its elements to corresponding properties. I used to do this by specifying table and section indexes but this turned out to be too difficult to maintain, so I switched to inout arguments (pragmatism beats purity):

private let ds: Section = DummySection()


class DevelopmentPanel {
    let _table: Table

    var colors = ds

    let board = Board(); class Board {
        let rules = Rules(); class Rules {
            var root   = ds
            var custom = ds
        }

        var highlights = ds
        var shake = ds
    }

    // ...

    init(dummy: Bool = false) {
        _table = dummy ? Table() : Table(items: [
            Item("Colors", &colors),

            Item("Board", board) {[
                Item("Rules", &$0.rules.root, $0.rules) {[
                    Item("Custom", &$0.custom),
                ]},
                Item("Highlights", &$0.highlights),
                Item("Shake", &$0.shake),
            ]},

            // ...
        ])
    }
}

I know, this looks strange. It’s DSL-ish in the wrong kind of way. Nevertheless, I’m willing to have some “weird” code isolated in one part of the app, if it results in the most convenient API exposed to the rest of the system:

let dev = services.developmentPanel.board.rules.custom

How tweaks work

I access tweaks through section methods. They are defined separately for each type:

extension Section {
    func tweak(name: String)(x: Bool) -> Bool {
        // ...
    }
}

I use the application operator from Rob Rix’s Prelude to have it so that the value is first and the most important and the fact that it’s tweakable is secondary:

let dev = services.developmentPanel.board.shake
println(20 |> dev.tweak("dx"))

The first time this code is executed, a tweak item called “dx” is added to the section in the table. If this tweak has been saved in the past, its value is read from disk, deserialized, and returned. If it hasn’t been saved, the supplied value (20) is returned. On any following call the “current value”, which can be changed in the UI, is returned.

The tweak itself is an object that knows both the current value and the value provided in the source code. It also has rows of buttons that have titles and that can transform values, for example increment numbers. Tweak values can be serialized and they have descriptions visible in the UI:

class Tweak {
    let sourceCodeValue: TweakValue
    var currentValue: TweakValue
    let buttonRows: [[TweakButton]]

    // init...
}

struct TweakValue {
    let value: Any
    let description: String
    let serialized: String
}

struct TweakButton {
    let title: String
    let transform: TweakValue -> TweakValue
}

Example tweak implementation

Tweaks for each type are implemented in separate files as extensions. I provide here the actual implementation of the boolean tweak.

First, the tweak value has to have a description and support serialization:

extension TweakValue {
    init(_ x: Bool) {
        value = x
        description = x.description
        serialized = JSON(["value": x]).rawString()!
    }

    init?(serializedBool: String) {
        if let x = JSON(rawString: serializedBool)["value"].bool {
            self.init(x)
        } else {
            return nil
        }
    }
}

I delegate the actual serialization and deserialization to SwiftyJSON, but it can be done in any other way.

Secondly, the tweak has to have buttons:

extension Tweak {
    convenience init(_ x: Bool) {
        func button(title: String, f: Bool -> Bool) -> TweakButton {
            return TweakButton(title: title) { TweakValue(f($0.value as! Bool)) }
        }

        let v = TweakValue(x)
        self.init(sourceCodeValue: v, currentValue: v, buttonRows: [[
            button("false") { _ in false },
            button("true")  { _ in true  },
        ]])
    }
}

Finally, the Section class has to be extended with a method providing the tweak:

extension Section {
    func tweak(name: String)(x: Bool) -> Bool {
        return isDummy ? x : getOrCreateTweak(
            name: name,
            create: { Tweak(x) },
            deserialize: { TweakValue(serializedBool: $0) }
        ) as! Bool
    }
}

There are two things that bother me here but I couldn’t come up with a better solution. First is the use of the isDummy property. DummySection is a subclass of Section that allows the whole tweaks functionality to be disabled. It’s not possible to create an extension of DummySection that overrides the tweak method to do nothing. Therefore, Section extension has to know about the “dummy feature” — in the form of the isDummy property.

The second thing that bugs me are forced casts (exclamation marks). They are there because the whole knowledge about the actual type of a tweak is contained in the tweak method — Tweak, TweakValue, and TweakButton don’t use generics. They can’t, because Tweak is owned by Item and Section can have many Items. In this relationship all generic tweaks in a section would have to have the same type and this is too strong of a restriction for my use case.

On a happier note, I want to point out that the implementation of tweaks for specific types is reusable. For example, CGFloat and HalfOpenInterval<Double> tweaks both reuse the implementation for Double, including buttons and some subtle rounding that I perform behind the scenes (try echo '0.3 - 0.1 - 0.1 - 0.1' | xcrun swift).

Performance

Relax. It’s OK.

I don’t know what to make of the continual stream of people in 2015 with fixations on low-level performance and control.

James Hague

Tweaks make the program a little bit slower, of course, because they are another thing that has to be done. I didn’t measure it but I didn’t notice any slowness.

Tweaks add an overhead of finding a few properties and calling a few simple functions. They are negligible compared to computational cost of moving things on the screen. Deserialization happens only on the first access. In release builds I use dummy tweaks that return the supplied value right away and bypass the whole serialization/remembering mechanism. I think that tweak methods can be optimized by adding an #if DEBUG clause and asking the compiler to inline the method, but I would have to find the time too look at the assembly to verify this:

extension Section {
    @inline(__always)
    func tweak(name: String)(x: Bool) -> Bool {
        #if DEBUG
            return isDummy ? x : getOrCreateTweak(
                name: name,
                create: { Tweak(x) },
                deserialize: { TweakValue(serializedBool: $0) }
            ) as! Bool
        #else
            return x
        #endif
    }
}

Another possible optimization, at the cost of the ease of use, might be switching from a property access to a function call and #ifDEBUGging (it’s a verb now) and inlining it as well:

// current version
let dev = services.developmentPanel.piece.explosion

// possible optimization
let dev = services.developmentPanel { $0.piece.explosion }

Open source

My implementation of tweaks is not open-sourced at the moment and I don’t know if it will ever be.

There are a couple of reasons for that, for example the UI is specific to my game, the buttons I use for each type are tailored to my needs, and so on. The current version is the 5th or 6th implementation — I believe it’s easier to make big changes in the private rather than in the open, without feeling the need to justify decisions and layout the pros and cons. This aspect is the biggest point against open-sourcing: making the code public creates and obligation, whether real or imaginary, to maintain it, respond to issues, write documentation, etc. It is something I don’t want to spend time on currently.

Having written this, it doesn’t mean that everything is lost. The point of this post is to, well… show off… but besides that to present an idea and start a discussion. It might materialize in something that everybody will be able to use.

One of the Checkers’ components has already completed this journey. I rely heavily on state machines (I should blog about this…) and I went through several private implementations of them. I also discussed the topic with colleagues and thought about the problem a lot. The result of this process is the SwiftyStateMachine open source µframework that I wrote at Macoscope.

The future

I feel that I’m closer to the end than to the beginning of this project. It’s a good feeling! I reviewed the list of things that I need to do before releasing the game and I split remaining tasks (GitHub Issues: 43 open, 54 closed) into small milestones, each of which should be achievable in a weekend (yeah, right…). This also made me feel good but then I counted the milestones and realised there’s about a dozen of them, which means, given the fact that I work on this game on every other weekend or so, they might take half a year to complete… Well, time will tell.

To see how this project unfolds, follow me on Twitter (I am @narfdotpl), subscribe to the Checkers newsletter or, even better, subscribe to the RSS feed of my site.

Shattering the fourth wall

And now for something completely different!

I’m a recovering perfectionist and producing these updates is hard for me. Part of me is angry that even though I cut many corners, it still takes a lot of time (I estimate that creating this post, including the video, took between 20 and 30 hours). The other part of me is even more unhappy, because these things are barely good enough to be published. But the alternative is to never ship anything, so here we are.

For the sake of documenting the process, or showing some “hacks”, I thought I will summerise how I made the video for this post.

Originally I wanted to go half-CGP and record a very fast-paced video with many short cuts in rapid succession. I was thinking about mixing camera footage with an Xcode screencast, buying or borrowing a good microphone, a tripod, a GoPro… Did I mention I’m recovering?

I ended up taking a much more lo-fi approach. I experimented with what I wanted to show and wrote down everything I wanted to say, about 1200 words. I recorded myself reading the script using Voice Memos, the stock iOS app, on an iPhone 6. The first take was Good Enough™, so I didn’t try to repeat it.

Then, I played the recording from the speakers and used Instagram’s Hyperlapse, also on an iPhone 6, to record myself tapping and swiping on an iPad, trying to do everything in sync with the prerecorded voice. I used Hyperlapse to limit the amount of perceived camera shake. It went OK, again at the first try, even though I got a bit out of sync in a few places (especially around the 5:15 mark).

I created the slides you see in the video in the Deckset app, on OS X, and presented them in the Dropbox app. Before putting the slides in Dropbox, I exported them to PDF and converted that PDF into a series of PNGs, using ImageMagick:

convert -density 108 slides.pdf slides/%02d.png

I used iMovie to put both recordings together and uploaded the final video to YouTube from the app.

That’s it. Enjoy the summer in the northern hemisphere and see you next time!


You can discuss this post on Reddit or message me on Twitter.


Checkers series

Game's website