Skip to content

Commit 764f991

Browse files
committed
Fix PR comments
1 parent febd6d5 commit 764f991

File tree

6 files changed

+62
-43
lines changed

6 files changed

+62
-43
lines changed

workflow-trace-viewer/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ It can be run via Gradle using:
1212

1313
By Default, the app will be in file parsing mode, where you are able to select a previously recorded workflow trace file for it to visualize the data.
1414

15-
By hitting the bottom switch, you are able to toggle to live stream mode, where data is directly pulled from the emulator into the visualizer.
15+
By hitting the bottom switch, you are able to toggle to live stream mode, where data is directly pulled from the emulator into the visualizer. A connection can only happen once. If there needs to be rerecording of the trace, the emulator must first be restarted, and then the app must be restarted as well. This is due to the fact that any open socket will consume all render pass data, meaning there is nothing to read from the emulator.
1616

1717
It is ***important*** to run the emulator first before toggling to live mode.
1818

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/App.kt

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ internal fun App(
4242
var selectedTraceFile by remember { mutableStateOf<PlatformFile?>(null) }
4343
val socket = remember { SocketClient() }
4444

45-
Runtime.getRuntime().addShutdownHook(
46-
Thread {
47-
socket.close()
48-
}
49-
)
50-
5145
LaunchedEffect(sandboxState) {
5246
snapshotFlow { frameIndex }.collect {
5347
sandboxState.reset()
@@ -57,7 +51,7 @@ internal fun App(
5751
Box(
5852
modifier = modifier
5953
) {
60-
fun resetStates() = run {
54+
fun resetStates() {
6155
socket.close()
6256
selectedTraceFile = null
6357
selectedNode = null
@@ -112,7 +106,7 @@ internal fun App(
112106
frames get populated, so we avoid off by one when indexing into the frames.
113107
*/
114108
frameIndex = -1
115-
socket.start()
109+
socket.open()
116110
TraceMode.Live(socket)
117111
}
118112
},
@@ -140,11 +134,10 @@ internal class SandboxState {
140134

141135
fun reset() {
142136
offset = Offset.Zero
143-
// scale = 1f
144137
}
145138
}
146139

147140
internal sealed interface TraceMode {
148141
data class File(val file: PlatformFile?) : TraceMode
149-
data class Live(val socket: SocketClient = SocketClient()) : TraceMode
142+
data class Live(val socket: SocketClient) : TraceMode
150143
}

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/Main.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ fun main() {
1414
.start().waitFor()
1515
}
1616
)
17-
singleWindowApplication(title = "Workflow Trace Viewer") {
17+
singleWindowApplication(title = "Workflow Trace Viewer", exitProcessOnExit = false) {
1818
App(Modifier.fillMaxSize())
1919
}
2020
}

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/FrameSelectTab.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ internal fun FrameSelectTab(
3232
modifier: Modifier = Modifier
3333
) {
3434
val lazyListState = rememberLazyListState()
35-
LaunchedEffect(currentIndex) {
36-
if (currentIndex < 0) return@LaunchedEffect
37-
lazyListState.animateScrollToItem(currentIndex)
35+
if (currentIndex >= 0) {
36+
LaunchedEffect(currentIndex) {
37+
lazyListState.animateScrollToItem(currentIndex)
38+
}
3839
}
3940

4041
Surface(

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowTree.kt

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ import com.squareup.workflow1.traceviewer.util.createMoshiAdapter
3434
import com.squareup.workflow1.traceviewer.util.parseFileTrace
3535
import com.squareup.workflow1.traceviewer.util.parseLiveTrace
3636
import kotlinx.coroutines.Dispatchers
37+
import kotlinx.coroutines.launch
3738
import kotlinx.coroutines.withContext
39+
import java.net.SocketException
3840

3941
/**
4042
* Access point for drawing the main content of the app. It will load the trace for given files and
@@ -71,11 +73,10 @@ internal fun RenderTrace(
7173
fun handleParseResult(
7274
parseResult: ParseResult,
7375
onNewFrame: (() -> Unit)? = null
74-
): Boolean {
75-
return when (parseResult) {
76+
) {
77+
when (parseResult) {
7678
is ParseResult.Failure -> {
7779
error = parseResult.error
78-
false
7980
}
8081
is ParseResult.Success -> {
8182
addToStates(
@@ -84,28 +85,39 @@ internal fun RenderTrace(
8485
affected = parseResult.affectedNodes
8586
)
8687
onNewFrame?.invoke()
87-
true
8888
}
8989
}
9090
}
9191

9292
LaunchedEffect(traceSource) {
9393
when (traceSource) {
9494
is TraceMode.File -> {
95-
// We guarantee the file is null since this composable can only be called when a file is selected.
96-
val parseResult = parseFileTrace(traceSource.file!!)
95+
checkNotNull(traceSource.file){
96+
"TraceMode.File should have a non-null file to parse."
97+
}
98+
val parseResult = parseFileTrace(traceSource.file)
9799
handleParseResult(parseResult)
98100
}
99101

100102
is TraceMode.Live -> {
101103
val socket = traceSource.socket
102-
socket.beginListen(this)
104+
launch {
105+
try {
106+
socket.pollSocket()
107+
} catch (e: SocketException) {
108+
error = SocketException("Socket has already been closed or is not available: ${e.message}")
109+
return@launch
110+
}
111+
}
112+
if (error != null) {
113+
return@LaunchedEffect
114+
}
103115
val adapter: JsonAdapter<List<Node>> = createMoshiAdapter<Node>()
104116

105-
withContext(Dispatchers.IO) {
117+
withContext(Dispatchers.Default) {
106118
// Since channel implements ChannelIterator, we can for-loop through on the receiver end.
107119
for (renderPass in socket.renderPassChannel) {
108-
val currentTree = if (fullTree.isEmpty()) null else fullTree.last()
120+
val currentTree = fullTree.lastOrNull()
109121
val parseResult = parseLiveTrace(renderPass, adapter, currentTree)
110122
handleParseResult(parseResult, onNewFrame)
111123
}

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/util/SocketClient.kt

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,28 @@ package com.squareup.workflow1.traceviewer.util
22

33
import kotlinx.coroutines.CoroutineScope
44
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.Job
56
import kotlinx.coroutines.channels.Channel
67
import kotlinx.coroutines.launch
8+
import kotlinx.coroutines.withContext
9+
import kotlinx.coroutines.delay
10+
import kotlinx.coroutines.yield
11+
712
import java.net.Socket
813
import java.net.SocketException
914

1015
/**
11-
* This is a client that connects to the `ActionLogger` Unix Domain Socket and listens for any new
12-
* render passes. Since this app is on JVM and the server is on Android, we use ADB to forward the
13-
* port onto the socket.
16+
* This is a client that can connect to any server socket that sends render pass data while using
17+
* the Workflow framework.
18+
*
19+
* [start] and [close] are idempotent commands, so this socket can only be started and closed once.
20+
*
21+
* Since this app is on JVM and the server is on Android, we use ADB to forward the port onto the socket.
1422
*/
1523
internal class SocketClient {
1624
private lateinit var socket: Socket
1725
private var initialized = false
18-
val renderPassChannel: Channel<String> = Channel(Channel.UNLIMITED)
26+
val renderPassChannel: Channel<String> = Channel(Channel.BUFFERED)
1927

2028
/**
2129
* We use any available ports on the host machine to connect to the emulator.
@@ -24,18 +32,20 @@ internal class SocketClient {
2432
* `LocalServerSocket` -- which creates a unix socket on the linux abstract namespace -- we use
2533
* `localabstract:` to connect to it.
2634
*/
27-
fun start() {
35+
fun open() {
36+
if (initialized){
37+
return
38+
}
2839
initialized = true
2940
val process = ProcessBuilder(
3041
"adb", "forward", "tcp:0", "localabstract:workflow-trace"
3142
).start()
3243

3344
// The adb forward command will output the port number it picks to connect.
34-
val port = run {
35-
process.waitFor()
36-
process.inputStream.bufferedReader().readText()
45+
process.waitFor()
46+
val port = process.inputStream.bufferedReader().readText()
3747
.trim().toInt()
38-
}
48+
3949
socket = Socket("localhost", port)
4050
}
4151

@@ -47,22 +57,25 @@ internal class SocketClient {
4757
}
4858

4959
/**
50-
* This will always be called within an asynchronous call, so we do not need to block/launch a
51-
* new coroutine here.
60+
* Polls the socket's input stream and sends the data into [renderPassChannel].
61+
* The caller should handle the scope of the coroutine that this function is called in.
5262
*
5363
* To better separate the responsibility of reading from the socket, we use a channel for the caller
5464
* to handle parsing and amalgamating the render passes.
5565
*/
56-
fun beginListen(scope: CoroutineScope) {
57-
scope.launch(Dispatchers.IO) {
66+
suspend fun pollSocket() {
67+
withContext(Dispatchers.IO) {
5868
val reader = socket.getInputStream().bufferedReader()
59-
try {
60-
while (true) {
61-
val input = reader.readLine()
62-
renderPassChannel.trySend(input)
69+
reader.use {
70+
try {
71+
while (true) {
72+
val input = reader.readLine()
73+
renderPassChannel.trySend(input)
74+
println(input)
75+
}
76+
} catch (e: SocketException) {
77+
e.printStackTrace()
6378
}
64-
} catch (e: SocketException) {
65-
println("Exiting socket listener due to: ${e.message}")
6679
}
6780
}
6881
}

0 commit comments

Comments
 (0)