1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-05-06 05:45:19 +08:00

[core] Update docs for implementations

This commit is contained in:
Him188 2022-07-31 15:44:48 +08:00
parent c383d3bf9c
commit eeb10cc89a
No known key found for this signature in database
GPG Key ID: BA439CDDCF652375
6 changed files with 61 additions and 17 deletions
mirai-core-utils/src/commonMain/kotlin
mirai-core/src/commonMain/kotlin/network

View File

@ -18,8 +18,15 @@ import kotlin.reflect.KProperty
private val UNINITIALIZED: Any? = Symbol("UNINITIALIZED")
/**
* - [initializer] is supported to be called at most once, however multiple invocations may happen if executed by multiple coroutines in single thread.
* - [ReadWriteProperty.setValue] prevails on competition with [initializer].
* Creates a lazily initialized, atomic, mutable property.
*
* [initializer] will be called at most once for most of the time, but not always.
* Multiple invocations may happen if property getter is called by multiple coroutines in single thread (implementation use reentrant lock).
* Hence, you must not trust [initializer] to be called only once.
*
* If property setter is executed before any execution of getter, [initializer] will not be called.
* While [initializer] is running, i.e. still calculating the value to set to the property,
* calling property setter will *outdo* the initializer. That is, the setter always prevails on competition with [initializer].
*/
public fun <T> lateinitMutableProperty(initializer: () -> T): ReadWriteProperty<Any?, T> =
LateinitMutableProperty(initializer)
@ -38,7 +45,7 @@ private class LateinitMutableProperty<T>(
val initializer = initializer
if (initializer != null && this.value.value === UNINITIALIZED) {
val value = initializer()
this.initializer = null
this.initializer = null // not used anymore, help gc
this.value.compareAndSet(UNINITIALIZED, value) // setValue prevails
this.value.value.let {
check(it !== UNINITIALIZED)
@ -46,6 +53,7 @@ private class LateinitMutableProperty<T>(
}
} else this.value.value as T
}
else -> v as T
}
}

View File

