Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollback Example #78

Draft
wants to merge 89 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
f05a3a6
drop down the socket interval
Giesch Oct 27, 2024
5393f9d
bring in the forgiving justfile from main
Giesch Oct 27, 2024
9b96565
convert to fixed timestep
Giesch Oct 27, 2024
8e1385a
WIP splitting gamestate before/after connection
Giesch Oct 27, 2024
56ba6cd
move rollback to it's own example
Giesch Oct 28, 2024
fbda290
Merge remote-tracking branch 'upstream/main' into ROLLBACK_NETCODE
Giesch Oct 28, 2024
fe2b708
Merge remote-tracking branch 'upstream/purity-inference' into ROLLBAC…
Giesch Oct 28, 2024
e5dcc27
move to purity inference
Giesch Oct 28, 2024
9e420ad
tweaks
Giesch Oct 28, 2024
1a1962c
fix animation
Giesch Oct 28, 2024
7b53f53
tweaking peer updates
Giesch Oct 28, 2024
ccf57a0
wire up publishing inputs
Giesch Oct 29, 2024
d5af47d
rolls forward and back on first misprediction
Giesch Oct 29, 2024
4f893e8
fix using wrong input in roll forward
Giesch Oct 29, 2024
887a839
it kinda works
Giesch Oct 30, 2024
bab94c6
avoid sending position at all
Giesch Oct 30, 2024
695a61e
convert millisPerTick to int
Giesch Oct 30, 2024
595d79a
use correct snapshots in roll forward
Giesch Oct 30, 2024
abbd190
fiddling
Giesch Oct 30, 2024
cf041a5
remove non-animation floats
Giesch Oct 30, 2024
df80d72
still kinda broken but a little less
Giesch Nov 2, 2024
7fe9300
separate input type, test stub
Giesch Nov 2, 2024
c172300
tweaks, simplifications, bugfixes
Giesch Nov 2, 2024
03e2fb6
eat fewer inputs
Giesch Nov 2, 2024
dbf1e8c
fix some bugs
Giesch Nov 2, 2024
5717204
add a hacky checksum
Giesch Nov 3, 2024
b2c9a82
diffable crash logs on desyncs
Giesch Nov 3, 2024
985d381
logs, tests, fix off-by-one in send
Giesch Nov 3, 2024
3e4c3d3
resolve some todos
Giesch Nov 4, 2024
249515e
Merge remote-tracking branch 'upstream/purity-inference' into ROLLBAC…
Giesch Nov 4, 2024
f07efd8
fix bug in sync tick checksum debug assertion
Giesch Nov 5, 2024
3642ce0
resolve some todos, start splitting out a module
Giesch Nov 5, 2024
4ce3e45
add a Rollback ability
Giesch Nov 5, 2024
c8386b3
before ability refactor
Giesch Nov 6, 2024
49e28af
trying function-passing config api
Giesch Nov 6, 2024
97d6ae5
fiddling
Giesch Nov 6, 2024
ece1bc8
module split
Giesch Nov 6, 2024
dad0e1b
hold on to the most recent remote sync tick
Giesch Nov 6, 2024
1a2bee5
Merge branch 'ROLLBACK_NETCODE' into ROLLBACK_NETCODE-merge-main
Giesch Nov 6, 2024
6c7c02b
fix init error type
Giesch Nov 6, 2024
4b83165
rename module to Rollback, add NonEmptyRingBuffer
Giesch Nov 7, 2024
bfe3dda
remove unique & merge from cleanAndSortInputs
Giesch Nov 7, 2024
666df31
more input buffer
Giesch Nov 8, 2024
22cacfc
finish module split
Giesch Nov 8, 2024
dba6788
GameState -> World
Giesch Nov 8, 2024
fbf3e5a
remove InputBuffer
Giesch Nov 8, 2024
7f504f9
merge player types
Giesch Nov 8, 2024
09be428
Merge branch 'ROLLBACK_NETCODE' into ROLLBACK_NETCODE-merge-main
Giesch Nov 8, 2024
a34d364
Merge remote-tracking branch 'upstream/purity-inference' into ROLLBAC…
Giesch Nov 8, 2024
4a7732e
add Network.configure
Giesch Nov 8, 2024
7679fd6
add logging for decode errors
Giesch Nov 8, 2024
336b1eb
Merge remote-tracking branch 'upstream/main' into ROLLBACK_NETCODE-me…
Giesch Nov 8, 2024
4789cae
docs and todo cleanup
Giesch Nov 9, 2024
e9db9f6
add nonempty list type that crashes
Giesch Nov 9, 2024
8abe849
fix not writing snapshots to non-empty
Giesch Nov 10, 2024
f50a754
handle roc panic more gracefully
Giesch Nov 10, 2024
2eab066
use non-empty list for remote messages
Giesch Nov 10, 2024
0887b42
use non-empty messages instead of remoteInputTicks
Giesch Nov 10, 2024
bb78caf
remove remoteInputTicks
Giesch Nov 10, 2024
9f5f975
remove localInputTicks
Giesch Nov 10, 2024
9b30a22
make tickOnce explicitly rollback-aware
Giesch Nov 10, 2024
db8b246
docs & cleanup
Giesch Nov 10, 2024
3f46d2b
more comments
Giesch Nov 10, 2024
34184be
add tcp caveat
Giesch Nov 10, 2024
9863501
tweaks
Giesch Nov 10, 2024
5e461ec
bump max rollback and hold on to messages longer
Giesch Nov 10, 2024
f710fa4
fancy new checksum algo plz no steal
Giesch Nov 10, 2024
2c7c397
remove assertion & bump prediction allowance again
Giesch Nov 10, 2024
a7a2e66
bump blocked world values
Giesch Nov 10, 2024
51dd0fd
10x config again
Giesch Nov 10, 2024
239baaf
hold more inputs based on config
Giesch Nov 10, 2024
857364d
use signed ints
Giesch Nov 10, 2024
6897adc
move towards handling unorderd messages
Giesch Nov 10, 2024
efe84e8
make it easier to swap out the base url
Giesch Nov 11, 2024
6ea7481
config module
Giesch Nov 11, 2024
3a957bc
remove unused arg from Recording.start
Giesch Nov 11, 2024
8f582e2
display a message instead of crashing on desync
Giesch Nov 11, 2024
0d106ac
better message for missing checksum
Giesch Nov 11, 2024
97b4d94
hold on to first desync info
Giesch Nov 11, 2024
616a98d
add NonEmptyList.walkUntil
Giesch Nov 11, 2024
16d083c
make size of inputs sent configurable
Giesch Nov 11, 2024
6a6f4c2
remove old outgoingMessage
Giesch Nov 11, 2024
06ab466
add a byte encoding for Input
Giesch Nov 11, 2024
152e98f
remove optional argument from Pixel.fromParts
Giesch Nov 11, 2024
c1b0748
switch to unreliable channel
Giesch Nov 12, 2024
8b5e4b2
add fly-deployed matchbox url
Giesch Nov 12, 2024
76c9242
use latest json package
Giesch Nov 23, 2024
228fd1c
Merge remote-tracking branch 'upstream/main' into ROLLBACK_NETCODE
Giesch Dec 1, 2024
3f62bdb
Merge remote-tracking branch 'upstream/main' into ROLLBACK_NETCODE
Giesch Dec 3, 2024
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
14 changes: 14 additions & 0 deletions examples/rollback/Config.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module [baseUrl, rollback]

