Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ jobs:
uses: "gradle/gradle-build-action@v2"
with:
arguments: "compileKotlin"
- name: "Run code"
uses: "gradle/gradle-build-action@v2"
with:
arguments: "run"
- name: "Run tests"
uses: "gradle/gradle-build-action@v2"
with:
Expand Down
59,877 changes: 59,877 additions & 0 deletions Notebook.ipynb

Large diffs are not rendered by default.

41 changes: 23 additions & 18 deletions src/main/java/com/hopskipnfall/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Client(
val incomingPackets = mutableListOf<DelayedPacket>()

/** When the current frame started. */
private var newFrameTimestamp: Duration = now
private var newFrameTimestamp: Duration = 0.milliseconds

private var frameNumber = 0L

Expand All @@ -32,12 +32,18 @@ class Client(

private var timeReceivedDataNecessaryForNextFrame: Duration? = null

fun run() {
fun run(
now: Duration,
frameNumberLogger: TimeBasedDataLogger,
diagramBuilder: DiagramBuilder,
objectiveLagLogger: TimeBasedDataLogger,
timeStep: Duration
) {
// This block ensures that all of the clients are synchronized with respect to the server.
// Different frame differences require different respective start times.
val minFrameDelay = min(frameDelay, siblings.minOfOrNull { it.frameDelay } ?: frameDelay)
val waitForFrames: Double = max((frameDelay - minFrameDelay).toDouble(), 0.0) / 2.0
if (now < singleFrameDuration * waitForFrames) {
if (now < SINGLE_FRAME_DURATION * waitForFrames) {
return
} else if (init) {
newFrameTimestamp = now
Expand All @@ -49,11 +55,11 @@ class Client(
// This is the first loop!

if (lastFrameDataFrameNumberSent == null && frameNumber >= firstFrameToSendFrom) {
sendPacketToServer()
sendPacketToServer(now, diagramBuilder)
}
}

if (now >= newFrameTimestamp + singleFrameDuration) {
if (now >= newFrameTimestamp + SINGLE_FRAME_DURATION) {
// We can send inputs to the server and/or move forward in time if we are ready to do so.

// Do we have the necessary data to move forward one frame?
Expand All @@ -65,7 +71,7 @@ class Client(
if (needSiblingsFrameData) {
val arrivedPackets =
incomingPackets.findAndRemoveAll {
it.hasArrived() && it.frameData.first().frameNumber == frameNumber + 1
it.hasArrived(now) && it.frameData.first().frameNumber == frameNumber + 1
}

if (arrivedPackets.isNotEmpty()) {
Expand All @@ -74,14 +80,14 @@ class Client(
) // THIS MIGHT NOT ACTUALLY BE IMPOSSIBLE FOR IT TO BE GREATER THAN 1
frameDataRequirementSatisfied = true
timeReceivedDataNecessaryForNextFrame = arrivedPackets.single().arrivalTime
log("Received packet: ${arrivedPackets.single()}.", debug = true)
log("Received packet: ${arrivedPackets.single()}.", debug = true, now)
}
} else {
frameDataRequirementSatisfied = true
}

if (frameDataRequirementSatisfied) {
log("moving to next frame", debug = true)
log("moving to next frame", debug = true, now)
frameNumber++
if (
timeReceivedDataNecessaryForNextFrame != null &&
Expand All @@ -101,12 +107,12 @@ class Client(
"Frame Number" to frameNumber,
"Client" to "Client $id" + if (description == null) "" else " ($description)",
"Objective lag in a single frame (ms)" to
max((now - newFrameTimestamp - singleFrameDuration).toMillisDouble(), 0.0)
max((now - newFrameTimestamp - SINGLE_FRAME_DURATION).toMillisDouble(), 0.0)
)
}

timeReceivedDataNecessaryForNextFrame = null
val lag = now - (newFrameTimestamp) - singleFrameDuration
val lag = now - (newFrameTimestamp) - SINGLE_FRAME_DURATION
if (lag <= timeStep) {
// No lag.
} else {
Expand All @@ -127,15 +133,14 @@ class Client(
frameNumber >= firstFrameToSendFrom &&
(lastFrameNumberSent == null || lastFrameNumberSent < frameNumber + frameDelay)
) {
sendPacketToServer()
sendPacketToServer(now, diagramBuilder)
}
}
}

val isHealthy: Boolean
get() = now - newFrameTimestamp < singleFrameDuration * 10
fun isHealthy(now: Duration): Boolean = now - newFrameTimestamp < SINGLE_FRAME_DURATION * 10

private fun sendPacketToServer() {
private fun sendPacketToServer(now: Duration, diagramBuilder: DiagramBuilder) {
val packet =
DelayedPacket(
arrivalTime = now + (pingRange.random() / 2),
Expand All @@ -144,11 +149,11 @@ class Client(
diagramBuilder.registerPacketToServer(now, client = id, packet)
server.incomingPackets += packet
lastFrameDataFrameNumberSent = packet.frameData.single().frameNumber
log("Sending to server: $packet", debug = true)
log("Sending to server: $packet", debug = true, now)
}

private fun log(s: String, debug: Boolean = false) {
logWithTime("Client $id (frame $frameNumber): $s", debug)
private fun log(s: String, debug: Boolean = false, now: Duration) {
logWithTime("Client $id (frame $frameNumber): $s", debug, now)
}

data class ClientPerceivedLag(
Expand All @@ -163,7 +168,7 @@ class Client(

/** Data the server tracks about the client. */
data class ServerData(
var lagLeeway: Duration = singleFrameDuration,
var lagLeeway: Duration = SINGLE_FRAME_DURATION,
var totalDrift: Duration = Duration.ZERO,
var receivedDataAt: Duration = Duration.ZERO,
)
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/hopskipnfall/DelayedPacket.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.hopskipnfall

import kotlin.time.Duration

/** Simulates a [FrameData] packet in flight. */
data class DelayedPacket(val arrivalTime: Duration, val frameData: List<FrameData>) {
fun hasArrived(now: Duration): Boolean = now >= arrivalTime
}
11 changes: 3 additions & 8 deletions src/main/java/com/hopskipnfall/DiagramBuilder.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package com.hopskipnfall

import com.github.nwillc.ksvg.RenderMode
import com.github.nwillc.ksvg.elements.SVG
import java.io.FileWriter
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.time.Duration

val diagramBuilder = DiagramBuilder()

// These helper functions prepare the number for SVG.
private fun Int.str() = this.toString()

Expand All @@ -20,7 +16,7 @@ class DiagramBuilder(private val heightPx: Int = 450, private val framesToDraw:
private val clientsDoneTracking = mutableSetOf<Int>()

private val leftMargin: Int =
((singleFrameDuration * 1.5).toMillisDouble() * PIXELS_PER_MILLISECOND).roundToInt()
((SINGLE_FRAME_DURATION * 1.5).toMillisDouble() * PIXELS_PER_MILLISECOND).roundToInt()
private val svgActions = mutableListOf<SVG.() -> Unit>()

private var maxX = 0
Expand Down Expand Up @@ -133,7 +129,7 @@ class DiagramBuilder(private val heightPx: Int = 450, private val framesToDraw:
}
}

fun draw() {
fun draw(): SVG {
val svg =
SVG.svg(true) {
style {
Expand Down Expand Up @@ -202,8 +198,7 @@ class DiagramBuilder(private val heightPx: Int = 450, private val framesToDraw:

for (it in svgActions) it()
}

FileWriter("diagram.svg").use { svg.render(it, RenderMode.FILE) }
return svg
}

private companion object {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/hopskipnfall/FrameData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.hopskipnfall

/** Frame data that needs to be synchronized between clients in order to move forward. */
data class FrameData(val frameNumber: Long, val fromClientId: Int)
110 changes: 0 additions & 110 deletions src/main/java/com/hopskipnfall/Main.kt

This file was deleted.

Loading
Loading