Skip to content

ReloadResolvers.kt

src/jvmMain/kotlin/afield/graphics/render/ReloadResolvers.kt · 57 lines

package afield.graphics.render

Keeps a module's resource resolvers one layer deep across repeated shader reloads.

Each module pointer installs its carry-over behaviour by replacing the resolvers on its Parameters. Module, ComputeModule and MeshModule replace all three of uboResolver, ssboResolver and textureResolver; RayTracingModule replaces the latter two. A reload is handed a copy of the parameters, and a copy brings the resolvers across as they are, so each wrap would otherwise land on the one the previous reload installed and every uniform, buffer and texture lookup would walk one closure more than it did on the run before.

reloadWrap unwraps before it wraps, so what sits underneath stays the resolver the box or moduleHere supplied and the depth stays at one however often a shader is reloaded. reloadWrapDepth reports that depth; ReloadResolversTest holds it at one across a hundred reloads, and checks that replacing the resolver yourself makes the replacement what the next reload wraps.

internal class ReloadWrappedResolver<A, R>

A resolver a module pointer's reload installed on a Parameters to carry resources over from the module being replaced, remembering the resolver it wrapped.

A reload is handed current.parameters.copy(), and a data-class copy carries the resolvers across as they are — so the previous reload's carry-over wrapper came along, and wrapping it grew the chain by one level per reload: after a hundred alt-rs, each UBO lookup went through a hundred closures, every one of them consulting a current module. reloadWrap always wraps the original — the resolver the user or moduleHere supplied — so the depth stays at one.

internal class ReloadWrappedResolver<A, R>(
    val original: (A) -> R,
    private val body: (original: (A) -> R, A) -> R
) : (A) -> R {

override fun invoke(p1: A): R = body(original, p1)

internal fun <A, R> reloadWrap(current: (A) -> R, body: (original: (A) -> R, A) -> R): (A) -> R {

Wrap current for a reload, unwrapping any wrapper an earlier reload installed first, so repeated reloads replace the carry-over layer rather than stacking another one on it.

@Suppress("UNCHECKED_CAST")
internal fun <A, R> reloadWrap(current: (A) -> R, body: (original: (A) -> R, A) -> R): (A) -> R {
    val original = (current as? ReloadWrappedResolver<A, R>)?.original ?: current
    return ReloadWrappedResolver(original, body)
}

internal fun reloadWrapDepth(resolver: Function1<*, *>): Int

How many reload wrappers sit in front of the user's resolver; 0 or 1 once reloadWrap is used.

internal fun reloadWrapDepth(resolver: Function1<*, *>): Int {
    var depth = 0
    var r: Function1<*, *> = resolver
    while (r is ReloadWrappedResolver<*, *>) {
        depth++
        r = r.original
    }
    return depth
}