Skip to content

Commit c82c3ac

Browse files
committed
Port BotCommands-Restart (repo)
1 parent 68cd70d commit c82c3ac

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2757
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
3+
plugins {
4+
id("BotCommands-conventions")
5+
id("BotCommands-publish-conventions")
6+
}
7+
8+
dependencies {
9+
api(projects.botCommands)
10+
11+
// Logging
12+
implementation(libs.kotlin.logging)
13+
14+
// -------------------- TEST DEPENDENCIES --------------------
15+
16+
testImplementation(libs.mockk)
17+
testImplementation(libs.bytebuddy)
18+
testImplementation(libs.logback.classic)
19+
}
20+
21+
java {
22+
sourceCompatibility = JavaVersion.VERSION_24
23+
targetCompatibility = JavaVersion.VERSION_24
24+
}
25+
26+
kotlin {
27+
compilerOptions {
28+
jvmTarget = JvmTarget.JVM_24
29+
30+
freeCompilerArgs.addAll(
31+
"-Xcontext-parameters",
32+
)
33+
}
34+
}
35+
36+
val jar by tasks.getting(Jar::class) {
37+
manifest {
38+
attributes(
39+
"Premain-Class" to "dev.freya02.botcommands.restart.jda.cache.Agent",
40+
)
41+
}
42+
}
43+
44+
tasks.withType<Test> {
45+
useJUnitPlatform()
46+
47+
jvmArgs("-javaagent:${jar.archiveFile.get().asFile.absolutePath}")
48+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dev.freya02.botcommands.restart.jda.cache
2+
3+
import dev.freya02.botcommands.restart.jda.cache.transformer.BContextImplTransformer
4+
import dev.freya02.botcommands.restart.jda.cache.transformer.JDABuilderTransformer
5+
import dev.freya02.botcommands.restart.jda.cache.transformer.JDAImplTransformer
6+
import dev.freya02.botcommands.restart.jda.cache.transformer.JDAServiceTransformer
7+
import java.lang.instrument.Instrumentation
8+
9+
object Agent {
10+
11+
@JvmStatic
12+
fun premain(agentArgs: String?, inst: Instrumentation) {
13+
inst.addTransformer(JDABuilderTransformer)
14+
inst.addTransformer(JDAServiceTransformer)
15+
inst.addTransformer(BContextImplTransformer)
16+
inst.addTransformer(JDAImplTransformer)
17+
}
18+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dev.freya02.botcommands.restart.jda.cache
2+
3+
import net.dv8tion.jda.api.events.GenericEvent
4+
import net.dv8tion.jda.api.hooks.IEventManager
5+
import java.util.concurrent.locks.ReentrantLock
6+
import kotlin.concurrent.withLock
7+
8+
internal class BufferingEventManager @DynamicCall constructor(
9+
delegate: IEventManager,
10+
) : IEventManager {
11+
12+
private val lock = ReentrantLock()
13+
private val eventBuffer: MutableList<GenericEvent> = arrayListOf()
14+
15+
private var delegate: IEventManager? = delegate
16+
17+
internal fun setDelegate(delegate: IEventManager) {
18+
lock.withLock {
19+
check(delegate !is BufferingEventManager) {
20+
"Tried to delegate to a BufferingEventManager!"
21+
}
22+
23+
this.delegate = delegate
24+
eventBuffer.forEach(::handle)
25+
}
26+
}
27+
28+
internal fun detach() {
29+
lock.withLock {
30+
delegate = null
31+
}
32+
}
33+
34+
override fun register(listener: Any) {
35+
lock.withLock {
36+
val delegate = delegate ?: error("Should not happen, implement a listener queue if necessary")
37+
delegate.register(listener)
38+
}
39+
}
40+
41+
override fun unregister(listener: Any) {
42+
lock.withLock {
43+
val delegate = delegate ?: error("Should not happen, implement a listener queue if necessary")
44+
delegate.unregister(listener)
45+
}
46+
}
47+
48+
override fun handle(event: GenericEvent) {
49+
val delegate = lock.withLock {
50+
val delegate = delegate
51+
if (delegate == null) {
52+
eventBuffer += event
53+
return
54+
}
55+
delegate
56+
}
57+
58+
delegate.handle(event)
59+
}
60+
61+
override fun getRegisteredListeners(): List<Any?> {
62+
lock.withLock {
63+
val delegate = delegate ?: error("Should not happen, implement a listener queue if necessary")
64+
return delegate.registeredListeners
65+
}
66+
}
67+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.freya02.botcommands.restart.jda.cache
2+
3+
/**
4+
* This member is used by generated code and as such is not directly referenced.
5+
*
6+
* This member must be `public`.
7+
*/
8+
@Retention(AnnotationRetention.SOURCE)
9+
@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
10+
internal annotation class DynamicCall
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package dev.freya02.botcommands.restart.jda.cache
2+
3+
import io.github.freya022.botcommands.api.core.utils.enumSetOf
4+
import io.github.freya022.botcommands.api.core.utils.enumSetOfAll
5+
import io.github.oshai.kotlinlogging.KotlinLogging
6+
import net.dv8tion.jda.api.OnlineStatus
7+
import net.dv8tion.jda.api.entities.Activity
8+
import net.dv8tion.jda.api.hooks.IEventManager
9+
import net.dv8tion.jda.api.hooks.InterfacedEventManager
10+
import net.dv8tion.jda.api.utils.ChunkingFilter
11+
import net.dv8tion.jda.api.utils.MemberCachePolicy
12+
import net.dv8tion.jda.api.utils.cache.CacheFlag
13+
import java.util.*
14+
15+
private val logger = KotlinLogging.logger { }
16+
17+
class JDABuilderConfiguration internal constructor() {
18+
19+
private val warnedUnsupportedValues: MutableSet<String> = hashSetOf()
20+
21+
var hasUnsupportedValues = false
22+
private set
23+
24+
private val builderValues: MutableMap<ValueType, Any?> = hashMapOf()
25+
private var _eventManager: IEventManager? = null
26+
val eventManager: IEventManager get() = _eventManager ?: InterfacedEventManager()
27+
28+
// So we can track the initial token and intents, the constructor will be instrumented and call this method
29+
// The user overriding the values using token/intent setters should not be an issue
30+
@DynamicCall
31+
fun onInit(token: String?, intents: Int) {
32+
builderValues[ValueType.TOKEN] = token
33+
builderValues[ValueType.INTENTS] = intents
34+
builderValues[ValueType.CACHE_FLAGS] = enumSetOfAll<CacheFlag>()
35+
}
36+
37+
@DynamicCall
38+
fun markUnsupportedValue(signature: String) {
39+
if (warnedUnsupportedValues.add(signature))
40+
logger.warn { "Unsupported JDABuilder method '$signature', JDA will not be cached between restarts" }
41+
hasUnsupportedValues = true
42+
}
43+
44+
@DynamicCall
45+
fun setStatus(status: OnlineStatus) {
46+
builderValues[ValueType.STATUS] = status
47+
}
48+
49+
@DynamicCall
50+
fun setEventManager(eventManager: IEventManager?) {
51+
_eventManager = eventManager
52+
}
53+
54+
@DynamicCall
55+
fun setEventPassthrough(enable: Boolean) {
56+
builderValues[ValueType.EVENT_PASSTHROUGH] = enable
57+
}
58+
59+
@DynamicCall
60+
@Suppress("UNCHECKED_CAST")
61+
fun enableCache(first: CacheFlag, vararg others: CacheFlag) {
62+
(builderValues[ValueType.CACHE_FLAGS] as EnumSet<CacheFlag>) += enumSetOf(first, *others)
63+
}
64+
65+
@DynamicCall
66+
@Suppress("UNCHECKED_CAST")
67+
fun enableCache(flags: Collection<CacheFlag>) {
68+
(builderValues[ValueType.CACHE_FLAGS] as EnumSet<CacheFlag>) += flags
69+
}
70+
71+
@DynamicCall
72+
@Suppress("UNCHECKED_CAST")
73+
fun disableCache(first: CacheFlag, vararg others: CacheFlag) {
74+
(builderValues[ValueType.CACHE_FLAGS] as EnumSet<CacheFlag>) -= enumSetOf(first, *others)
75+
}
76+
77+
@DynamicCall
78+
@Suppress("UNCHECKED_CAST")
79+
fun disableCache(flags: Collection<CacheFlag>) {
80+
(builderValues[ValueType.CACHE_FLAGS] as EnumSet<CacheFlag>) -= flags
81+
}
82+
83+
@DynamicCall
84+
fun setMemberCachePolicy(memberCachePolicy: MemberCachePolicy?) {
85+
builderValues[ValueType.MEMBER_CACHE_POLICY] = memberCachePolicy
86+
}
87+
88+
@DynamicCall
89+
fun setChunkingFilter(filter: ChunkingFilter?) {
90+
builderValues[ValueType.CHUNKING_FILTER] = filter
91+
}
92+
93+
@DynamicCall
94+
fun setLargeThreshold(threshold: Int) {
95+
builderValues[ValueType.LARGE_THRESHOLD] = threshold
96+
}
97+
98+
@DynamicCall
99+
fun setActivity(activity: Activity?) {
100+
builderValues[ValueType.ACTIVITY] = activity
101+
}
102+
103+
@DynamicCall
104+
fun setEnableShutdownHook(enable: Boolean) {
105+
builderValues[ValueType.ENABLE_SHUTDOWN_HOOK] = enable
106+
}
107+
108+
internal infix fun isSameAs(other: JDABuilderConfiguration): Boolean {
109+
if (hasUnsupportedValues) return false
110+
if (other.hasUnsupportedValues) return false
111+
112+
return builderValues == other.builderValues
113+
}
114+
115+
private enum class ValueType {
116+
TOKEN,
117+
INTENTS,
118+
STATUS,
119+
EVENT_PASSTHROUGH,
120+
CACHE_FLAGS,
121+
// These two are interfaces, it's fine to compare them by equality,
122+
// their reference will be the same as they are from the app class loader,
123+
// so if two runs uses MemberCachePolicy#VOICE, it'll still be compatible
124+
MEMBER_CACHE_POLICY,
125+
CHUNKING_FILTER,
126+
LARGE_THRESHOLD,
127+
ACTIVITY,
128+
ENABLE_SHUTDOWN_HOOK,
129+
}
130+
}

0 commit comments

Comments
 (0)