Skip to content

Commit

Permalink
Engine: Track the currently executing module
Browse files Browse the repository at this point in the history
  • Loading branch information
mattco98 committed Jul 1, 2024
1 parent 73a4095 commit 10c8e32
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 32 deletions.
27 changes: 27 additions & 0 deletions src/main/kotlin/com/chattriggers/ctjs/CTJS.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import com.chattriggers.ctjs.api.world.World
import com.chattriggers.ctjs.engine.Console
import com.chattriggers.ctjs.engine.Register
import com.chattriggers.ctjs.internal.commands.StaticCommand
import com.chattriggers.ctjs.internal.engine.JSLoader
import com.chattriggers.ctjs.internal.engine.module.Module
import com.chattriggers.ctjs.internal.engine.module.ModuleManager
import com.chattriggers.ctjs.internal.utils.Initializer
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -82,6 +84,31 @@ class CTJS : ClientModInitializer {
readTimeout = 3000
}

/**
* Gets the active (currently running) Module object.
*
* There are different ways the active module is determined:
* - At engine startup, the active module is the module that is currently having its "entry" script ran. The
* "entry" scripts are ran in dependency order such that modules that don't have any dependencies are ran
* first.
* - After engine startup, the active module is the module that belongs to the trigger that is currently
* running, as triggers are the only way user code is ran after engine startup.
* - On background threads started from the Thread global, the active module is the module which spawned the
* background thread.
* - On background threads not started from the Thread global, such as when invoking java.lang.Thread manually,
* the active module is undefined, and this method will return null.
* - During mixin application, the active module is undefined, and this method will return null.
*
* Due to the way user code is exclusively invoked via triggers, this method allows library authors to determine
* where their library code is being invoked from, assuming this method is called outside a trigger created by
* the library.
*/
@JvmStatic
fun activeModule(): Module? {
// Copy the object so the users can't change the mutable internal state
return JSLoader.activeModule?.copy()
}

@JvmStatic
fun unload(asCommand: Boolean = true) {
TriggerType.WORLD_UNLOAD.triggerAll()
Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/com/chattriggers/ctjs/api/client/KeyBind.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.chattriggers.ctjs.api.triggers.RegularTrigger
import com.chattriggers.ctjs.api.triggers.TriggerType
import com.chattriggers.ctjs.api.world.World
import com.chattriggers.ctjs.internal.BoundKeyUpdater
import com.chattriggers.ctjs.internal.engine.JSLoader
import com.chattriggers.ctjs.internal.mixins.GameOptionsAccessor
import com.chattriggers.ctjs.internal.mixins.KeyBindingAccessor
import com.chattriggers.ctjs.internal.utils.Initializer
Expand Down Expand Up @@ -103,12 +104,12 @@ class KeyBind {
}
}

onKeyPress?.trigger(arrayOf())
onKeyPress?.let(JSLoader::trigger)
down = true
}

if (isKeyDown()) {
onKeyDown?.trigger(arrayOf())
onKeyDown?.let(JSLoader::trigger)
down = true
}

Expand All @@ -117,7 +118,7 @@ class KeyBind {
// consume the rest of the key presses
}

onKeyRelease?.trigger(arrayOf())
onKeyRelease?.let(JSLoader::trigger)
down = false
}
}
Expand Down
19 changes: 10 additions & 9 deletions src/main/kotlin/com/chattriggers/ctjs/api/render/Gui.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.chattriggers.ctjs.api.client.Client
import com.chattriggers.ctjs.api.message.TextComponent
import com.chattriggers.ctjs.api.triggers.RegularTrigger
import com.chattriggers.ctjs.api.triggers.TriggerType
import com.chattriggers.ctjs.internal.engine.JSLoader
import com.chattriggers.ctjs.internal.mixins.ClickableWidgetAccessor
import com.chattriggers.ctjs.internal.utils.asMixin
import gg.essential.universal.UKeyboard
Expand Down Expand Up @@ -218,27 +219,27 @@ class Gui @JvmOverloads constructor(
super.initScreen(width, height)

ScreenMouseEvents.afterMouseScroll(this).register { _, x, y, _, dy ->
onScroll?.trigger(arrayOf(x, y, dy))
onScroll?.let { JSLoader.trigger(it, arrayOf(x, y, dy)) }
}

buttons.values.forEach(::addDrawableChild)
onOpened?.trigger(arrayOf(this))
onOpened?.let { JSLoader.trigger(it, arrayOf(this)) }
}

/**
* Internal method to run trigger. Not meant for public use
*/
override fun onScreenClose() {
super.onScreenClose()
onClosed?.trigger(arrayOf(this))
onClosed?.let { JSLoader.trigger(it, arrayOf(this)) }
}

/**
* Internal method to run trigger. Not meant for public use
*/
override fun onMouseClicked(mouseX: Double, mouseY: Double, mouseButton: Int) {
super.onMouseClicked(mouseX, mouseY, mouseButton)
onClick?.trigger(arrayOf(mouseX, mouseY, mouseButton))
onClick?.let { JSLoader.trigger(it, arrayOf(mouseX, mouseY, mouseButton)) }
}