import Rollback

baseUrl : Str
baseUrl = "wss://matchbox-10.fly.dev"

rollback : Rollback.Config
rollback = {
millisPerTick: 1000 // 120,
maxRollbackTicks: 16,
tickAdvantageLimit: 10,
sendMostRecent: 20,
}
82 changes: 82 additions & 0 deletions examples/rollback/Input.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
module [Input, read, blank, toByte, fromByte]

import rr.Keys exposing [Keys]

Input : {
up : [Up, Down],
down : [Up, Down],
left : [Up, Down],
right : [Up, Down],
}

read : Keys -> Input
read = \keys ->
up = if Keys.anyDown keys [KeyUp, KeyW] then Down else Up
down = if Keys.anyDown keys [KeyDown, KeyS] then Down else Up
left = if Keys.anyDown keys [KeyLeft, KeyA] then Down else Up
right = if Keys.anyDown keys [KeyRight, KeyD] then Down else Up

{ up, down, left, right }

blank : Input
blank =
{ up: Up, down: Up, left: Up, right: Up }

upMask : U8
upMask = Num.shiftLeftBy 1 0

downMask : U8
downMask = Num.shiftLeftBy 1 1

leftMask : U8
leftMask = Num.shiftLeftBy 1 2

rightMask : U8
rightMask = Num.shiftLeftBy 1 3

toByte : Input -> U8
toByte = \input ->
merge = \previous, direction, mask ->
when direction input is
Up -> previous
Down -> Num.bitwiseOr previous mask

0
|> merge .up upMask
|> merge .down downMask
|> merge .left leftMask
|> merge .right rightMask

fromByte : U8 -> Input
fromByte = \byte ->
lookup = \mask ->
if (Num.bitwiseAnd mask byte) != 0 then
Down
else
Up

{
up: lookup upMask,
down: lookup downMask,
left: lookup leftMask,
right: lookup rightMask,
}

expect
cycled = blank |> toByte |> fromByte
cycled == blank

expect
justUp = { Input.blank & up: Down }
cycled = justUp |> toByte |> fromByte
cycled == justUp

expect
upLeft = { Input.blank & up: Down, left: Down }
cycled = upLeft |> toByte |> fromByte
cycled == upLeft

expect
allDown = { up: Down, left: Down, right: Down, down: Down }
cycled = allDown |> toByte |> fromByte
cycled == allDown
146 changes: 146 additions & 0 deletions examples/rollback/NonEmptyList.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
module [
NonEmptyList,
new,
first,
last,
findLast,
findFirst,
map,
toList,
dropNonLastIf,
append,
appendAll,
updateNonLast,
sortWith,
fromList,
walkUntil,
]

## A List guaranteed to contain at least one item
NonEmptyList a := Inner a

Inner a : {
list : List a,
last : a,
}

new : a -> NonEmptyList a
new = \item ->
@NonEmptyList { list: [], last: item }

first : NonEmptyList a -> a
first = \@NonEmptyList inner ->
when List.first inner.list is
Ok f -> f
Err ListWasEmpty -> inner.last

last : NonEmptyList a -> a
last = \@NonEmptyList inner ->
inner.last

findLast : NonEmptyList a, (a -> Bool) -> Result a [NotFound]
findLast = \@NonEmptyList inner, isMatch ->
if isMatch inner.last then
Ok inner.last
else
List.findLast inner.list isMatch

findFirst : NonEmptyList a, (a -> Bool) -> Result a [NotFound]
findFirst = \@NonEmptyList inner, isMatch ->
when List.findFirst inner.list isMatch is
Ok found -> Ok found
Err NotFound ->
if isMatch inner.last then
Ok inner.last
else
Err NotFound

map : NonEmptyList a, (a -> b) -> NonEmptyList b
map = \@NonEmptyList inner, transform ->
newList = List.map inner.list transform
newLast = transform inner.last
@NonEmptyList { list: newList, last: newLast }

toList : NonEmptyList a -> List a
toList = \@NonEmptyList inner ->
inner.list
|> List.append inner.last

fromList : List a -> Result (NonEmptyList a) [ListWasEmpty]
fromList = \original ->
when List.last original is
Err ListWasEmpty -> Err ListWasEmpty
Ok lastItem ->
nonLast = List.dropLast original 1
Ok (@NonEmptyList { list: nonLast, last: lastItem })

## Drop items from the list matching a predicate
## But keep the last item in the list regardless of whether it passes
dropNonLastIf : NonEmptyList a, (a -> Bool) -> NonEmptyList a
dropNonLastIf = \@NonEmptyList inner, shouldDrop ->
list = List.dropIf inner.list shouldDrop
@NonEmptyList { inner & list }

updateNonLast : NonEmptyList a, (List a -> List a) -> NonEmptyList a
updateNonLast = \@NonEmptyList inner, update ->
list = update inner.list
@NonEmptyList { inner & list }

append : NonEmptyList a, a -> NonEmptyList a
append = \@NonEmptyList inner, item ->
@NonEmptyList {
last: item,
list: List.append inner.list inner.last,
}

