Skip to content

Commit

Permalink
Rework finalization to make it only in emulation and export modes
Browse files Browse the repository at this point in the history
  • Loading branch information
kunyavskiy committed Sep 3, 2023
1 parent 90bd7c1 commit 39b3466
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 43 deletions.
6 changes: 3 additions & 3 deletions src/cds-converter/src/main/kotlin/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,16 @@ object PCMSDumpCommand : CliktCommand(name = "pcms", help = "Dump pcms xml", pri
val flow = getFlow(
fileJsonContentFlow<AdvancedProperties>(CommonOptions.advancedJsonPath, logger, AdvancedProperties()),
logger
).stateGroupedByTeam()
)
val data = runBlocking {
logger.info("Waiting till contest become finalized...")
val result = flow.first { it.infoAfterEvent?.status == ContestStatus.FINALIZED }
val result = flow.finalContestState()
logger.info("Loaded contest data")
result
}
val dump = PCMSExporter.format(
data.infoAfterEvent!!,
data.runs,
data.runs.values.groupBy { it.teamId },
)
output.toFile().printWriter().use {
it.println(dump)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ object PCMSExporter {
setAttribute("id", teamInfo.contestSystemId)
// setAttribute("time", "")
setAttribute("alias", teamInfo.contestSystemId)
setAttribute("penalty", row.penalty.toString())
setAttribute("penalty", row.penalty.inWholeMinutes.toString())
setAttribute("solved", row.totalScore.toInt().toString())
val runsByProblem = runs.groupBy { it.problemId }
row.problemResults.forEachIndexed { index, probResult ->
Expand Down
8 changes: 4 additions & 4 deletions src/cds/src/main/kotlin/org/icpclive/api/ContestInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package org.icpclive.api

import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.*
import org.icpclive.util.*
import java.awt.Color
import kotlin.time.Duration
Expand Down Expand Up @@ -257,13 +256,14 @@ data class ContestInfo(
val emulationSpeed: Double = 1.0,
val medals: List<MedalType> = emptyList(),
val penaltyPerWrongAttempt: Duration = 20.minutes,
@Transient
val cdsSupportsFinalization: Boolean = false,
) {
val currentContestTime
get() = when (status) {
ContestStatus.BEFORE -> Duration.ZERO
ContestStatus.RUNNING -> (Clock.System.now() - startTime) * emulationSpeed
ContestStatus.OVER -> contestLength
ContestStatus.FINALIZED -> contestLength
ContestStatus.OVER, ContestStatus.FINALIZED -> contestLength
}
val groups by lazy { groupList.associateBy { it.name } }
val teams by lazy { teamList.associateBy { it.id } }
Expand Down
24 changes: 24 additions & 0 deletions src/cds/src/main/kotlin/org/icpclive/cds/adapters/AutoFinalize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.icpclive.cds.adapters

import kotlinx.coroutines.flow.*
import org.icpclive.api.ContestStatus
import org.icpclive.cds.ContestUpdate
import org.icpclive.cds.InfoUpdate
import org.icpclive.util.getLogger

internal object AutoFinalize

fun Flow<ContestUpdate>.autoFinalize() = withGroupedRuns({ it.result != null })
.transformWhile {
emit(it.event)
val info = it.infoAfterEvent
if (info?.status == ContestStatus.OVER && !info.cdsSupportsFinalization && it.runs[false].isNullOrEmpty()) {
emit(InfoUpdate(it.infoAfterEvent!!.copy(status = ContestStatus.FINALIZED)))
getLogger(AutoFinalize::class).info("Contest finished. Finalizing.")
false
} else {
true
}
}

suspend fun Flow<ContestUpdate>.finalContestState() = autoFinalize().contestState().first { it.infoAfterEvent?.status == ContestStatus.FINALIZED }
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ private val logger = getLogger(EmulationAdapter::class)

internal fun Flow<ContestUpdate>.toEmulationFlow(startTime: Instant, emulationSpeed: Double) = flow {
val scope = CoroutineScope(currentCoroutineContext())
val stateFlow = contestState().stateIn(scope)
scope.launch {
delay(1.seconds)
while (stateFlow.value.infoAfterEvent?.status != ContestStatus.FINALIZED) {
logger.info("Waiting for contest to become Finalized to start emulation...")
val logJob = scope.launch {
while (true) {
delay(1.seconds)
logger.info("Waiting for contest to become Finalized to start emulation...")
}
}
val state = stateFlow.first { it.infoAfterEvent?.status == ContestStatus.FINALIZED }
val state = finalContestState()
logJob.cancel()
val finalContestInfo = state.infoAfterEvent!!
val runs = state.runs.values.toList()
val analyticsMessages = state.analyticsMessages.values.toList()
Expand Down
3 changes: 2 additions & 1 deletion src/cds/src/main/kotlin/org/icpclive/cds/clics/ClicsModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ internal class ClicsModel(
penaltyPerWrongAttempt = penaltyPerWrongAttempt,
holdBeforeStartTime = holdBeforeStartTime,
penaltyRoundingMode = PenaltyRoundingMode.EACH_SUBMISSION_DOWN_TO_MINUTE,
organizationList = organisations.values.map { it.toApi() }
organizationList = organisations.values.map { it.toApi() },
cdsSupportsFinalization = true
)

fun processContest(contest: Contest): List<RunInfo> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import kotlin.time.Duration
internal abstract class FullReloadContestDataSource(val interval: Duration) : ContestDataSource {
abstract suspend fun loadOnce(): ContestParseResult

open val autoFinalize = true
private fun ContestParseResult.isAutoFinal() =
contestInfo.status == ContestStatus.OVER && runs.all { it.result != null }
var isOver = false

override fun getFlow() = loopFlow(
interval,
Expand All @@ -22,21 +20,17 @@ internal abstract class FullReloadContestDataSource(val interval: Duration) : Co
loadOnce()
}.flowOn(Dispatchers.IO)
.conflate()
.transformWhile {
val isFinal = it.contestInfo.status == ContestStatus.FINALIZED || (autoFinalize && it.isAutoFinal())
if (isFinal) {
getLogger(FullReloadContestDataSource::class).info("Finalizing contest")
emit(InfoUpdate(it.contestInfo.copy(status = ContestStatus.OVER)))
.transform {
if (!isOver && it.contestInfo.status == ContestStatus.OVER) {
emit(InfoUpdate(it.contestInfo.copy(status = ContestStatus.RUNNING)))
} else {
emit(InfoUpdate(it.contestInfo))
}
it.runs.forEach { run -> emit(RunUpdate(run)) }
it.analyticsMessages.forEach { msg -> emit(AnalyticsUpdate(msg)) }
if (isFinal) {
emit(InfoUpdate(it.contestInfo.copy(status = ContestStatus.FINALIZED)))
false
} else {
true
if (!isOver && it.contestInfo.status == ContestStatus.OVER) {
isOver = true
emit(InfoUpdate(it.contestInfo))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,7 @@ internal class YandexDataSource(settings: YandexSettings, creds: Map<String, Str
}.flatMapConcat { it.asFlow() }
emitAll(merge(allRunsFlow, rawContestInfoFlow.map { InfoUpdate(it.toApi()) }))
}
}.withGroupedRuns({ it.result != null })
.transformWhile {
emit(it.event)
if (it.infoAfterEvent?.status == ContestStatus.OVER && it.runs[false].isNullOrEmpty()) {
emit(InfoUpdate(it.infoAfterEvent!!.copy(status = ContestStatus.FINALIZED)))
log.info("Contest finished. Finalizing.")
false
} else {
true
}
}
}

companion object {
private val log = getLogger(YandexDataSource::class)
Expand Down
6 changes: 2 additions & 4 deletions src/cds/src/test/kotlin/org/icpclive/cds/CdsLoadersTest.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package org.icpclive.cds

import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.approvaltests.Approvals
import org.approvaltests.core.Options
import org.icpclive.api.ContestResultType
import org.icpclive.api.ContestStatus
import org.icpclive.api.tunning.*
import org.icpclive.cds.adapters.applyAdvancedProperties
import org.icpclive.cds.adapters.contestState
import org.icpclive.cds.adapters.finalContestState
import org.icpclive.cds.clics.FeedVersion
import org.icpclive.cds.common.ContestParseResult
import org.icpclive.cds.settings.*
Expand Down Expand Up @@ -106,7 +104,7 @@ class CdsLoadersTest {
val loader = args.toFlow(emptyMap())
val result = runBlocking {
val result = withTimeout(1.minutes) {
loader.contestState().first { it.infoAfterEvent?.status == ContestStatus.FINALIZED }.let {
loader.finalContestState().let {
ContestParseResult(
it.infoAfterEvent!!,
it.runs.values.toList(),
Expand Down

0 comments on commit 39b3466

Please sign in to comment.