Skip to content

Commit

Permalink
rearrange and fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Oct 22, 2024
1 parent b6fc7b5 commit bc27ea4
Show file tree
Hide file tree
Showing 8 changed files with 522 additions and 381 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ import me.him188.ani.app.torrent.api.pieces.PieceList
import me.him188.ani.app.torrent.api.pieces.PiecePriorities
import me.him188.ani.app.torrent.api.pieces.PieceState
import me.him188.ani.app.torrent.api.pieces.TorrentDownloadController
import me.him188.ani.app.torrent.api.pieces.count
import me.him188.ani.app.torrent.api.pieces.first
import me.him188.ani.app.torrent.api.pieces.isEmpty
import me.him188.ani.app.torrent.api.pieces.last
import me.him188.ani.app.torrent.api.pieces.sumOf
import me.him188.ani.app.torrent.io.TorrentInput
Expand Down Expand Up @@ -392,7 +394,7 @@ class AnitorrentDownloadSession(
fun onPieceDownloading(pieceIndex: Int) {
useTorrentInfoOrLaunch { info ->
with(info.allPiecesInTorrent) {
getByAbsolutePieceIndex(pieceIndex).compareAndSetState(
get(pieceIndex).compareAndSetState(
PieceState.READY,
PieceState.DOWNLOADING,
)
Expand All @@ -412,7 +414,7 @@ class AnitorrentDownloadSession(
pieceIndex: Int
) {
with(info.allPiecesInTorrent) {
info.allPiecesInTorrent.getByAbsolutePieceIndex(pieceIndex).state = PieceState.FINISHED
info.allPiecesInTorrent.get(pieceIndex).state = PieceState.FINISHED
}
for (file in openFiles) {
if (pieceIndex in file.entry.pieceIndexRange) {
Expand Down
333 changes: 10 additions & 323 deletions torrent/api/src/commonMain/kotlin/api/pieces/Piece.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,14 @@

package me.him188.ani.app.torrent.api.pieces

import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.jvm.JvmInline

@RequiresOptIn(
"This is a raw constructor, use it only when you know what you are doing.",
level = RequiresOptIn.Level.ERROR,
)
annotation class RawPieceConstructor

@JvmInline
value class Piece @RawPieceConstructor constructor(
value class Piece
/**
* You should always prefer [PieceList.createPieceByListIndexUnsafe]
*/
@RawPieceConstructor constructor(
/**
* 在一个 torrent file 中的 index.
*/
Expand All @@ -34,319 +29,11 @@ value class Piece @RawPieceConstructor constructor(
override fun toString(): String = "Piece($pieceIndex)"
}

class PieceListImpl(
@PublishedApi
override val sizes: LongArray, // immutable
@PublishedApi
override val dataOffsets: LongArray, // immutable
private val states: Array<PieceState>, // mutable
@PublishedApi
override val initialPieceIndex: Int, // 第 0 个元素的 piece index
) : PieceList(
) {
init {
require(sizes.size == dataOffsets.size) { "sizes.size != dataOffsets.size" }
require(sizes.size == states.size) { "sizes.size != states.size" }
}

override var Piece.state: PieceState
get() = states[pieceIndex]
set(value) {
states[pieceIndex] = value
}

private val lock = SynchronizedObject()

override fun Piece.compareAndSetState(expect: PieceState, update: PieceState): Boolean {
synchronized(lock) {
if (state == expect) {
state = update
return true
}
return false
}
}
}

class PieceListSlice(
private val delegate: PieceList,
private val startIndex: Int,
endIndex: Int,
) : PieceList() {
init {
require(startIndex >= 0) { "startIndex < 0" }
require(endIndex <= delegate.sizes.size) { "endIndex > list.sizes.size" }
require(startIndex < endIndex) { "startIndex >= endIndex" }
}

override val sizes: LongArray = delegate.sizes.copyOfRange(startIndex, endIndex)
override val dataOffsets: LongArray = delegate.dataOffsets.copyOfRange(startIndex, endIndex)
override val initialPieceIndex: Int = delegate.initialPieceIndex + startIndex
override var Piece.state: PieceState
get() = with(delegate) { delegate.getByAbsolutePieceIndex(pieceIndex).state }
set(value) {
with(delegate) {
delegate.getByAbsolutePieceIndex(pieceIndex).state = value
}
}

override fun Piece.compareAndSetState(expect: PieceState, update: PieceState): Boolean {
with(delegate) {
return delegate.getByAbsolutePieceIndex(pieceIndex).compareAndSetState(expect, update)
}
}
}

fun PieceList.slice(startIndex: Int, endIndex: Int): PieceList {
require(startIndex >= 0) { "startIndex < 0" }
require(endIndex <= sizes.size) { "endIndex > sizes.size" }
require(startIndex < endIndex) { "startIndex >= endIndex" }
return PieceListSlice(this, startIndex, endIndex)
}

abstract class PieceList protected constructor(
) {
@PublishedApi
internal abstract val sizes: LongArray // immutable

@PublishedApi
internal abstract val dataOffsets: LongArray // immutable

@PublishedApi
internal abstract val initialPieceIndex: Int // 第 0 个元素的 piece index

val count get() = sizes.size

fun isEmpty() = sizes.isEmpty()

val totalSize: Long get() = sizes.sum()

abstract var Piece.state: PieceState
abstract fun Piece.compareAndSetState(expect: PieceState, update: PieceState): Boolean

@PublishedApi
internal inline val Piece.indexInList get() = pieceIndex

/**
* 该 piece 的数据长度 bytes
*/
inline val Piece.size get() = sizes[indexInList]

/**
* 在种子所能下载的所有文件数据中的 offset bytes
*/
inline val Piece.dataOffset get() = dataOffsets[indexInList]

// extensions

val Piece.dataStartOffset: Long get() = dataOffset
val Piece.dataLastOffset: Long get() = dataOffset + size - 1
inline val Piece.offsetRange: LongRange get() = dataStartOffset..dataLastOffset

@OptIn(RawPieceConstructor::class)
@PublishedApi
internal fun createPieceByListIndex(listIndex: Int): Piece =
Piece(initialPieceIndex + listIndex)

fun getByAbsolutePieceIndex(pieceIndex: Int): Piece {
if (!containsAbsolutePieceIndex(pieceIndex)) {
throw IndexOutOfBoundsException(
"pieceIndex $pieceIndex out of bounds " +
"${initialPieceIndex}..${initialPieceIndex + sizes.size}",
)
}
return createPieceByListIndex(pieceIndex - initialPieceIndex)
}

// fun getByListIndex(listIndex: Int): Piece {
// if (!containsListIndex(listIndex)) {
// throw IndexOutOfBoundsException("listIndex $listIndex out of bounds")
// }
// return createPieceByListIndex(listIndex)
// }

suspend inline fun Piece.awaitFinished() {
val piece = this
TODO("Piece.awaitFinished")
// if (piece.state.value != PieceState.FINISHED) {
// piece.state.takeWhile { it != PieceState.FINISHED }.collect()
// }
}


class Subscription(
val pieceIndex: Int,
val onStateChange: PieceList.(Piece) -> Unit,
)

internal val subscriptions: MutableStateFlow<List<Subscription>> = MutableStateFlow(emptyList())

companion object {
fun create(
numPieces: Int,
initialDataOffset: Long = 0L,
initialPieceIndex: Int = 0,
getPieceSize: (index: Int) -> Long,
): PieceList {
val sizes = LongArray(numPieces) { getPieceSize(it) }
val offsets = LongArray(numPieces)
val states = Array(numPieces) { PieceState.READY }

var pieceOffset = initialDataOffset
for (i in 0 until numPieces) {
offsets[i] = pieceOffset
pieceOffset += sizes[i]
}

return PieceListImpl(sizes, offsets, states, initialPieceIndex)
}

fun create(
totalSize: Long,
pieceSize: Long,
initial: Long = 0L,
initialPieceIndex: Int = 0,
): PieceList {
if (totalSize % pieceSize == 0L) {
return create((totalSize / pieceSize).toInt(), initial, getPieceSize = { pieceSize })
}

val numPieces = (totalSize / pieceSize).toInt() + 1
val sizes = LongArray(numPieces) { pieceSize }
val offsets = LongArray(numPieces)
val states = Array(numPieces) { PieceState.READY }

var pieceOffset = initial
for (i in 0 until numPieces - 1) {
offsets[i] = pieceOffset
pieceOffset += sizes[i]
}

sizes[numPieces - 1] = totalSize % pieceSize
offsets[numPieces - 1] = totalSize - (totalSize % pieceSize) + initial

return PieceListImpl(sizes, offsets, states, initialPieceIndex)
}

}
}

// TODO: mark as TestOnly
fun PieceList.asSequence(): Sequence<Piece> = object : Sequence<Piece> {
override fun iterator(): Iterator<Piece> = object : Iterator<Piece> {
private var index = 0
override fun hasNext(): Boolean = index < sizes.size
override fun next(): Piece = createPieceByListIndex(index++)
}
}

//fun PieceList.containsListIndex(listIndex: Int): Boolean = listIndex in sizes.indices
fun PieceList.containsAbsolutePieceIndex(absolutePieceIndex: Int): Boolean =
absolutePieceIndex in initialPieceIndex until initialPieceIndex + sizes.size

fun PieceList.first(): Piece {
if (isEmpty()) throw NoSuchElementException()
return createPieceByListIndex(0)
}

fun PieceList.last(): Piece {
if (isEmpty()) throw NoSuchElementException()
return createPieceByListIndex(sizes.size - 1)
}

inline fun PieceList.forEach(block: PieceList.(Piece) -> Unit) {
for (i in sizes.indices) {
block(createPieceByListIndex(i))
}
}

inline fun PieceList.sumOf(block: PieceList.(Piece) -> Long): Long {
var sum = 0L
for (i in sizes.indices) {
sum += block(createPieceByListIndex(i))
}
return sum
}

inline fun PieceList.maxOf(block: PieceList.(Piece) -> Long): Long {
val sizes = sizes
if (sizes.isEmpty()) {
throw NoSuchElementException()
}
var max = Long.MIN_VALUE
for (i in sizes.indices) {
val value = block(createPieceByListIndex(i))
if (value > max) {
max = value
}
}
return max
}

inline fun PieceList.minOf(block: PieceList.(Piece) -> Long): Long {
val sizes = sizes
if (sizes.isEmpty()) {
throw NoSuchElementException()
}
var min = Long.MAX_VALUE
for (i in sizes.indices) {
val value = block(createPieceByListIndex(i))
if (value < min) {
min = value
}
}
return min
}

inline fun PieceList.indexOfFirst(predicate: PieceList.(Piece) -> Boolean): Int {
for (i in sizes.indices) {
if (predicate(createPieceByListIndex(i))) {
return i
}
}
return -1
}

inline fun PieceList.dropWhile(predicate: PieceList.(Piece) -> Boolean): List<Piece> {
val list = mutableListOf<Piece>()
var found = false
for (i in sizes.indices) {
if (!found && predicate(createPieceByListIndex(i))) {
continue
}
found = true
list.add(createPieceByListIndex(i))
}
return list
}

inline fun PieceList.takeWhile(predicate: PieceList.(Piece) -> Boolean): List<Piece> {
val list = mutableListOf<Piece>()
for (i in sizes.indices) {
if (predicate(createPieceByListIndex(i))) {
list.add(createPieceByListIndex(i))
} else {
break
}
}
return list
}

inline fun PieceList.binarySearch(predicate: PieceList.(Piece) -> Int): Int {
// TODO: check, this is written by Copilot
var low = 0
var high = sizes.size - 1
while (low <= high) {
val mid = (low + high).ushr(1)
val result = predicate(createPieceByListIndex(mid))
when {
result < 0 -> high = mid - 1
result > 0 -> low = mid + 1
else -> return mid
}
}
return -1
}
@RequiresOptIn(
"This is a raw constructor, don't use it",
level = RequiresOptIn.Level.ERROR,
)
annotation class RawPieceConstructor

enum class PieceState {
READY,
Expand Down
Loading

0 comments on commit bc27ea4

Please sign in to comment.