Skip to content

Commit bbb86bf

Browse files
committed
fix: improve netplay loading readiness reliability
1 parent b24259d commit bbb86bf

1 file changed

Lines changed: 92 additions & 41 deletions

File tree

src/netplay.go

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ const (
2828
syncConfigVersion uint16 = 1
2929
replayFormatVersion uint16 = 1
3030
replayMagic = "IKRPLCFG"
31+
32+
netLoadingPollTimeout = time.Millisecond
33+
34+
netLoadingReadyToken byte = 0xC7
35+
netLoadingAckToken byte = 0x7C
36+
)
37+
38+
type LoadingPhase uint8
39+
40+
const (
41+
LP_Idle LoadingPhase = iota
42+
LP_HostReadySent
43+
LP_GuestReadySent
3144
)
3245

3346
type NetState int
@@ -174,7 +187,7 @@ type NetConnection struct {
174187
closeOnce sync.Once
175188
uiInputDebounced bool
176189
headerWritten bool
177-
loadingPhase uint8 // 0=idle, 1=host sent token
190+
loadingPhase LoadingPhase
178191
}
179192

180193
func NewNetConnection() *NetConnection {
@@ -211,7 +224,7 @@ func (nc *NetConnection) Close() {
211224
return
212225
}
213226
// Reset any pending loading rendezvous.
214-
nc.loadingPhase = 0
227+
nc.loadingPhase = LP_Idle
215228
// Ensure connect/accept goroutines stop retrying/handshaking.
216229
nc.closeOnce.Do(func() {
217230
if nc.closing != nil {
@@ -438,6 +451,11 @@ func (nc *NetConnection) writeI8(i8 int8) error {
438451
return nil
439452
}
440453

454+
func (nc *NetConnection) writeU8(u8 byte) error {
455+
_, err := nc.conn.Write([]byte{u8})
456+
return err
457+
}
458+
441459
func (nc *NetConnection) readI16() (int16, error) {
442460
b := [2]byte{}
443461
if _, err := nc.conn.Read(b[:]); err != nil {
@@ -536,7 +554,7 @@ func (nc *NetConnection) Synchronize() error {
536554
return Error("Cannot connect to the other player")
537555
}
538556
// Reset any pending loading rendezvous so it can't interfere with gameplay sync.
539-
nc.loadingPhase = 0
557+
nc.loadingPhase = LP_Idle
540558
nc.Stop()
541559

542560
header, err := sys.synchronizeNetplayConfig(nc)
@@ -697,85 +715,118 @@ func (nc *NetConnection) ResetLoadingPhase() {
697715
if nc == nil {
698716
return
699717
}
700-
nc.loadingPhase = 0
718+
nc.loadingPhase = LP_Idle
701719
}
702720

703721
func (nc *NetConnection) tryReadU8() (byte, bool, error) {
704722
if nc == nil || nc.conn == nil {
705723
return 0, false, Error("Cannot connect to the other player")
706724
}
707725
b := [1]byte{}
708-
_ = nc.conn.SetReadDeadline(time.Now())
726+
_ = nc.conn.SetReadDeadline(time.Now().Add(netLoadingPollTimeout))
709727
n, err := nc.conn.Read(b[:])
710728
_ = nc.conn.SetReadDeadline(time.Time{})
729+
if n > 0 {
730+
return b[0], true, nil
731+
}
711732
if err != nil {
712733
if ne, ok := err.(net.Error); ok && ne.Timeout() {
713734
return 0, false, nil
714735
}
715736
return 0, false, err
716737
}
717-
if n <= 0 {
718-
return 0, false, nil
719-
}
720-
return b[0], true, nil
738+
return 0, false, nil
739+
}
740+
741+
func (nc *NetConnection) finishLoadingBarrier() {
742+
nc.loadingPhase = LP_Idle
743+
nc.time = 0
744+
nc.stoppedcnt = 0
721745
}
722746

723747
// Handshake for both peers finished loading
724748
func (nc *NetConnection) LoadingReady() (bool, error) {
749+
if sys.esc || sys.gameEnd {
750+
return false, nil
751+
}
725752
if nc == nil || !nc.IsConnected() || nc.st == NS_Error {
726753
return false, Error("Cannot connect to the other player")
727754
}
728755
nc.Stop()
729756

730-
const token byte = 0xC7
731-
const tokenI8 int8 = -57
732-
733757
// Host
734758
if nc.host {
735-
// Phase 0: send token once.
736-
if nc.loadingPhase == 0 {
737-
if err := nc.writeI8(tokenI8); err != nil {
738-
nc.loadingPhase = 0
759+
switch nc.loadingPhase {
760+
case LP_Idle:
761+
if err := nc.writeU8(netLoadingReadyToken); err != nil {
762+
nc.loadingPhase = LP_Idle
739763
return false, err
740764
}
741-
nc.loadingPhase = 1
765+
nc.loadingPhase = LP_HostReadySent
742766
return false, nil
767+
case LP_HostReadySent:
768+
v, ok, err := nc.tryReadU8()
769+
if err != nil {
770+
nc.loadingPhase = LP_Idle
771+
return false, err
772+
}
773+
if !ok {
774+
return false, nil
775+
}
776+
if v != netLoadingReadyToken {
777+
nc.loadingPhase = LP_Idle
778+
return false, Error("Synchronization error")
779+
}
780+
if err := nc.writeU8(netLoadingAckToken); err != nil {
781+
nc.loadingPhase = LP_Idle
782+
return false, err
783+
}
784+
nc.finishLoadingBarrier()
785+
return true, nil
743786
}
744-
// Phase 1: wait for ack (non-blocking poll).
787+
nc.loadingPhase = LP_Idle
788+
return false, Error("Synchronization error")
789+
}
790+
791+
// Guest waits for host readiness, replies with its own readiness, then waits for the host ACK
792+
switch nc.loadingPhase {
793+
case LP_Idle:
745794
v, ok, err := nc.tryReadU8()
746795
if err != nil {
747-
nc.loadingPhase = 0
796+
nc.loadingPhase = LP_Idle
748797
return false, err
749798
}
750799
if !ok {
751800
return false, nil
752801
}
753-
if v != token {
754-
nc.loadingPhase = 0
802+
if v != netLoadingReadyToken {
803+
nc.loadingPhase = LP_Idle
755804
return false, Error("Synchronization error")
756805
}
757-
nc.loadingPhase = 0
758-
return true, nil
759-
}
760-
761-
// Guest: wait for token, then immediately ack.
762-
v, ok, err := nc.tryReadU8()
763-
if err != nil {
764-
nc.loadingPhase = 0
765-
return false, err
766-
}
767-
if !ok {
806+
if err := nc.writeU8(netLoadingReadyToken); err != nil {
807+
nc.loadingPhase = LP_Idle
808+
return false, err
809+
}
810+
nc.loadingPhase = LP_GuestReadySent
768811
return false, nil
812+
case LP_GuestReadySent:
813+
v, ok, err := nc.tryReadU8()
814+
if err != nil {
815+
nc.loadingPhase = LP_Idle
816+
return false, err
817+
}
818+
if !ok {
819+
return false, nil
820+
}
821+
if v != netLoadingAckToken {
822+
nc.loadingPhase = LP_Idle
823+
return false, Error("Synchronization error")
824+
}
825+
nc.finishLoadingBarrier()
826+
return true, nil
769827
}
770-
if v != token {
771-
nc.loadingPhase = 0
772-
return false, Error("Synchronization error")
773-
}
774-
if err := nc.writeI8(tokenI8); err != nil {
775-
nc.loadingPhase = 0
776-
return false, err
777-
}
778-
return true, nil
828+
nc.loadingPhase = LP_Idle
829+
return false, Error("Synchronization error")
779830
}
780831

781832
func (nc *NetConnection) Update() bool {

0 commit comments

Comments
 (0)