commonMain.pipeline.ProcessorPipeline.kt Maven / Gradle / Ivy
Show all versions of mirai-core-jvm Show documentation
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.pipeline
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.network.components.NoticeProcessor
import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable
import net.mamoe.mirai.utils.*
import java.io.Closeable
import java.util.*
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.ConcurrentLinkedQueue
internal interface Processor, D> : PipelineConsumptionMarker {
val origin: Any get() = this
suspend fun process(context: C, data: D)
}
internal interface ProcessorPipeline, C : ProcessorPipelineContext, D, R> {
val processors: MutableCollection>
fun interface DisposableRegistry : Closeable {
fun dispose()
override fun close() {
dispose()
}
}
fun registerProcessor(processor: P): DisposableRegistry // after
fun registerBefore(processor: P): DisposableRegistry
fun createContext(data: D, attributes: TypeSafeMap): C
/**
* Process using the [context].
*/
suspend fun process(
data: D,
context: C,
attributes: TypeSafeMap = TypeSafeMap.EMPTY,
): ProcessResult
/**
* Process with a new context
*/
suspend fun process(
data: D,
attributes: TypeSafeMap = TypeSafeMap.EMPTY
): ProcessResult
}
internal inline fun , Pip : ProcessorPipeline
> Pip.replaceProcessor(
predicate: (origin: Any) -> Boolean,
processor: P
): Boolean {
for (box in processors) {
val value = box.value
if (predicate(value.origin)) {
box.value = processor
return true
}
}
return false
}
internal data class ProcessorBox
>(
var value: P
)
internal data class ProcessResult, R>(
val context: C,
val collected: Collection,
)
@JvmInline
internal value class MutablePipelineResult(
val data: MutableCollection
)
internal interface PipelineConsumptionMarker
internal interface ProcessorPipelineContext {
/**
* Child processes ([processAlso]) will inherit [attributes] from its parent, while any other properties from the context will not.
*/
val attributes: TypeSafeMap
val collected: MutablePipelineResult
// DSL to simplify some expressions
operator fun MutablePipelineResult.plusAssign(result: R?) {
if (result != null) collect(result)
}
/**
* Collect a result.
*/
fun collect(result: R)
/**
* Collect results.
*/
fun collect(results: Iterable)
val isConsumed: Boolean
/**
* Marks the input as consumed so that there will not be warnings like 'Unknown type xxx'. This will not stop the pipeline.
*
* If this is executed, make sure you provided all information important for debugging.
*
* You need to invoke [markAsConsumed] if your implementation includes some `else` branch which covers all situations,
* and throws a [contextualBugReportException] or logs something.
*/
@ConsumptionMarker
fun PipelineConsumptionMarker.markAsConsumed(marker: Any = this)
/**
* Marks the input as not consumed, if it was marked by this [NoticeProcessor].
*/
@ConsumptionMarker
fun PipelineConsumptionMarker.markNotConsumed(marker: Any = this)
@DslMarker
annotation class ConsumptionMarker // to give an explicit color.
/**
* Fire the [data] into the processor pipeline, and collect the results to current [collected], updating *some mutable properties* in contexts, e.g. [OutgoingMessagePipelineContext.currentMessageChain]
*
* @param extraAttributes extra attributes
* @return result collected from processors. This would also have been collected to this context (where you call [processAlso]).
*/
suspend fun processAlso(
data: D,
extraAttributes: TypeSafeMap = TypeSafeMap.EMPTY
): ProcessResult, R>
}
internal abstract class AbstractProcessorPipelineContext(
override val attributes: TypeSafeMap,
private val traceLogging: MiraiLogger,
) : ProcessorPipelineContext {
private val consumers: Stack = Stack()
override val isConsumed: Boolean get() = consumers.isNotEmpty()
override fun PipelineConsumptionMarker.markAsConsumed(marker: Any) {
traceLogging.info { "markAsConsumed: marker=$marker" }
consumers.push(marker)
}
override fun PipelineConsumptionMarker.markNotConsumed(marker: Any) {
if (consumers.peek() === marker) {
consumers.pop()
traceLogging.info { "markNotConsumed: Y, marker=$marker" }
} else {
traceLogging.info { "markNotConsumed: N, marker=$marker" }
}
}
override val collected: MutablePipelineResult = MutablePipelineResult(ConcurrentLinkedQueue())
override fun collect(result: R) {
collected.data.add(result)
traceLogging.info { "collect: $result" }
}
override fun collect(results: Iterable) {
this.collected.data.addAll(results)
traceLogging.info { "collect: $results" }
}
}
internal class PipelineConfiguration(
var stopWhenConsumed: Boolean
)
internal abstract class AbstractProcessorPipeline, C : ProcessorPipelineContext, D, R>
protected constructor(
val configuration: PipelineConfiguration,
val traceLogging: MiraiLogger,
) : ProcessorPipeline {
constructor(configuration: PipelineConfiguration) : this(configuration, SilentLogger)
/**
* Must be ordered
*/
override val processors: ConcurrentLinkedDeque> = ConcurrentLinkedDeque()
override fun registerProcessor(processor: P): ProcessorPipeline.DisposableRegistry {
val box = ProcessorBox(processor)
processors.add(box)
return ProcessorPipeline.DisposableRegistry {
processors.remove(box)
}
}
override fun registerBefore(processor: P): ProcessorPipeline.DisposableRegistry {
val box = ProcessorBox(processor)
processors.addFirst(box)
return ProcessorPipeline.DisposableRegistry {
processors.remove(box)
}
}
abstract inner class BaseContextImpl(
attributes: TypeSafeMap,
) : AbstractProcessorPipelineContext(attributes, traceLogging) {
override suspend fun processAlso(
data: D,
extraAttributes: TypeSafeMap
): ProcessResult, R> {
traceLogging.info { "processAlso: data=${data.structureToStringAndDesensitizeIfAvailable()}" }
traceLogging.info { "extraAttributes = $extraAttributes" }
val newAttributes = this.attributes + extraAttributes
traceLogging.info { "newAttributes = $newAttributes" }
return process(data, newAttributes).also {
this.collected.data += it.collected
traceLogging.info { "processAlso: result=$it" }
}
}
}
protected open fun handleExceptionInProcess(
data: D,
context: C,
attributes: TypeSafeMap,
processor: P,
e: Throwable
): Unit = throw e
override suspend fun process(data: D, attributes: TypeSafeMap): ProcessResult {
return process(data, createContext(data, attributes), attributes)
}
override suspend fun process(data: D, context: C, attributes: TypeSafeMap): ProcessResult {
traceLogging.info { "process: data=${data.structureToStringAndDesensitizeIfAvailable()}" }
val diff = if (traceLogging.isEnabled) CollectionDiff() else null
diff?.save(context.collected.data)
for ((processor) in processors) {
val result = kotlin.runCatching {
processor.process(context, data)
}.onFailure { e ->
handleExceptionInProcess(data, context, attributes, processor, e)
}
diff?.run {
val diffPackets = subtractAndSave(context.collected.data)
traceLogging.info {
"Finished ${
processor.toString().replace("net.mamoe.mirai.internal.network.notice.", "")
}, success=${result.isSuccess}, consumed=${context.isConsumed}, diff=$diffPackets"
}
}
if (context.isConsumed && configuration.stopWhenConsumed) {
traceLogging.info { "stopWhenConsumed=true, stopped." }
break
}
}
return ProcessResult(context, context.collected.data)
}
}