Skip to content

Instantly share code, notes, and snippets.

@JSH32
Created March 10, 2023 00:07
Show Gist options
  • Save JSH32/70cedfc288b933da875267c35ea44d61 to your computer and use it in GitHub Desktop.
Save JSH32/70cedfc288b933da875267c35ea44d61 to your computer and use it in GitHub Desktop.
package com.github.jsh32.paradoxia.commons
import net.minestom.server.MinecraftServer
import net.minestom.server.command.builder.Command
import net.minestom.server.event.EventNode
import org.koin.core.component.KoinComponent
import org.koin.core.context.loadKoinModules
import org.koin.core.context.unloadKoinModules
import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.qualifier
import org.koin.dsl.module
import org.koin.java.KoinJavaComponent
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.reflect.KClass
internal typealias FeatureDefinition = Pair<KClass<out Feature>, Definition<out Feature>>
/**
* Builder for initializing features
*/
class FeatureBuilder {
/**
* Features that will be added later
*/
internal val features = mutableListOf<FeatureDefinition>()
fun addFeature(qualifier: KClass<out Feature>, definition: Definition<out Feature>) { features.add(Pair(qualifier, definition)) }
inline fun <reified T: Feature> feature(noinline definition: Definition<T>) { addFeature(T::class, definition) }
}
/**
* Feature manager.
*/
internal class FeatureManager {
private data class FeatureBox(val ctx: FeatureContext, val module: Module)
private val features = IdentityHashMap<KClass<*>, FeatureBox>()
/**
* Initialize all features. This will inject them into Koin.
*/
suspend fun initialize(toAdd: List<FeatureDefinition>) {
// This should create and inject everything in proper order
val modules = toAdd.map { (type, feature) ->
// Inject with class name and with qualifier.
Pair(type, module { single(qualifier(type.qualifiedName!!), definition = feature) })
}
// Load all modules.
loadKoinModules(modules.map { it.second })
// Initialize the modules after injection.
modules.forEach { (type, module) ->
if (features[type] != null) {
throw IllegalArgumentException("Feature ${type.simpleName} already loaded.")
}
val context = FeatureContext(type)
KoinJavaComponent.getKoin().get<Feature>(qualifier(type.qualifiedName!!)).initialize(context)
context.logger.info("${type.simpleName} initialized.")
features[type] = FeatureBox(context, module)
}
}
/**
* De-initialize all features.
*/
suspend fun deInitialize() {
val koin = KoinJavaComponent.getKoin()
features.forEach { (type, box) ->
val feature = koin.get<Feature>(qualifier(type.qualifiedName!!))
feature.deInitialize(box.ctx)
box.ctx.destroy()
features.remove(type)
// Remove from the DI system.
unloadKoinModules(box.module)
}
}
}
class FeatureContext internal constructor(type: KClass<out Feature>) {
private val commands = mutableSetOf<Command>()
/**
* Logger for the
*/
val logger: Logger = LoggerFactory.getLogger(type.simpleName!!)
val eventNode = run {
val eventNode = EventNode.all(type.simpleName!!)
MinecraftServer.getGlobalEventHandler().addChild(eventNode)
eventNode
}
fun registerCommand(command: Command) {
commands.add(command)
MinecraftServer.getCommandManager().register(command)
}
internal fun destroy() {
MinecraftServer.getGlobalEventHandler().removeChild(eventNode)
commands.forEach { MinecraftServer.getCommandManager().unregister(it) }
}
}
/**
* A concept for loading modules of the server, similar-ish to extensions.
* Common registration functions are provided on [FeatureContext]. These should be used over accessing the
* server directly, because they can be transparently extended to support unloading facets if this ever
* becomes a desired behavior.
*/
abstract class Feature : KoinComponent {
abstract suspend fun initialize(context: FeatureContext)
abstract suspend fun deInitialize(context: FeatureContext)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment