Skip to content

Instantly share code, notes, and snippets.

@hakanai
Last active March 31, 2024 08:40
Show Gist options
  • Save hakanai/df8af66e46744bac5bb8bf9a93d1ba30 to your computer and use it in GitHub Desktop.
Save hakanai/df8af66e46744bac5bb8bf9a93d1ba30 to your computer and use it in GitHub Desktop.
When you have to initialise multiple resources

Sometimes you have to initialise more than one resource and properly clean everything up.

This is the cleanest option I was able to figure out for Kotlin.

Example of usage:

        private val buffers: Array<AudioBuffer>
        private val device: AudioDevice
        private val context: AudioContext
        private val source: AudioSource
        private val resourcesToCloseLater: AutoCloseable

        init {
            resourcesToCloseLater = initializingResources { ->
                buffers = Array(BUFFER_COUNT) { AudioBuffer.generate().also(::closeLater) }
                device = AudioDevice.openDefaultDevice().also(::closeLater)
                context = AudioContext.create(device, intArrayOf(0)).also(::closeLater)

                context.makeCurrent()
                device.createCapabilities()
                source = AudioSource.generate().also(::closeLater)
                for (i in 0 until BUFFER_COUNT) {
                    bufferSamples(ShortArray(0))
                }
                source.play()
                device.detectInternalExceptions()
            }
        }
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
private fun closeChain(items: List<AutoCloseable>) {
if (resources.isEmpty()) return
try {
resources.last().close()
} finally {
resources.dropLast(1).also(::closeChain)
}
}
class ResourceInitializationScope {
private val itemsToCloseLater = mutableListOf<AutoCloseable>()
/**
* Adds a resource to be closed later.
*
* @param resource the resource.
*/
fun closeLater(item: AutoCloseable) {
itemsToCloseLater.add(item)
}
fun closeNow() = closeChain(itemsToCloseLater)
}
/**
* Begins a block to initialize multiple resources.
*
* This is done safely. If any failure occurs, all previously-initialized resources are closed.
*
* @param block the block of code to run.
* @return an object which should be closed later to close all resources opened during the block.
*/
@OptIn(ExperimentalContracts::class)
inline fun initializingResources(block: ResourceInitializationScope.() -> Unit): AutoCloseable {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val scope = ResourceInitializationScope()
var success = false
try {
scope.apply(block)
success = true
return AutoCloseable { scope.closeNow() }
} finally {
if (!success) scope.closeNow()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment