Skip to content

Mesh_context.kt

src/jvmMain/kotlin/afield/document/context/Mesh_context.kt · 111 lines

package afield.document.context

import afield.document.graph.Box
import afield.document.context.ContextResolution
import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.buffers.MissingRefException
import afield.graphics.vulkan.buffers.Structure
import afield.graphics.vulkan.buffers.VertexBuffer
import afield.graphics.vulkan.buffers.bindPushConstant
import afield.graphics.vulkan.buffers.getBoundPushConstant
import afield.graphics.vulkan.buffers.getPushConstant
import afield.graphics.vulkan.buffers.setPushConstant
import afield.graphics.vulkan.device.CommandBuffer
import afield.graphics.vulkan.util.MapOfSequences
import afield.app.diagnostics.logging.lprintln
import org.lwjgl.system.MemoryStack
import org.lwjgl.vulkan.VK10.*
import java.nio.ByteBuffer
import java.nio.FloatBuffer
import kotlin.reflect.KProperty
import kotlin.reflect.full.isSubclassOf

The two calls that find a value somewhere up a box's parent chain: fromContext by type, fromContextWithNameOrNull by name.

Both are inline wrappers over ContextResolution's walk, which does the searching. What they add is the reified type argument and the lookup of the box that is asking, so outside a running box there is nothing to search: fromContext throws unless it was given a default, and fromContextWithNameOrNull returns the default or null.

Neither searches the asking box's own top-level variables unless includeLocalVariables is set. That is what keeps val x = fromContext<X>() resolving from a parent on every later run instead of finding the answer it left behind last time.

inline fun <reified A> fromContext(defaultValue: A? = null, includeLocalVariables: Boolean = false): A

Resolve a value of type A from the box context: this box first, then its ancestors, breadth-first.

Search order is 1. the current box itself (if it is an A), 2. the current box's properties, 3. each ancestor generation in turn - properties, then that box's top-level (ki) variables.

Note what is deliberately absent: the current box's own top-level variables. Including them makes the canonical idiom self-pinning -

var foo = fromContext<Foo>()

resolves from a parent the first time, but that leaves a Foo-typed variable in this box's variable table, so every later execution finds its own previous answer. foo can then never track the parent again, and re-pointing it by hand doesn't survive either.

Pass includeLocalVariables = true to opt back in when a box genuinely wants to find a value it declared itself - e.g. code running after an async boundary in the same box.

inline fun <reified A> fromContext(defaultValue: A? = null, includeLocalVariables: Boolean = false): A {
    val f = Box.context.get()
    if (f == null) {
        if (defaultValue != null) return defaultValue
        // A `main`, a test or a background thread: there is no box to search, so say that, rather
        // than letting the walk fail on a null box.
        throw IllegalStateException(
            "fromContext<${A::class.java.simpleName}>() needs a running box, and this code is not running " +
                "inside one (a main, a test, or another thread); pass the ${A::class.java.simpleName} explicitly"
        )
    }
    val found = ContextResolution.walk(
        f,
        ContextResolution.Query.byType(A::class.java) { it is A },
        ContextResolution.Policy.fromContext(includeLocalVariables),
        ContextResolution.Mechanism.FROM_CONTEXT,
    )
    if (found != null) return found.value as A
    if (defaultValue != null) return defaultValue as A
    throw IllegalArgumentException("must pass in a value for context parameter ${A::class.java.name}")
}

inline fun <reified A> fromContextWithNameOrNull

Named sibling of fromContext: resolve the value called name (and assignable to A) from the box context.

Same search order, and the same deliberate omission - this box's own top-level variables are not searched unless includeLocalVariables is true. Here the trap is even easier to hit, since the natural idiom gives the local the same name as the thing it is looking up:

val agent_context_value = fromContextWithNameOrNull<String>("agent_context_value")
inline fun <reified A> fromContextWithNameOrNull(
    name: String,
    defaultValue: A? = null,
    includeLocalVariables: Boolean = false,
): A? {
    val f = Box.context.get()
    if (f == null) return defaultValue

    val accept: (Any?) -> Boolean = { it != null && it::class.isSubclassOf(A::class) }
    val found = ContextResolution.walk(
        f,
        ContextResolution.Query(name, listOf(name), accept, { accept(it.value) }),
        ContextResolution.Policy.fromContext(includeLocalVariables, selfCandidate = false),
        ContextResolution.Mechanism.FROM_CONTEXT_NAMED,
    )
    if (found != null) return found.value as A
    if (defaultValue != null) return defaultValue as A
    return null
}