@ -11,6 +11,9 @@ package net.mamoe.mirai.utils
import kotlin.jvm.JvmName
/**
* An object with name for debugging purposes. Handy for states.
*/
public class Symbol private constructor(name: String) {
private val str = "Symbol($name)"
override fun toString(): String = str

View File

@ -88,7 +88,7 @@ internal class PacketCodecException(
OTHER,
}
// not available in native
// not available in native
// override fun getStackTrace(): Array<StackTraceElement> {
// return targetException.stackTrace
// }

View File

@ -63,6 +63,7 @@ internal abstract class CommonNetworkHandler<Conn>(
setState { StateClosed(error) }
return
}
PacketCodecException.Kind.PROTOCOL_UPDATED -> passToExceptionHandler()
PacketCodecException.Kind.OTHER -> passToExceptionHandler()
}
@ -76,7 +77,7 @@ internal abstract class CommonNetworkHandler<Conn>(
///////////////////////////////////////////////////////////////////////////
/**
* Creates a connection
* Creates a connection.
*/
protected abstract suspend fun createConnection(): Conn
@ -85,9 +86,17 @@ internal abstract class CommonNetworkHandler<Conn>(
*/
protected abstract fun Conn.writeAndFlushOrCloseAsync(packet: OutgoingPacket)
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
/**
* Close this connection for general reason, immediately. Immediately means any live objects with lifecycle must be marked *cancelled*, *dead* or any terminate state.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "KotlinRedundantDiagnosticSuppress") // can happen on some platforms
protected abstract fun Conn.close()
/**
* *Single-thread* event-loop packet decoder.
*
* Call [PacketDecodePipeline.send] to submit a decoding job.
*/
internal inner class PacketDecodePipeline(parentContext: CoroutineContext) :
CoroutineScope by parentContext.childScope() {
private val packetCodec: PacketCodec by lazy { context[PacketCodec] }

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
import net.mamoe.mirai.internal.network.handler.state.StateObserver
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
import net.mamoe.mirai.internal.network.protocol.packet.PacketFactory
import net.mamoe.mirai.utils.MiraiLogger
/**
@ -127,12 +128,6 @@ internal interface NetworkHandler : CoroutineScope {
suspend fun resumeConnection()
suspend fun <P : Packet?> sendAndExpect(
packet: OutgoingPacketWithRespType<P>,
timeout: Long = 5000,
attempts: Int = 2
): P
/**
* Sends [packet], suspends and expects to receive a response from the server.
*
@ -145,6 +140,19 @@ internal interface NetworkHandler : CoroutineScope {
*
* @param attempts ranges `1..INFINITY`
*/
suspend fun <P : Packet?> sendAndExpect(
packet: OutgoingPacketWithRespType<P>,
timeout: Long = 5000,
attempts: Int = 2
): P
/**
* Sends [packet], suspends and expects to receive a response from the server.
*
* Note that it's corresponding [PacketFactory]'s responsibility to decode the returned date from server to give resultant [Packet],
* and that packet is cast to [P], hence unsafe (vulnerable for [ClassCastException]).
* Always use [sendAndExpect] with [OutgoingPacketWithRespType], use this unsafe overload only for legacy code.
*/
suspend fun <P : Packet?> sendAndExpect(packet: OutgoingPacket, timeout: Long = 5000, attempts: Int = 2): P
/**
@ -173,13 +181,16 @@ internal interface NetworkHandler : CoroutineScope {
internal val NetworkHandler.logger: MiraiLogger get() = context.logger
/**
* Suspend coroutine to wait for the state [suspendUntil].
* Suspends coroutine to wait for the state [suspendUntil].
*/
internal suspend fun NetworkHandler.awaitState(suspendUntil: NetworkHandler.State) {
if (this.state == suspendUntil) return
stateChannel.consumeAsFlow().takeWhile { it != suspendUntil }.collect()
}
/**
* Suspends coroutine to wait for a state change. Returns immediately after [NetworkHandler.state] has changed.
*/
internal suspend fun NetworkHandler.awaitStateChange() {
stateChannel.consumeAsFlow().first()
}

View File

@ -35,7 +35,7 @@ import kotlin.jvm.Volatile
import kotlin.reflect.KClass
/**
* Implements basic logics of [NetworkHandler]
* Implements a state-based [NetworkHandler].
*/
internal abstract class NetworkHandlerSupport(
final override val context: NetworkHandlerContext,
@ -45,14 +45,24 @@ internal abstract class NetworkHandlerSupport(
additionalCoroutineContext.childScopeContext(SupervisorJob(context.bot.coroutineContext.job))
.plus(CoroutineExceptionHandler.fromMiraiLogger(logger))
/**
* Creates an instance of the initial state. This is guaranteed to be called at most once.
*/
protected abstract fun initialState(): BaseStateImpl
/**
* It's not guaranteed whether this function sends the packet in-place or launches a coroutine for it.
* Caller should not rely on this property.
* Performs network IO or launches a coroutine for that, to send the [packet].
*
* **Node**: It's not guaranteed whether this function sends the packet in-place or launches a coroutine for it.
* Caller should not rely on this characteristic.
*/
protected abstract suspend fun sendPacketImpl(packet: OutgoingPacket)
/**
* Handles *unknown* [RawIncomingPacket]s. Packets with unrecognized [RawIncomingPacket.commandName] are considered *unknown*.
*
* Can be called by implementation
*/
protected fun collectUnknownPacket(raw: RawIncomingPacket) {
packetLogger.debug { "Unknown packet: commandName=${raw.commandName}, body=${raw.body.toUHexString()}" }
// may add hooks here (to context)
@ -137,6 +147,9 @@ internal abstract class NetworkHandlerSupport(
sendPacketImpl(packet)
}
/**
* Listens for the resultant packet from server.
*/
protected class PacketListener(
val commandName: String,
val sequenceId: Int,
@ -231,7 +244,7 @@ internal abstract class NetworkHandlerSupport(
/**
* State is *lazy*, initialized only if requested.
*
* You need to call setter inside `synchronized(this) { }`.
* You must not set this property directly, but use [setState].
*/
@Suppress("PropertyName")
protected var _state: BaseStateImpl by lateinitMutableProperty { initialState() }