Game Services in Kotlin

Published on 16th Jul, 2016 by matt

While working on my games there are always particular global objects that I need access to in a lot of places. The typical way to handle this would be to pass in these objects via a class constructor or simply make the objects available via some static instance somewhere. Neither solution is particularly nice. The former clutters your object construction and has to be refactored any time a new dependency is required. The latter works, but doesn't offer any kind of encapsulation and is pretty unwieldy.

Short of implementing some kind of full blown dependency injection system I've opted for a simple 'game services' strategy that lets me define all of my global 'services' in a single place. This affords me some niceties that we'll get into later. First, my Services object:


object Services : Disposable {
    private val serviceMap by lazy { mutableMapOf , Any>() }

    @Suppress("UNCHECKED_CAST")
    operator fun  get(type: KClass): T? = serviceMap[type] as? T

    fun put(service: Any) {
        serviceMap[service.javaClass.kotlin] = service
    }

    override fun dispose() {
        serviceMap.mapNotNull { it.value as? Disposable }.forEach { it.dispose() }
        serviceMap.clear()
    }
}

In Kotlin the object keyword is an easy way to setup the singleton pattern. I can then access my Services object anywhere. This gives us the ease-of-access that our static instance option above offered us, but in a nicer way. Internally I keep a service map which just maps service class types to the service itself. I also implement libGDX's Disposable interface to make cleanup easy. It simply looks through the service map for anything that also implements Disposable and disposes it as well.

Usage is easy:


// During initialization
Services.put(SpriteBatch())
// Somewhere else...
val spriteBatch = Services[SpriteBatch::class]

That by itself is fine and completely usable. However by using Kotlin's delegated properties we can take it one step further and in the process decouple ourselves a little bit from the Services object itself.


// Delegate
class ServiceDelegate(private val type: KClass) : ReadOnlyProperty {
    private var service: T? = null

    override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
        if (service == null) {
            service = Services[type]
        }

        return service as? T
    }
}

// Caller
fun  service(type: KClass) = ServiceDelegate(type)

The ServiceDelegate class sets up a simple read-only, lazy property that takes in a service class type. When accessed it will check if we have a value already, and if not pull it from our Services singleton. This implementation will return a null if the requested service is not available, but I could just as well throw an exception instead (and probably should).

Usage:


val spriteBatch by service(SpriteBatch::class)

What benefit does this offer us over simply accessing Services directly? Well, one we get lazy initialization for member properties which can be nice depending on the situation. Second it decouples us from the Services implementation meaning if we change that in the future then we only need to change our ServiceDelegate class and not every place we accessed Services directly in the past. Another possible addition I could see would be allowing the Services object to hold factories that create unique instances of services rather than single global instances. In this case the lazy loading of our delegate is a big win.

Comments