Fixes and Text
This commit is contained in:
parent
e6ca5fce3b
commit
abf67dd663
10 changed files with 171 additions and 37 deletions
18
build.gradle
18
build.gradle
|
|
@ -24,9 +24,13 @@ dependencies {
|
|||
implementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}"
|
||||
implementation "com.mojang:brigadier:1.2.9"
|
||||
|
||||
// NanoVG is not part of Minecraft's LWJGL modules, so bundle it (and its
|
||||
// natives) into the mod jar via include so it is present at runtime.
|
||||
implementation "org.lwjgl:lwjgl-nanovg:${project.nanovg_version}"
|
||||
include "org.lwjgl:lwjgl-nanovg:${project.nanovg_version}"
|
||||
['linux', 'linux-arm64', 'windows', 'macos', 'macos-arm64'].each { platform ->
|
||||
runtimeOnly "org.lwjgl:lwjgl-nanovg:${project.nanovg_version}:natives-${platform}"
|
||||
include "org.lwjgl:lwjgl-nanovg:${project.nanovg_version}:natives-${platform}"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,3 +67,17 @@ publishing {
|
|||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
// Copies the built lattice and example mod jars into a Minecraft mods folder.
|
||||
// Override the target with -PmodsDir=/path/to/mods (defaults to the Prism instance).
|
||||
def defaultModsDir = "${System.getProperty('user.home')}/.local/share/PrismLauncher/instances/Lattice testing/minecraft/mods"
|
||||
|
||||
tasks.register('deployMods', Copy) {
|
||||
group = 'lattice'
|
||||
description = 'Copies the lattice and example mod jars into a Minecraft mods folder.'
|
||||
dependsOn tasks.named('jar'), project(':example').tasks.named('jar')
|
||||
|
||||
from tasks.named('jar').flatMap { it.archiveFile }
|
||||
from project(':example').tasks.named('jar').flatMap { it.archiveFile }
|
||||
into providers.gradleProperty('modsDir').orElse(defaultModsDir)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
org.gradle.jvmargs=-Xmx4G
|
||||
org.gradle.jvmargs=-Xmx10G
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configuration-cache=false
|
||||
loom.ignoreDependencyLoomVersionValidation=true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package xyz.meowing.lattice
|
||||
|
||||
import net.minecraft.resources.Identifier
|
||||
import xyz.meowing.lattice.client.Client.minecraft
|
||||
import xyz.meowing.lattice.event.EventBus
|
||||
import xyz.meowing.lattice.render.Font
|
||||
import xyz.meowing.lattice.render.NVGRenderer
|
||||
|
|
@ -14,10 +12,7 @@ object Lattice {
|
|||
val eventBus = EventBus()
|
||||
|
||||
@JvmStatic
|
||||
val defaultFont: Font by lazy {
|
||||
val id = Identifier.fromNamespaceAndPath(MOD_ID, "font.ttf")
|
||||
Font("Default", minecraft.resourceManager.getResourceStack(id).first().open())
|
||||
}
|
||||
val defaultFont: Font by lazy { Font("Default", "/assets/$MOD_ID/font.ttf") }
|
||||
|
||||
private var _renderer: Renderer? = null
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder.literal
|
|||
* The tree must always end in an [Executable][xyz.meowing.lattice.command.nodes.Executable],
|
||||
* which serves as an exit point for a command.
|
||||
*/
|
||||
open class Commodore(private val root: LiteralNode) {
|
||||
open class Commodore(@PublishedApi internal val root: LiteralNode) {
|
||||
|
||||
constructor(
|
||||
vararg name: String,
|
||||
|
|
@ -35,10 +35,18 @@ open class Commodore(private val root: LiteralNode) {
|
|||
/**
|
||||
* DSL access to the root node for object-style definitions.
|
||||
*/
|
||||
fun runs(function: Function<Unit>) = root.runs(function)
|
||||
|
||||
fun runs(block: () -> Unit) = root.runs(block)
|
||||
|
||||
inline fun <reified P1> runs(noinline block: (P1) -> Unit) = root.runs(block)
|
||||
|
||||
inline fun <reified P1, reified P2> runs(noinline block: (P1, P2) -> Unit) = root.runs(block)
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3> runs(noinline block: (P1, P2, P3) -> Unit) = root.runs(block)
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3, reified P4> runs(noinline block: (P1, P2, P3, P4) -> Unit) = root.runs(block)
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3, reified P4, reified P5> runs(noinline block: (P1, P2, P3, P4, P5) -> Unit) = root.runs(block)
|
||||
|
||||
fun literal(string: String, block: LiteralNode.() -> Unit = {}): LiteralNode {
|
||||
return root.literal(string, block)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package xyz.meowing.lattice.command.functions
|
|||
|
||||
import java.lang.invoke.MethodHandle
|
||||
import java.lang.invoke.MethodHandles
|
||||
import java.lang.reflect.ParameterizedType
|
||||
|
||||
/**
|
||||
* # FunctionInvoker
|
||||
|
|
@ -59,7 +60,21 @@ sealed interface FunctionInvoker<T> {
|
|||
mHandle = MethodHandles.lookup().unreflect(invokeMethod).bindTo(lambda)
|
||||
|
||||
if (parameterTypes.isEmpty()) {
|
||||
parameters = invokeMethod.parameterTypes.mapIndexed { index, type ->
|
||||
val rawTypes = invokeMethod.parameterTypes
|
||||
// Kotlin K2/IR may compile lambdas so the only non-bridge invoke method
|
||||
// has erased Object types. Recover concrete types from the FunctionN
|
||||
// generic interface that the lambda class still declares.
|
||||
val actualTypes = if (rawTypes.isNotEmpty() && rawTypes.all { it == Any::class.java }) {
|
||||
recoverFromFunctionInterface(lambdaClass, rawTypes.size)
|
||||
?: throw IllegalStateException(
|
||||
"Cannot recover parameter types for lambda ${lambdaClass.name}. " +
|
||||
"Interfaces: ${lambdaClass.genericInterfaces.toList()}. " +
|
||||
"Use a typed runs { p: Type -> } overload instead of Function<Unit>."
|
||||
)
|
||||
} else {
|
||||
rawTypes
|
||||
}
|
||||
parameters = actualTypes.mapIndexed { index, type ->
|
||||
Parameter("param$index", type, false)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -76,6 +91,27 @@ sealed interface FunctionInvoker<T> {
|
|||
override fun invoke(arguments: List<Any?>): T {
|
||||
return mHandle.invokeWithArguments(arguments) as T
|
||||
}
|
||||
|
||||
private companion object {
|
||||
fun recoverFromFunctionInterface(lambdaClass: Class<*>, paramCount: Int): Array<Class<*>>? {
|
||||
for (iface in lambdaClass.genericInterfaces) {
|
||||
val pt = iface as? ParameterizedType ?: continue
|
||||
val raw = pt.rawType as? Class<*> ?: continue
|
||||
if (!raw.name.startsWith("kotlin.jvm.functions.Function")) continue
|
||||
val args = pt.actualTypeArguments
|
||||
// FunctionN<P1,...,Pn,R> — last arg is return type
|
||||
if (args.size != paramCount + 1) continue
|
||||
return Array(paramCount) { i ->
|
||||
when (val arg = args[i]) {
|
||||
is Class<*> -> arg
|
||||
is ParameterizedType -> arg.rawType as? Class<*> ?: Any::class.java
|
||||
else -> Any::class.java
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -72,8 +72,31 @@ class Executable : Node() {
|
|||
* - Use a [ParameterModifier] to apply a custom parser to a certain parameter.
|
||||
* You need to ensure you're parsing for the correct class.
|
||||
*/
|
||||
fun runs(function: Function<Unit>) {
|
||||
funInvoker = FunctionInvoker.from(function)
|
||||
fun runs(block: () -> Unit) = define(block)
|
||||
|
||||
inline fun <reified P1> runs(noinline block: (P1) -> Unit) =
|
||||
define(block, P1::class.java)
|
||||
|
||||
inline fun <reified P1, reified P2> runs(noinline block: (P1, P2) -> Unit) =
|
||||
define(block, P1::class.java, P2::class.java)
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3> runs(noinline block: (P1, P2, P3) -> Unit) =
|
||||
define(block, P1::class.java, P2::class.java, P3::class.java)
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3, reified P4> runs(noinline block: (P1, P2, P3, P4) -> Unit) =
|
||||
define(block, P1::class.java, P2::class.java, P3::class.java, P4::class.java)
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3, reified P4, reified P5> runs(noinline block: (P1, P2, P3, P4, P5) -> Unit) =
|
||||
define(block, P1::class.java, P2::class.java, P3::class.java, P4::class.java, P5::class.java)
|
||||
|
||||
/**
|
||||
* Low-level entry point that builds the invoker from a function and its
|
||||
* explicit parameter types. Prefer the typed [runs] overloads, which capture
|
||||
* the types via reified generics instead of relying on runtime reflection.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal fun define(function: Function<Unit>, vararg explicitTypes: Class<*>) {
|
||||
funInvoker = FunctionInvoker.from(function, *explicitTypes)
|
||||
|
||||
for (parameter in funInvoker.parameters) {
|
||||
val modifier = parameterModifiers[parameter.name]
|
||||
|
|
|
|||
|
|
@ -94,15 +94,29 @@ open class LiteralNode(val name: String) : Node() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates an [Executable] and apply function directly.
|
||||
* Creates an [Executable] that runs [block] with typed command arguments.
|
||||
*
|
||||
* This won't allow you to apply [modifiers][Executable.ParameterModifier]
|
||||
* to the function's parameters.
|
||||
*
|
||||
* @see runs
|
||||
* Each parameter type is captured via reified generics, so it works with
|
||||
* Kotlin's invokedynamic lambdas where the types are erased at runtime.
|
||||
*/
|
||||
fun runs(function: Function<Unit>) = executable {
|
||||
runs(function)
|
||||
inline fun <reified P1> runs(noinline block: (P1) -> Unit) = executable {
|
||||
define(block, P1::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified P1, reified P2> runs(noinline block: (P1, P2) -> Unit) = executable {
|
||||
define(block, P1::class.java, P2::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3> runs(noinline block: (P1, P2, P3) -> Unit) = executable {
|
||||
define(block, P1::class.java, P2::class.java, P3::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3, reified P4> runs(noinline block: (P1, P2, P3, P4) -> Unit) = executable {
|
||||
define(block, P1::class.java, P2::class.java, P3::class.java, P4::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified P1, reified P2, reified P3, reified P4, reified P5> runs(noinline block: (P1, P2, P3, P4, P5) -> Unit) = executable {
|
||||
define(block, P1::class.java, P2::class.java, P3::class.java, P4::class.java, P5::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ object GLState {
|
|||
@JvmStatic
|
||||
var previousProgram = -1
|
||||
|
||||
@JvmStatic
|
||||
var previousSampler = -1
|
||||
|
||||
@JvmStatic
|
||||
var previousVao = -1
|
||||
|
||||
@JvmStatic
|
||||
var drawing = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package xyz.meowing.lattice.render
|
|||
|
||||
import com.mojang.blaze3d.opengl.GlStateManager
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.lwjgl.nanovg.NVGColor
|
||||
import org.lwjgl.nanovg.NVGPaint
|
||||
import org.lwjgl.nanovg.NanoSVG
|
||||
|
|
@ -11,6 +12,7 @@ import org.lwjgl.opengl.GL11
|
|||
import org.lwjgl.opengl.GL13
|
||||
import org.lwjgl.opengl.GL20
|
||||
import org.lwjgl.opengl.GL30
|
||||
import org.lwjgl.opengl.GL33C
|
||||
import org.lwjgl.stb.STBImage
|
||||
import org.lwjgl.system.MemoryUtil
|
||||
import xyz.meowing.lattice.client.Client.minecraft
|
||||
|
|
@ -18,7 +20,6 @@ import xyz.meowing.lattice.render.Color.Companion.alpha
|
|||
import xyz.meowing.lattice.render.Color.Companion.blue
|
||||
import xyz.meowing.lattice.render.Color.Companion.green
|
||||
import xyz.meowing.lattice.render.Color.Companion.red
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.max
|
||||
|
|
@ -48,23 +49,30 @@ object NVGRenderer : Renderer {
|
|||
// Resolved reflectively once: GlTexture.getFbo(GlStateManager$DirectStateAccess, depth) is not public API.
|
||||
private var fboLookup: Pair<Any, Method>? = null
|
||||
private var fboLookupFailed = false
|
||||
private var loggedFbo = false
|
||||
|
||||
private fun mainFramebuffer(colorTex: Any): Int {
|
||||
private fun fieldValue(obj: Any, name: String): Any? {
|
||||
var clazz: Class<*>? = obj.javaClass
|
||||
while (clazz != null) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name).apply { isAccessible = true }.get(obj)
|
||||
} catch (_: NoSuchFieldException) {
|
||||
clazz = clazz.superclass
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun mainFramebuffer(colorTex: Any, depthTex: Any?): Int {
|
||||
if (fboLookupFailed) return 0
|
||||
|
||||
try {
|
||||
val lookup = fboLookup ?: run {
|
||||
val device = RenderSystem.getDevice() ?: return 0
|
||||
var dsaField: Field? = null
|
||||
var clazz: Class<*>? = device.javaClass
|
||||
while (clazz != null && dsaField == null) {
|
||||
try {
|
||||
dsaField = clazz.getDeclaredField("directStateAccess")
|
||||
} catch (_: NoSuchFieldException) {}
|
||||
clazz = clazz.superclass
|
||||
}
|
||||
dsaField?.isAccessible = true
|
||||
val dsa = dsaField?.get(device) ?: throw IllegalStateException("no directStateAccess")
|
||||
// GpuDevice wraps the GL backend (GlDevice), which owns directStateAccess.
|
||||
val backend = fieldValue(device, "backend") ?: device
|
||||
val dsa = fieldValue(backend, "directStateAccess")
|
||||
?: throw IllegalStateException("no directStateAccess")
|
||||
|
||||
var getFbo: Method? = null
|
||||
var texClazz: Class<*>? = colorTex.javaClass
|
||||
|
|
@ -78,9 +86,12 @@ object NVGRenderer : Renderer {
|
|||
Pair(dsa, getFbo).also { fboLookup = it }
|
||||
}
|
||||
|
||||
return (lookup.second.invoke(colorTex, lookup.first, null) as? Int) ?: 0
|
||||
} catch (_: Exception) {
|
||||
// Pass the depth texture so getFbo returns the FBO with the depth+stencil
|
||||
// attachment; NanoVG's stencil fills render nothing on a color-only FBO.
|
||||
return (lookup.second.invoke(colorTex, lookup.first, depthTex) as? Int) ?: 0
|
||||
} catch (e: Throwable) {
|
||||
fboLookupFailed = true
|
||||
LogManager.getLogger("Lattice").warn("FBO lookup failed: device=${RenderSystem.getDevice()?.javaClass?.name} color=${colorTex.javaClass.name} depth=${depthTex?.javaClass?.name}", e)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
|
@ -95,10 +106,23 @@ object NVGRenderer : Renderer {
|
|||
val colorTex = renderTarget.getColorTexture() ?: return
|
||||
if (vg == -1L) return
|
||||
|
||||
GlStateManager._glBindFramebuffer(GL30.GL_FRAMEBUFFER, mainFramebuffer(colorTex))
|
||||
val fbo = mainFramebuffer(colorTex, renderTarget.getDepthTexture())
|
||||
if (!loggedFbo) {
|
||||
loggedFbo = true
|
||||
LogManager.getLogger("Lattice").info("NVG beginFrame: fbo=$fbo failed=$fboLookupFailed size=${width}x$height target=${renderTarget.width}x${renderTarget.height}")
|
||||
}
|
||||
GlStateManager._glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo)
|
||||
GlStateManager._viewport(0, 0, renderTarget.width, renderTarget.height)
|
||||
GlStateManager._activeTexture(GL30.GL_TEXTURE0)
|
||||
|
||||
// NanoVG binds its own VAO without restoring the previous one.
|
||||
GLState.previousVao = GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING)
|
||||
|
||||
// Unbind MC's sampler from unit 0 so NanoVG's font atlas texture samples
|
||||
// with its own parameters; otherwise glyphs render blank while shapes are fine.
|
||||
GLState.previousSampler = GL11.glGetInteger(GL33C.GL_SAMPLER_BINDING)
|
||||
GL33C.glBindSampler(0, 0)
|
||||
|
||||
NanoVG.nvgBeginFrame(vg, width, height, 1f)
|
||||
NanoVG.nvgTextAlign(vg, NanoVG.NVG_ALIGN_LEFT or NanoVG.NVG_ALIGN_TOP)
|
||||
GLState.drawing = true
|
||||
|
|
@ -114,13 +138,23 @@ object NVGRenderer : Renderer {
|
|||
GlStateManager._enableBlend()
|
||||
GlStateManager._blendFuncSeparate(770, 771, 1, 0)
|
||||
|
||||
// NanoVG's stencil strokes can leave the stencil test on and the color mask off.
|
||||
GL11.glDisable(GL11.GL_STENCIL_TEST)
|
||||
GL11.glColorMask(true, true, true, true)
|
||||
|
||||
if (GLState.previousProgram != -1) GlStateManager._glUseProgram(GLState.previousProgram)
|
||||
|
||||
// Restore the sampler on unit 0 that we unbound for the font atlas.
|
||||
GlStateManager._activeTexture(GL30.GL_TEXTURE0)
|
||||
if (GLState.previousSampler != -1) GL33C.glBindSampler(0, GLState.previousSampler)
|
||||
|
||||
if (GLState.previousActiveTexture != -1) {
|
||||
GlStateManager._activeTexture(GLState.previousActiveTexture)
|
||||
if (GLState.previousBoundTexture != -1) GlStateManager._bindTexture(GLState.previousBoundTexture)
|
||||
}
|
||||
|
||||
if (GLState.previousVao != -1) GL30.glBindVertexArray(GLState.previousVao)
|
||||
|
||||
GlStateManager._glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)
|
||||
GLState.drawing = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,10 +95,10 @@ abstract class UIScreen(screenName: String = "Lattice-Screen") : Screen(Componen
|
|||
super.onClose()
|
||||
}
|
||||
|
||||
/** Opens this screen on the next client tick, safe to call from any thread. */
|
||||
/** Opens this screen on the render thread, safe to call from any thread. */
|
||||
fun display() {
|
||||
TimeScheduler.schedule(50) {
|
||||
minecraft.setScreen(this@UIScreen)
|
||||
minecraft.execute { minecraft.setScreen(this@UIScreen) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue