简体   繁体   English

如何跳过流并仅发送最后发出的值

[英]How to skip a Flow and just send last emitted value

I am working on a web application to visualize maze generation and solving algorithms based on Websockets.我正在开发一个 web 应用程序,以可视化基于 Websockets 的迷宫生成和求解算法。 The generation algorithms implement the interface IMazeGenerator and return a Flow.生成算法实现接口 IMazeGenerator 并返回一个 Flow。

interface IMazeGenerator {

    fun generate(maze: Maze): Flow<Maze>

}

After each step in the generator algorithm, the updated Maze is emitted.在生成器算法的每一步之后,都会发出更新的迷宫。 The collector then sends the intermediate steps to the client after a short delay.然后,收集器会在短暂延迟后将中间步骤发送给客户端。

generatorFlow
            .map { it.toDto() }
            .delay(150)
            .onEach {
                client.send(UpdateMaze(it))
            }
            .launchIn(this.scope)

And now to my problem: I want to offer the client the possibility to skip the intermediate steps and then send only the last result, ie the final Maze.现在我的问题是:我想为客户提供跳过中间步骤的可能性,然后只发送最后一个结果,即最终的迷宫。 To do this, I first need to be able to determine if the emitter is already done (ie the final maze was already emitted) and if so, send only the last result when a skip command comes.为此,我首先需要能够确定发射器是否已经完成(即最终迷宫已经发射),如果是,则在跳过命令到来时仅发送最后一个结果。

Unfortunately I don't know at all at this point, I'm not even sure if this works with flows.不幸的是,我现在根本不知道,我什至不确定这是否适用于流程。 Any help is welcome.欢迎任何帮助。

You can create an extension function that collects the flow and returns the last element.您可以创建一个扩展 function 来收集流并返回最后一个元素。 Be aware that it would suspend indefinitely if the input flow is infinite.请注意,如果输入流是无限的,它将无限期暂停。

Something like this:像这样的东西:

suspend fun <T> Flow<T>.lastOrNull(): T? {
    var elem: T? = null
    collect { elem = it }
    return elem
}

Now the client can just do val finalMaze = mazeSteps.lastOrNull() to get the final maze.现在客户端只需执行val finalMaze = mazeSteps.lastOrNull()即可获得最终迷宫。

If you want to transform the input flow to a flow that only emits the final maze, you can just do this:如果您想将输入流转换为只发出最终迷宫的流,您可以这样做:

fun <T> Flow<T>.skipUntilLast(): Flow<T> = flow { 
    val last = lastOrNull()
    if (last != null) emit(last)
}

Thank you marstran, unfortunately this has not yet worked directly, but you have given me the necessary inspiration.谢谢marstran,不幸的是这还没有直接起作用,但你给了我必要的灵感。 Let me first explain the problem, then my solution.让我先解释问题,然后是我的解决方案。

The flow is started as soon as a generation is triggered.一旦触发生成,流程就会启动。 If the client wants to skip all further intermediate steps after a certain point, I need to transmit the last emission from exactly this flow.如果客户端想在某个点之后跳过所有进一步的中间步骤,我需要从这个流中传输最后一个发射。 With your solution, the problem was that I would restart the flow in this case, and get a different result, because some of the algorithms are non deterministic.使用您的解决方案,问题是在这种情况下我会重新启动流程,并获得不同的结果,因为某些算法是不确定的。 Another problem was that I first have to make sure if the emitter has already calculated the last result, since some algorithms do take some time.另一个问题是我首先必须确定发射器是否已经计算了最后一个结果,因为某些算法确实需要一些时间。 At this point, the flow would be skippable.此时,流程将是可跳过的。 Therefore I also had to use a buffer to omit the backpressure.因此,我还必须使用缓冲区来忽略背压。

I now created an extension function which receives a callback function on the last emitted value.我现在创建了一个扩展 function,它在最后一个发出的值上接收一个回调 function。

fun <T> Flow<T>.finalValueCallback(block: suspend (T?) -> Unit) = flow {
    var finalValue: T? = null
    collect {
        emit(it)
        finalValue = it
    }
    block(finalValue)
}

I then used this callback to save the last result from the current flow.然后我使用这个回调来保存当前流的最后一个结果。 If a skip command comes in, I interrupt the flow and send only the last result.如果出现跳过命令,我会中断流程并仅发送最后一个结果。

generatorJob = generatorFlow
            .onStart { client.send(UpdateGeneratorState(GeneratorState.RUNNING)) }
            .map { it.toDto() }
            .finalValueCallback {
                finalMaze = it
                client.send(UpdateGeneratorState(GeneratorState.SKIPPABLE))
            }
            .buffer()
            .delay(150)
            .onEach {
                client.send(UpdateMaze(it))
            }
            .onCompletion { cause ->
                finalValue = null
                if (cause == null || cause is FlowSkippedException) {
                    client.send(UpdateGeneratorState(GeneratorState.COMPLETED))
                } else {
                    client.send(UpdateGeneratorState(GeneratorState.CANCELLED))
                }
            }
            .launchIn(this.scope)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM