Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/main/kotlin/dev/jetpack/engine/parser/Parser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Parser(private val tokens: List<Token>) {
private var pos = 0
private var statementDepth = 0
private var pendingCommandAnnotations: CommandAnnotations = CommandAnnotations.EMPTY
private var pendingListenerAnnotations: ListenerAnnotations = ListenerAnnotations.EMPTY

private fun peek(): Token = tokens[pos]
private fun peek(offset: Int): Token =
Expand Down Expand Up @@ -76,6 +77,8 @@ class Parser(private val tokens: List<Token>) {
if (isAtEnd()) { stmts.addAll(pendingMeta); break }
if (pendingMeta.isNotEmpty() && isCommandDeclarationAhead()) {
pendingCommandAnnotations = buildCommandAnnotations(pendingMeta)
} else if (pendingMeta.isNotEmpty() && isListenerDeclarationAhead()) {
pendingListenerAnnotations = buildListenerAnnotations(pendingMeta)
} else {
stmts.addAll(pendingMeta)
}
Expand All @@ -91,6 +94,12 @@ class Parser(private val tokens: List<Token>) {
return i < tokens.size && tokens[i].type == TokenType.KW_COMMAND
}

private fun isListenerDeclarationAhead(): Boolean {
var i = pos
while (i < tokens.size && tokens[i].type in ACCESS_MODIFIERS) i++
return i < tokens.size && tokens[i].type == TokenType.KW_LISTENER
}

private fun buildCommandAnnotations(meta: List<Statement.Metadata>): CommandAnnotations {
var description: String? = null
var permission: String? = null
Expand All @@ -109,6 +118,18 @@ class Parser(private val tokens: List<Token>) {
return CommandAnnotations(description, permission, permissionMessage, usage, aliases)
}

private fun buildListenerAnnotations(meta: List<Statement.Metadata>): ListenerAnnotations {
var priority: String? = null
var ignoreCancelled = false
for (m in meta) {
when (m.key) {
"priority" -> priority = m.value
"ignoreCancelled" -> ignoreCancelled = m.value.trim().lowercase() == "true"
}
}
return ListenerAnnotations(priority, ignoreCancelled)
}

private fun parseAliasList(raw: String): List<String> {
val trimmed = raw.trim()
if (trimmed.isEmpty()) return emptyList()
Expand Down Expand Up @@ -453,6 +474,7 @@ class Parser(private val tokens: List<Token>) {
}

private fun parseListenerDecl(access: AccessModifier, line: Int): Statement.ListenerDecl {
val annotations = pendingListenerAnnotations.also { pendingListenerAnnotations = ListenerAnnotations.EMPTY }
expect(TokenType.KW_LISTENER, "Expected 'listener'")
val eventType = expect(TokenType.IDENTIFIER, "Expected event type").value
val name = expect(TokenType.IDENTIFIER, "Expected listener name").value
Expand All @@ -465,7 +487,7 @@ class Parser(private val tokens: List<Token>) {
skipNewlines()
expect(TokenType.RPAREN, "Expected ')' after sender parameter")
val body = parseBlock()
return Statement.ListenerDecl(access, eventType, name, sender, body, line)
return Statement.ListenerDecl(access, eventType, name, sender, body, annotations, line)
}

private fun parseIfStmt(line: Int): Statement.IfStmt {
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/dev/jetpack/engine/parser/ast/Statement.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ sealed class Statement {
val name: String,
val senderParam: String?,
val body: List<Statement>,
val annotations: ListenerAnnotations,
override val line: Int,
) : Statement()
data class IfStmt(
Expand Down Expand Up @@ -99,6 +100,15 @@ sealed class Statement {
) : Statement()
}

data class ListenerAnnotations(
val priority: String?,
val ignoreCancelled: Boolean,
) {
companion object {
val EMPTY = ListenerAnnotations(null, false)
}
}

data class CommandAnnotations(
val description: String?,
val permission: String?,
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/dev/jetpack/engine/resolver/NameResolver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ class NameResolver(private val reservedNames: Set<String> = emptySet()) {
if (!isFileScope) error("Listener can only be declared at file scope", stmt.line)
if (JetpackEvent.resolve(stmt.eventType) == null)
error("Unknown event type '${stmt.eventType}'", stmt.line)
val priority = stmt.annotations.priority
if (priority != null) {
val validPriorities = setOf("LOWEST", "LOW", "NORMAL", "HIGH", "HIGHEST", "MONITOR")
if (priority.uppercase() !in validPriorities)
error("Unknown event priority '$priority'. Valid values: ${validPriorities.joinToString()}", stmt.line)
}
val prevFn = insideFunction
val prevLoop = insideLoop
val prevFile = isFileScope
Expand Down
16 changes: 13 additions & 3 deletions src/main/kotlin/dev/jetpack/engine/runtime/Interpreter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,13 @@ class CommandNode(

interface ScriptEnvironment {
fun registerInterval(name: String, ms: Int, body: suspend () -> Unit): IntervalHandle
fun registerListener(eventType: String, line: Int, body: suspend (JetValue) -> Unit): ListenerHandle
fun registerListener(
eventType: String,
line: Int,
priority: String?,
ignoreCancelled: Boolean,
body: suspend (JetValue) -> Unit,
): ListenerHandle
fun registerCommand(node: CommandNode): CommandHandle
suspend fun <T> runThread(body: suspend () -> T): T
}
Expand Down Expand Up @@ -494,8 +500,12 @@ class Interpreter(
if (stmt.senderParam != null) child.define(stmt.senderParam, senderValue)
try { executeBlock(stmt.body, child) } catch (_: ReturnSignal) {}
}
val handle = env?.registerListener(stmt.eventType, stmt.line, body)
?: DetachedListenerHandle(body)
val handle = env?.registerListener(
stmt.eventType, stmt.line,
stmt.annotations.priority,
stmt.annotations.ignoreCancelled,
body,
) ?: DetachedListenerHandle(body)
withScopeRuntimeError(stmt.line) {
scope.define(stmt.name, JListener(handle))
}
Expand Down
35 changes: 24 additions & 11 deletions src/main/kotlin/dev/jetpack/event/EventBridge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ object EventBridge {
private class EventBinding(
val eventClassName: String,
val eventClass: Class<out Event>,
val priority: EventPriority,
val ignoreCancelled: Boolean,
val listener: Listener = object : Listener {},
@Volatile var registered: Boolean = false,
)
Expand All @@ -38,13 +40,18 @@ object EventBridge {
plugin: JetpackPlugin,
eventClassName: String,
scriptFile: String,
priority: EventPriority,
ignoreCancelled: Boolean,
callback: (JetValue) -> Unit,
): ListenerHandle {
val eventClass = resolveEventClass(eventClassName)
requireNotNull(eventClass) { "Unknown event type '$eventClassName'" }
val binding = bindings.computeIfAbsent(eventClassName) { EventBinding(eventClassName, eventClass) }
val bindingKey = bindingKey(eventClassName, priority, ignoreCancelled)
val binding = bindings.computeIfAbsent(bindingKey) {
EventBinding(eventClassName, eventClass, priority, ignoreCancelled)
}
val entry = ListenerEntry(scriptFile, callback)
handlers.getOrPut(eventClassName) { CopyOnWriteArrayList() }.add(entry)
handlers.getOrPut(bindingKey) { CopyOnWriteArrayList() }.add(entry)
reconcileBinding(plugin, binding)

return object : ListenerHandle {
Expand All @@ -66,7 +73,7 @@ object EventBridge {
if (entry.destroyed) return false
entry.destroyed = true
entry.active = false
handlers[eventClassName]?.remove(entry)
handlers[bindingKey]?.remove(entry)
cleanupEmptyBinding(binding)
return true
}
Expand All @@ -82,15 +89,15 @@ object EventBridge {
}

fun unregisterAll(scriptFile: String) {
for ((eventClassName, list) in handlers) {
for ((bindingKey, list) in handlers) {
val removed = list.removeAll { entry ->
if (entry.scriptFile != scriptFile) return@removeAll false
entry.destroyed = true
entry.active = false
true
}
if (removed) {
bindings[eventClassName]?.let(::cleanupEmptyBinding)
bindings[bindingKey]?.let(::cleanupEmptyBinding)
}
}
}
Expand Down Expand Up @@ -120,8 +127,9 @@ object EventBridge {
JetpackEvent.resolve(name)?.eventClass

private fun reconcileBinding(plugin: JetpackPlugin, binding: EventBinding) {
val key = bindingKey(binding.eventClassName, binding.priority, binding.ignoreCancelled)
synchronized(binding) {
val activeEntries = handlers[binding.eventClassName].orEmpty().filter { it.active && !it.destroyed }
val activeEntries = handlers[key].orEmpty().filter { it.active && !it.destroyed }
when {
activeEntries.isEmpty() && binding.registered -> {
HandlerList.unregisterAll(binding.listener)
Expand All @@ -131,10 +139,10 @@ object EventBridge {
plugin.server.pluginManager.registerEvent(
binding.eventClass,
binding.listener,
EventPriority.NORMAL,
binding.priority,
eventCallback@{ _, event ->
if (!binding.eventClass.isInstance(event)) return@eventCallback
val callbacks = handlers[binding.eventClassName].orEmpty()
val callbacks = handlers[key].orEmpty()
.filter { it.active && !it.destroyed }
if (callbacks.isEmpty()) return@eventCallback
val reflected = reflectToJetValue(event)
Expand All @@ -149,6 +157,7 @@ object EventBridge {
callbacks.forEach { it.callback(jetValue) }
},
plugin,
binding.ignoreCancelled,
)
binding.registered = true
}
Expand All @@ -157,21 +166,25 @@ object EventBridge {
}

private fun cleanupEmptyBinding(binding: EventBinding) {
val key = bindingKey(binding.eventClassName, binding.priority, binding.ignoreCancelled)
synchronized(binding) {
val list = handlers[binding.eventClassName]
val list = handlers[key]
if (list != null && list.isNotEmpty()) {
if (list.none { it.active && !it.destroyed } && binding.registered) {
HandlerList.unregisterAll(binding.listener)
binding.registered = false
}
return
}
handlers.remove(binding.eventClassName)
handlers.remove(key)
if (binding.registered) {
HandlerList.unregisterAll(binding.listener)
binding.registered = false
}
bindings.remove(binding.eventClassName, binding)
bindings.remove(key, binding)
}
}

private fun bindingKey(eventClassName: String, priority: EventPriority, ignoreCancelled: Boolean): String =
"$eventClassName:${priority.name}:$ignoreCancelled"
}
14 changes: 12 additions & 2 deletions src/main/kotlin/dev/jetpack/script/ScriptRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import kotlinx.coroutines.withContext
import org.bukkit.command.Command
import org.bukkit.command.CommandMap
import org.bukkit.command.CommandSender
import org.bukkit.event.EventPriority
import org.bukkit.plugin.Plugin
import java.io.File
import java.util.IdentityHashMap
Expand Down Expand Up @@ -255,7 +256,13 @@ class ScriptRunner(private val plugin: JetpackPlugin) {
return interval
}

override fun registerListener(eventType: String, line: Int, body: suspend (JetValue) -> Unit): ListenerHandle {
override fun registerListener(
eventType: String,
line: Int,
priority: String?,
ignoreCancelled: Boolean,
body: suspend (JetValue) -> Unit,
): ListenerHandle {
if (JetpackEvent.resolve(eventType) == null) {
reportError(module.meta.scriptId, "Unknown event type '$eventType'", line, module.sourceLines)
return object : ListenerHandle {
Expand All @@ -266,7 +273,10 @@ class ScriptRunner(private val plugin: JetpackPlugin) {
override fun isActive(): Boolean = false
}
}
val inner = EventBridge.register(plugin, eventType, module.meta.scriptId) { senderValue ->
val eventPriority = priority?.let {
runCatching { EventPriority.valueOf(it.uppercase()) }.getOrNull()
} ?: EventPriority.NORMAL
val inner = EventBridge.register(plugin, eventType, module.meta.scriptId, eventPriority, ignoreCancelled) { senderValue ->
coroutineScope.launch {
try {
body(senderValue)
Expand Down