appendAll : NonEmptyList a, List a -> NonEmptyList a
appendAll = \@NonEmptyList inner, items ->
when List.last items is
Err ListWasEmpty -> @NonEmptyList inner
Ok newLast ->
newNonLast = List.takeFirst items (List.len items - 1)
@NonEmptyList {
last: newLast,
list: List.concat inner.list newNonLast,
}

sortWith : NonEmptyList a, (a, a -> [LT, EQ, GT]) -> NonEmptyList a
sortWith = \nonEmpty, compare ->
fullList = toList nonEmpty
sorted = List.sortWith fullList compare

nonLast = List.dropLast sorted 1
lastItem =
when List.last sorted is
Err _ -> crash "unreachable"
Ok item -> item

@NonEmptyList { list: nonLast, last: lastItem }

Step state : [Break state, Continue state]

walkUntil : NonEmptyList item, (item -> Step state), (state, item -> Step state) -> state
walkUntil = \nonEmpty, fromFirst, step ->
list = toList nonEmpty

WrappedState s : [Empty, SeenOne s]

wrappedInitial : WrappedState state
wrappedInitial = Empty

walked =
List.walkUntil list wrappedInitial \wrappedState, item ->
when wrappedState is
Empty ->
when fromFirst item is
Break s -> Break (SeenOne s)
Continue s -> Continue (SeenOne s)

SeenOne state ->
when step state item is
Break s -> Break (SeenOne s)
Continue s -> Continue (SeenOne s)

when walked is
Empty -> crash "unreachable"
SeenOne state -> state
80 changes: 80 additions & 0 deletions examples/rollback/Pixel.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module [
Pixel,
PixelVec,
add,
sub,
toVector2,
subpixelsPerPixel,
fromParts,
totalSubpixels,
fromPixels,
]

import rr.RocRay exposing [Vector2]

# A 1-D integer position with a integer sub-pixel component
# Use instead of floats to avoid rounding errors
Pixel := { pixels : I64, subpixels : I64 }
implements [
Eq,
Hash,
Inspect { toInspector: pixelInspector },
]

PixelVec : { x : Pixel, y : Pixel }

subpixelsPerPixel : I64
subpixelsPerPixel = 16

toVector2 : PixelVec -> Vector2
toVector2 = \{ x: @Pixel xpx, y: @Pixel ypx } ->
{ x: Num.toF32 xpx.pixels, y: Num.toF32 ypx.pixels }

add : Pixel, { pixels ? I64, subpixels ? I64 } -> Pixel
add = \@Pixel px, { pixels ? 0, subpixels ? 0 } ->
pixel = @Pixel { pixels: px.pixels + pixels, subpixels: px.subpixels + subpixels }
normalize pixel

sub : Pixel, { pixels ? I64, subpixels ? I64 } -> Pixel
sub = \@Pixel px, { pixels ? 0, subpixels ? 0 } ->
pixel = @Pixel { pixels: px.pixels - pixels, subpixels: px.subpixels - subpixels }
normalize pixel

normalize : Pixel -> Pixel
normalize = \@Pixel px ->
total = px.pixels * subpixelsPerPixel + px.subpixels

pixels = Num.divTrunc total subpixelsPerPixel
subpixels = Num.rem px.subpixels subpixelsPerPixel

@Pixel { pixels, subpixels }

totalSubpixels : Pixel -> I64
totalSubpixels = \@Pixel px ->
px.pixels * subpixelsPerPixel + px.subpixels

fromPixels : I64 -> Pixel
fromPixels = \pixels ->
fromParts { pixels, subpixels: 0 }

fromParts : { pixels : I64, subpixels : I64 } -> Pixel
fromParts = \{ pixels, subpixels } ->
@Pixel { pixels, subpixels }
|> normalize

pixelInspector : Pixel -> Inspector f where f implements InspectFormatter
pixelInspector = \@Pixel px ->
Inspect.str (Inspect.toStr px)

expect
x = fromParts { pixels: 1, subpixels: 0 }
y = fromParts { pixels: 2, subpixels: 2 }
vec = { x, y }

inspected = Inspect.toStr vec
expected =
"""
{x: "{pixels: 1, subpixels: 0}", y: "{pixels: 2, subpixels: 2}"}
"""

inspected == expected
7 changes: 7 additions & 0 deletions examples/rollback/Resolution.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module [width, height]

width : I64
width = 600

height : I64
height = 800
Loading
Loading