/**
Expand All @@ -248,7 +249,7 @@ class Gui @JvmOverloads constructor(
*/
override fun onMouseReleased(mouseX: Double, mouseY: Double, state: Int) {
super.onMouseReleased(mouseX, mouseY, state)
onMouseReleased?.trigger(arrayOf(mouseX, mouseY, state))
onMouseReleased?.let { JSLoader.trigger(it, arrayOf(mouseX, mouseY, state)) }
}

/**
Expand All @@ -261,7 +262,7 @@ class Gui @JvmOverloads constructor(
timeSinceLastClick: Long,
) {
super.onMouseDragged(x, y, clickedButton, timeSinceLastClick)
onMouseDragged?.trigger(arrayOf(mouseX, mouseY, clickedButton))
onMouseDragged?.let { JSLoader.trigger(it, arrayOf(mouseX, mouseY, clickedButton)) }
}

/**
Expand All @@ -283,7 +284,7 @@ class Gui @JvmOverloads constructor(

this.mouseX = mouseX
this.mouseY = mouseY
onDraw?.trigger(arrayOf(mouseX, mouseY, partialTicks))
onDraw?.let { JSLoader.trigger(it, arrayOf(mouseX, mouseY, partialTicks)) }

Renderer.popMatrix()
}
Expand All @@ -298,7 +299,7 @@ class Gui @JvmOverloads constructor(
var char = keyCode.toChar()
if (modifiers?.isShift != true)
char = char.lowercaseChar()
onKeyTyped?.trigger(arrayOf(char, keyCode))
onKeyTyped?.let { JSLoader.trigger(it, arrayOf(char, keyCode)) }
}
}

Expand Down Expand Up @@ -343,7 +344,7 @@ class Gui @JvmOverloads constructor(
): Int {
val id = nextButtonId++
val button = ButtonWidget.builder(buttonText) {
onActionPerformed?.trigger(arrayOf(id))
onActionPerformed?.let { JSLoader.trigger(it, arrayOf(id)) }
}.dimensions(x, y, width, height).build()
buttons[id] = button
addDrawableChild(button)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class ChatTrigger(method: Any, type: ITriggerType) : Trigger(method, type) {
* Argument 2 (ClientChatReceivedEvent) the chat event fired
* @param args list of arguments as described
*/
override fun trigger(args: Array<out Any?>) {
override fun triggerImpl(args: Array<out Any?>) {
require(args[0] is Event) {
"Argument 1 must be a ChatTrigger.Event"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ sealed class ClassFilterTrigger<Wrapped, Unwrapped>(
*/
fun setFilteredClasses(classes: List<Class<Unwrapped>>) = apply { triggerClasses = classes }

override fun trigger(args: Array<out Any?>) {
override fun triggerImpl(args: Array<out Any?>) {
val placeholder = evalTriggerType(args)
if (triggerClasses.isEmpty() || triggerClasses.any { it.isInstance(placeholder) })
callMethod(args)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CommandTrigger(method: Any) : Trigger(method, TriggerType.COMMAND) {
private var command: Command? = null
private var dynamicCompletions: ((List<String>) -> List<String>)? = null

override fun trigger(args: Array<out Any?>) {
override fun triggerImpl(args: Array<out Any?>) {
callMethod(args)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class EventTrigger(method: Any, triggerType: ITriggerType) : Trigger(method, tri
*/
fun triggerIfCanceled(bool: Boolean) = apply { triggerIfCanceled = bool }

override fun trigger(args: Array<out Any?>) {
override fun triggerImpl(args: Array<out Any?>) {
val isCanceled = when (val event = args.lastOrNull()) {
is CancellableEvent -> event.isCanceled()
is CallbackInfo -> event.isCancelled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.chattriggers.ctjs.api.triggers


class RegularTrigger(method: Any, triggerType: ITriggerType) : Trigger(method, triggerType) {
override fun trigger(args: Array<out Any?>) {
override fun triggerImpl(args: Array<out Any?>) {
callMethod(args)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class SoundPlayTrigger(method: Any) : Trigger(method, TriggerType.SOUND_PLAY) {
*/
fun setCriteria(soundNameCriteria: String) = apply { this.soundNameCriteria = soundNameCriteria }

override fun trigger(args: Array<out Any?>) {
override fun triggerImpl(args: Array<out Any?>) {
if (args[1] is CharSequence
&& soundNameCriteria != ""
&& !args[1].toString().equals(soundNameCriteria, ignoreCase = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class StepTrigger(method: Any) : Trigger(method, TriggerType.STEP) {
return super.register()
}

override fun trigger(args: Array<out Any?>) {
override fun triggerImpl(args: Array<out Any?>) {
if (delay < 0) {
// run trigger based on set fps value (60 per second by default)
while (systemTime < Client.getSystemTime() + 1000 / fps) {
Expand Down
7 changes: 5 additions & 2 deletions src/main/kotlin/com/chattriggers/ctjs/api/triggers/Trigger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ abstract class Trigger protected constructor(
var isRegistered = false
private set

internal val owningModule = JSLoader.activeModule

init {
// See note for register method
@Suppress("LeakingThis")
Expand Down Expand Up @@ -60,10 +62,11 @@ abstract class Trigger protected constructor(

protected fun callMethod(args: Array<out Any?>) {
if (CTJS.isLoaded)
JSLoader.trigger(this, method, args)
JSLoader.invoke(this, method, args)
}

internal abstract fun trigger(args: Array<out Any?>)
// Note: This method should not be called directly. Use JSLoader.trigger() instead
internal abstract fun triggerImpl(args: Array<out Any?>)

override fun compareTo(other: Trigger): Int {
val ordCmp = priority.ordinal - other.priority.ordinal
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.chattriggers.ctjs.internal.commands

import com.chattriggers.ctjs.api.triggers.CommandTrigger
import com.chattriggers.ctjs.internal.engine.JSLoader
import com.chattriggers.ctjs.internal.mixins.commands.CommandNodeAccessor
import com.chattriggers.ctjs.internal.utils.asMixin
import com.mojang.brigadier.CommandDispatcher
Expand Down Expand Up @@ -38,9 +39,9 @@ internal class StaticCommand(
builder.buildFuture()
}
.onExecute {
trigger.trigger(StringArgumentType.getString(it, "args").split(" ").toTypedArray())
JSLoader.trigger(trigger, StringArgumentType.getString(it, "args").split(" ").toTypedArray())
})
.onExecute { trigger.trigger(emptyArray()) }
.onExecute { JSLoader.trigger(trigger) }

val node = dispatcher.register(builder)

Expand Down
40 changes: 31 additions & 9 deletions src/main/kotlin/com/chattriggers/ctjs/internal/engine/JSLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ object JSLoader {
MethodType.methodType(Any::class.java, Callable::class.java, Array<Any?>::class.java),
)

internal var activeModuleMap = ThreadLocal<Module?>()
internal val activeModule: Module?
get() = activeModuleMap.get()

fun setup(jars: List<URL>) {
// Ensure all active mixins are invalidated
// TODO: It would be nice to do this, but it's possible to have a @Redirect or similar
Expand Down Expand Up @@ -111,17 +115,22 @@ object JSLoader {
}

fun entryPass(module: Module, entryURI: URI): Unit = wrapInContext {
check(activeModule == null) { "Entry pass ran while module ${activeModule!!.name} was active" }
activeModuleMap.set(module)

try {
require.loadCTModule(module.name, entryURI)
} catch (e: Throwable) {
println("Error loading module ${module.name}")
"Error loading module ${module.name}".printToConsole(LogType.ERROR)
e.printTraceToConsole()
} finally {
activeModuleMap.set(null)
}
}

fun exec(type: ITriggerType, args: Array<out Any?>) {
triggers[type]?.forEach { it.trigger(args) }
triggers[type]?.forEach { trigger(it, args) }
}

fun addTrigger(trigger: Trigger) {
Expand All @@ -136,7 +145,20 @@ object JSLoader {
triggers[trigger.type]?.remove(trigger)
}

// Note: block takes a Context since most caller use it. Context.getContext() is a threadlocal access, so we might
fun trigger(trigger: Trigger, args: Array<out Any?> = emptyArray()) {
check(activeModule == null) {
"${trigger::class.simpleName} called while module ${activeModule!!.name} was active"
}
activeModuleMap.set(trigger.owningModule)

try {
trigger.triggerImpl(args)
} finally {
activeModuleMap.set(null)
}
}

// Note: block takes a Context since most callers use it. Context.getContext() is a threadlocal access, so we might
// as well avoid it if we can
internal inline fun <T> wrapInContext(context: Context? = null, crossinline block: (Context) -> T): T {
contract {
Expand Down Expand Up @@ -174,13 +196,7 @@ object JSLoader {
}
}

fun invoke(method: Callable, args: Array<out Any?>, thisObj: Scriptable = moduleScope): Any? {
return wrapInContext {
Context.jsToJava(method.call(it, moduleScope, thisObj, args), Any::class.java)
}
}

fun trigger(trigger: Trigger, method: Any, args: Array<out Any?>) {
fun invoke(trigger: Trigger, method: Any, args: Array<out Any?>) {
try {
require(method is Callable) { "Need to pass actual function to the register function, not the name!" }
invoke(method, args)
Expand All @@ -190,6 +206,12 @@ object JSLoader {
}
}

fun invoke(method: Callable, args: Array<out Any?>, thisObj: Scriptable = moduleScope): Any? {
return wrapInContext {
Context.jsToJava(method.call(it, moduleScope, thisObj, args), Any::class.java)
}
}

private fun loadMixinLibs() {
val mixinProvidedLibs = saveResource(
"/assets/chattriggers/js/mixinProvidedLibs.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class Module(val name: String, var metadata: ModuleMetadata, val folder: File) {
var targetModVersion: Version? = null
var requiredBy = mutableSetOf<String>()

fun copy() = Module(name, metadata.copy(), folder).also {
it.targetModVersion = targetModVersion
it.requiredBy = requiredBy.toMutableSet()
}

private val gui = object {
var collapsed = true
var x = 0f
Expand Down

0 comments on commit 10c8e32

Please sign in to comment.