mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-19 13:39:18 +00:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e3fe773634 | ||
|
17cac19c62 | ||
|
26bdd41285 | ||
|
6bf70463fa | ||
|
e3d844abad | ||
|
0373665382 | ||
|
3b0db64792 | ||
|
dd8b731ade | ||
|
c4489e0912 | ||
|
348c889e14 | ||
|
c6473db561 | ||
|
e165d49cb1 | ||
|
26370dce61 | ||
|
f3a12b2aa9 | ||
|
1f5802ee14 | ||
|
080a384ade | ||
|
9469b27348 |
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
|
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
|
||||||
|
|
||||||
_(the following gif is sped up to ~1.5x for file size reduction, kindly note that around ~11s countdown, the attack animation is resumed from a partial progress)_
|
_(the following gif is sped up to ~1.33x for file size reduction, kindly note that around ~8s countdown, the attack animation is resumed from a partial progress)_
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Please also checkout [this demo video](https://pan.baidu.com/s/1U1wb7KWyHorZElNWcS5HHA?pwd=30wh) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.
|
Please also checkout [this demo video](https://pan.baidu.com/s/172AmIKxbFgGXZzWVqxNUPA?pwd=e2tp) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.
|
||||||
|
|
||||||
The video mainly shows the following features.
|
The video mainly shows the following features.
|
||||||
- The backend receives inputs from frontend peers and broadcasts back for synchronization.
|
- The backend receives inputs from frontend peers and broadcasts back for synchronization.
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
PROJECTNAME=server.exe
|
PROJECTNAME=server.exe
|
||||||
ROOT_DIR=.
|
ROOT_DIR=.
|
||||||
|
GOPROXY=https://mirrors.aliyun.com/goproxy
|
||||||
all: help
|
all: help
|
||||||
|
|
||||||
gen-constants:
|
gen-constants:
|
||||||
@@ -13,13 +14,13 @@ run-test-and-hotreload:
|
|||||||
ServerEnv=TEST CompileDaemon -log-prefix=false -build="go build" -command="./$(PROJECTNAME)"
|
ServerEnv=TEST CompileDaemon -log-prefix=false -build="go build" -command="./$(PROJECTNAME)"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build -o $(ROOT_DIR)/$(PROJECTNAME)
|
GOPROXY=$(GOPROXY) go build -o $(ROOT_DIR)/$(PROJECTNAME)
|
||||||
|
|
||||||
run-prod: build-prod
|
run-prod: build-prod
|
||||||
./$(PROJECTNAME)
|
./$(PROJECTNAME)
|
||||||
|
|
||||||
build-prod:
|
build-prod:
|
||||||
go build -ldflags "-s -w -X main.VERSION=$(shell git rev-parse --short HEAD)-$(shell date "+%Y%m%d-%H:%M:%S")" -o $(ROOT_DIR)/$(PROJECTNAME)
|
GOPROXY=$(GOPROXY) go build -ldflags "-s -w -X main.VERSION=$(shell git rev-parse --short HEAD)-$(shell date "+%Y%m%d-%H:%M:%S")" -o $(ROOT_DIR)/$(PROJECTNAME)
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ import (
|
|||||||
type PlayerBattleState struct {
|
type PlayerBattleState struct {
|
||||||
ADDED_PENDING_BATTLE_COLLIDER_ACK int32
|
ADDED_PENDING_BATTLE_COLLIDER_ACK int32
|
||||||
READDED_PENDING_BATTLE_COLLIDER_ACK int32
|
READDED_PENDING_BATTLE_COLLIDER_ACK int32
|
||||||
|
READDED_BATTLE_COLLIDER_ACKED int32
|
||||||
ACTIVE int32
|
ACTIVE int32
|
||||||
DISCONNECTED int32
|
DISCONNECTED int32
|
||||||
LOST int32
|
LOST int32
|
||||||
@@ -26,11 +27,12 @@ func InitPlayerBattleStateIns() {
|
|||||||
PlayerBattleStateIns = PlayerBattleState{
|
PlayerBattleStateIns = PlayerBattleState{
|
||||||
ADDED_PENDING_BATTLE_COLLIDER_ACK: 0,
|
ADDED_PENDING_BATTLE_COLLIDER_ACK: 0,
|
||||||
READDED_PENDING_BATTLE_COLLIDER_ACK: 1,
|
READDED_PENDING_BATTLE_COLLIDER_ACK: 1,
|
||||||
ACTIVE: 2,
|
READDED_BATTLE_COLLIDER_ACKED: 2,
|
||||||
DISCONNECTED: 3,
|
ACTIVE: 3,
|
||||||
LOST: 4,
|
DISCONNECTED: 4,
|
||||||
EXPELLED_DURING_GAME: 5,
|
LOST: 5,
|
||||||
EXPELLED_IN_DISMISSAL: 6,
|
EXPELLED_DURING_GAME: 6,
|
||||||
|
EXPELLED_IN_DISMISSAL: 7,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,6 +21,10 @@ func NewRingBuffer(n int32) *RingBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rb *RingBuffer) Put(pItem interface{}) {
|
func (rb *RingBuffer) Put(pItem interface{}) {
|
||||||
|
for 0 < rb.Cnt && rb.Cnt >= rb.N {
|
||||||
|
// Make room for the new element
|
||||||
|
rb.Pop()
|
||||||
|
}
|
||||||
rb.Eles[rb.Ed] = pItem
|
rb.Eles[rb.Ed] = pItem
|
||||||
rb.EdFrameId++
|
rb.EdFrameId++
|
||||||
rb.Cnt++
|
rb.Cnt++
|
||||||
@@ -69,5 +73,8 @@ func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
|
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
|
||||||
|
if frameId >= rb.EdFrameId || frameId < rb.StFrameId {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return rb.GetByOffset(frameId - rb.StFrameId)
|
return rb.GetByOffset(frameId - rb.StFrameId)
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1063,6 +1063,7 @@ type RoomDownsyncFrame struct {
|
|||||||
Players map[int32]*PlayerDownsync `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
Players map[int32]*PlayerDownsync `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||||
CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"`
|
CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"`
|
||||||
MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
|
MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
|
||||||
|
BackendUnconfirmedMask uint64 `protobuf:"varint,5,opt,name=backendUnconfirmedMask,proto3" json:"backendUnconfirmedMask,omitempty"` // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomDownsyncFrame) Reset() {
|
func (x *RoomDownsyncFrame) Reset() {
|
||||||
@@ -1125,6 +1126,13 @@ func (x *RoomDownsyncFrame) GetMeleeBullets() []*MeleeBullet {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *RoomDownsyncFrame) GetBackendUnconfirmedMask() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.BackendUnconfirmedMask
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
var File_room_downsync_frame_proto protoreflect.FileDescriptor
|
var File_room_downsync_frame_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_room_downsync_frame_proto_rawDesc = []byte{
|
var file_room_downsync_frame_proto_rawDesc = []byte{
|
||||||
@@ -1377,7 +1385,7 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
|
|||||||
0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
|
0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65,
|
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65,
|
||||||
0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
||||||
0x38, 0x01, 0x22, 0x9a, 0x02, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73,
|
0x38, 0x01, 0x22, 0xd2, 0x02, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73,
|
||||||
0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79,
|
0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79,
|
||||||
0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
@@ -1389,14 +1397,18 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
|
|||||||
0x6f, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65,
|
0x6f, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65,
|
||||||
0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d,
|
0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d,
|
||||||
0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x1a, 0x52, 0x0a, 0x0c, 0x50,
|
0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x62,
|
||||||
0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65,
|
||||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a,
|
0x64, 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x62, 0x61, 0x63,
|
||||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70,
|
0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e,
|
0x61, 0x73, 0x6b, 0x1a, 0x52, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e,
|
||||||
0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42,
|
0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
|
||||||
0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72,
|
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
|
||||||
0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c,
|
||||||
|
0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61,
|
||||||
|
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c,
|
||||||
|
0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -365,7 +365,7 @@ func Serve(c *gin.Context) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to receive from client-side in a non-blocking manner.
|
// TODO: Is there any potential edge-trigger improvement like the epoll approach mentioned above for the following statement? See discussion in https://github.com/gorilla/websocket/issues/122
|
||||||
_, bytes, err := conn.ReadMessage()
|
_, bytes, err := conn.ReadMessage()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
Logger.Error("About to `signalToCloseConnOfThisPlayer`", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Error(err))
|
Logger.Error("About to `signalToCloseConnOfThisPlayer`", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Error(err))
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.7 MiB |
BIN
charts/smooth_melee_attack_spedup.gif
Normal file
BIN
charts/smooth_melee_attack_spedup.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 MiB |
@@ -5,7 +5,7 @@ all: help
|
|||||||
GOPROXY=https://mirrors.aliyun.com/goproxy
|
GOPROXY=https://mirrors.aliyun.com/goproxy
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build -o $(ROOT_DIR)/$(PROJECTNAME)
|
GOPROXY=$(GOPROXY) go build -o $(ROOT_DIR)/$(PROJECTNAME)
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./$(PROJECTNAME)
|
./$(PROJECTNAME)
|
||||||
|
@@ -134,4 +134,5 @@ message RoomDownsyncFrame {
|
|||||||
map<int32, PlayerDownsync> players = 2;
|
map<int32, PlayerDownsync> players = 2;
|
||||||
int64 countdownNanos = 3;
|
int64 countdownNanos = 3;
|
||||||
repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
|
repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
|
||||||
|
uint64 backendUnconfirmedMask = 5; // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
|
||||||
}
|
}
|
||||||
|
@@ -440,7 +440,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
210.5241291124452,
|
210.43753679824133,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@@ -454,7 +454,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
210.5241291124452,
|
216.50635094610968,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@@ -82,21 +82,21 @@ cc.Class({
|
|||||||
// It turns out that "prevRdfPlayer.characterState" is not useful in this function :)
|
// It turns out that "prevRdfPlayer.characterState" is not useful in this function :)
|
||||||
if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
||||||
// No need to interrupt
|
// No need to interrupt
|
||||||
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, , to: ${JSON.stringify(rdfPlayer)}`);
|
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.animComp instanceof dragonBones.ArmatureDisplay) {
|
if (this.animComp instanceof dragonBones.ArmatureDisplay) {
|
||||||
this._interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl);
|
this._interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName);
|
||||||
} else {
|
} else {
|
||||||
this._interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName);
|
this._interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl) {
|
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) {
|
||||||
if (ATK_CHARACTER_STATE.Idle1[0] == newCharacterState || ATK_CHARACTER_STATE.Walking[0] == newCharacterState) {
|
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
||||||
// No "framesToRecover"
|
// No "framesToRecover"
|
||||||
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, playing new ${newAnimName} from the beginning: while the playing anim is ${playAnimation}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, , to: ${JSON.stringify(rdfPlayer)}`);
|
//console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
|
||||||
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
|
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
|
||||||
} else {
|
} else {
|
||||||
const animationData = underlyingAnimationCtrl._animations[newAnimName];
|
const animationData = underlyingAnimationCtrl._animations[newAnimName];
|
||||||
@@ -109,10 +109,10 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName) {
|
_interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName) {
|
||||||
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
||||||
// No "framesToRecover"
|
// No "framesToRecover"
|
||||||
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, playing new ${newAnimName} from the beginning: while the playing anim is ${playAnimation}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, , to: ${JSON.stringify(rdfPlayer)}`);
|
//console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
|
||||||
this.animComp.play(newAnimName, 0);
|
this.animComp.play(newAnimName, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -104,32 +104,6 @@ cc.Class({
|
|||||||
return (0 == inputFrameId % 10);
|
return (0 == inputFrameId % 10);
|
||||||
},
|
},
|
||||||
|
|
||||||
dumpToRenderCache: function(rdf) {
|
|
||||||
const self = this;
|
|
||||||
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId - 1; // Keep at least 1 prev render frame for anim triggering
|
|
||||||
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
|
|
||||||
self.recentRenderCache.pop();
|
|
||||||
}
|
|
||||||
const ret = self.recentRenderCache.setByFrameId(rdf, rdf.id);
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
|
|
||||||
dumpToInputCache: function(inputFrameDownsync) {
|
|
||||||
const self = this;
|
|
||||||
let minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames) - self.spAtkLookupFrames; // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding delayedInputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction.
|
|
||||||
if (minToKeepInputFrameId > self.lastAllConfirmedInputFrameId) {
|
|
||||||
minToKeepInputFrameId = self.lastAllConfirmedInputFrameId;
|
|
||||||
}
|
|
||||||
while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) {
|
|
||||||
self.recentInputCache.pop();
|
|
||||||
}
|
|
||||||
const ret = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
|
|
||||||
if (-1 < self.lastAllConfirmedInputFrameId && self.recentInputCache.stFrameId > self.lastAllConfirmedInputFrameId) {
|
|
||||||
console.error("Invalid input cache dumped! lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
|
|
||||||
_convertToInputFrameId(renderFrameId, inputDelayFrames) {
|
_convertToInputFrameId(renderFrameId, inputDelayFrames) {
|
||||||
if (renderFrameId < inputDelayFrames) return 0;
|
if (renderFrameId < inputDelayFrames) return 0;
|
||||||
return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames);
|
return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames);
|
||||||
@@ -153,28 +127,35 @@ cc.Class({
|
|||||||
null == self.ctrl ||
|
null == self.ctrl ||
|
||||||
null == self.selfPlayerInfo
|
null == self.selfPlayerInfo
|
||||||
) {
|
) {
|
||||||
return [null, null];
|
throw `noDelayInputFrameId=${inputFrameId} couldn't be generated: recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousSelfInput = null,
|
||||||
|
currSelfInput = null;
|
||||||
const joinIndex = self.selfPlayerInfo.joinIndex;
|
const joinIndex = self.selfPlayerInfo.joinIndex;
|
||||||
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId);
|
// [WARNING] The while-loop here handles a situation where the "resync rdf & accompaniedInputFrameDownsyncBatch" mismatched and we have to predict some "gap-inputFrames"!
|
||||||
const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
|
while (self.recentInputCache.edFrameId <= inputFrameId) {
|
||||||
|
// TODO: find some kind of synchronization mechanism against "onInputFrameDownsyncBatch"!
|
||||||
|
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1);
|
||||||
|
previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
|
||||||
|
|
||||||
// If "forceConfirmation" is active on backend, we shouldn't override the already downsynced "inputFrameDownsync"s.
|
// If "forceConfirmation" is active on backend, there's a chance that the already downsynced "inputFrameDownsync"s are ahead of a locally generating inputFrameId, in this case we respect the downsynced one.
|
||||||
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
|
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
|
||||||
if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) {
|
if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) {
|
||||||
|
console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache and is all-confirmed: recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
||||||
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
|
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
|
||||||
}
|
}
|
||||||
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
|
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
|
||||||
const currSelfInput = self.ctrl.getEncodedInput();
|
currSelfInput = self.ctrl.getEncodedInput();
|
||||||
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
|
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
|
||||||
const prefabbedInputFrameDownsync = {
|
const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({
|
||||||
inputFrameId: inputFrameId,
|
inputFrameId: self.recentInputCache.edFrameId,
|
||||||
inputList: prefabbedInputList,
|
inputList: prefabbedInputList,
|
||||||
confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1))
|
confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1))
|
||||||
};
|
});
|
||||||
|
|
||||||
self.dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
|
self.recentInputCache.put(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
|
||||||
|
}
|
||||||
|
|
||||||
return [previousSelfInput, currSelfInput];
|
return [previousSelfInput, currSelfInput];
|
||||||
},
|
},
|
||||||
@@ -203,7 +184,7 @@ cc.Class({
|
|||||||
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
|
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
|
||||||
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
|
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
|
||||||
if (null == inputFrameDownsync) {
|
if (null == inputFrameDownsync) {
|
||||||
console.error("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, ": latestLocalInputFrameId=", latestLocalInputFrameId, ", recentInputCache=", self._stringifyRecentInputCache(false));
|
console.error(`sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=i: latestLocalInputFrameId=${latestLocalInputFrameId}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
||||||
} else {
|
} else {
|
||||||
const inputFrameUpsync = {
|
const inputFrameUpsync = {
|
||||||
inputFrameId: i,
|
inputFrameId: i,
|
||||||
@@ -225,6 +206,9 @@ cc.Class({
|
|||||||
}).finish();
|
}).finish();
|
||||||
window.sendSafely(reqData);
|
window.sendSafely(reqData);
|
||||||
self.lastUpsyncInputFrameId = latestLocalInputFrameId;
|
self.lastUpsyncInputFrameId = latestLocalInputFrameId;
|
||||||
|
if (self.lastUpsyncInputFrameId >= self.recentInputCache.edFrameId) {
|
||||||
|
throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
@@ -312,7 +296,7 @@ cc.Class({
|
|||||||
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
|
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
|
||||||
|
|
||||||
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
|
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
|
||||||
self.recentInputCache = new RingBuffer((self.renderCacheSize >> 2) + 1);
|
self.recentInputCache = new RingBuffer((self.renderCacheSize >> 1) + 1);
|
||||||
|
|
||||||
self.collisionSys = new collisions.Collisions();
|
self.collisionSys = new collisions.Collisions();
|
||||||
|
|
||||||
@@ -320,6 +304,8 @@ cc.Class({
|
|||||||
self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets
|
self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets
|
||||||
self.collisionSysMap = new Map();
|
self.collisionSysMap = new Map();
|
||||||
|
|
||||||
|
console.log(`collisionSys & collisionSysMap reset`);
|
||||||
|
|
||||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||||
|
|
||||||
self.battleState = ALL_BATTLE_STATES.WAITING;
|
self.battleState = ALL_BATTLE_STATES.WAITING;
|
||||||
@@ -413,7 +399,10 @@ cc.Class({
|
|||||||
/** Init required prefab ended. */
|
/** Init required prefab ended. */
|
||||||
|
|
||||||
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
|
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
|
||||||
|
console.log(`Received parsedBattleColliderInfo via ws`);
|
||||||
|
// TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE".
|
||||||
Object.assign(self, parsedBattleColliderInfo);
|
Object.assign(self, parsedBattleColliderInfo);
|
||||||
|
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
|
||||||
|
|
||||||
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
||||||
|
|
||||||
@@ -458,7 +447,7 @@ cc.Class({
|
|||||||
const x0 = boundaryObj.anchor.x,
|
const x0 = boundaryObj.anchor.x,
|
||||||
y0 = boundaryObj.anchor.y;
|
y0 = boundaryObj.anchor.y;
|
||||||
|
|
||||||
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
||||||
return [p.x, p.y];
|
return [p.x, p.y];
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -493,10 +482,10 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// console.log("Created barrier: ", newBarrier);
|
|
||||||
++barrierIdCounter;
|
++barrierIdCounter;
|
||||||
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
||||||
self.collisionSysMap.set(collisionBarrierIndex, newBarrier);
|
self.collisionSysMap.set(collisionBarrierIndex, newBarrierCollider);
|
||||||
|
// console.log(`Created new barrier collider: ${collisionBarrierIndex}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer'));
|
self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer'));
|
||||||
@@ -509,6 +498,7 @@ cc.Class({
|
|||||||
act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK,
|
act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK,
|
||||||
}).finish();
|
}).finish();
|
||||||
window.sendSafely(reqData);
|
window.sendSafely(reqData);
|
||||||
|
console.log(`Sent UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK via ws`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -571,17 +561,21 @@ cc.Class({
|
|||||||
this._inputControlEnabled = false;
|
this._inputControlEnabled = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomDownsyncFrame(rdf) {
|
onRoomDownsyncFrame(rdf, accompaniedInputFrameDownsyncBatch) {
|
||||||
// This function is also applicable to "re-joining".
|
// This function is also applicable to "re-joining".
|
||||||
const self = window.mapIns;
|
const self = window.mapIns;
|
||||||
if (rdf.id < self.lastAllConfirmedRenderFrameId) {
|
if (!self.recentRenderCache) {
|
||||||
return window.RING_BUFF_FAILED_TO_SET;
|
return;
|
||||||
}
|
}
|
||||||
|
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id);
|
||||||
|
const shouldForceDumping2 = (rdf.id > self.renderFrameId + self.renderFrameIdLagTolerance);
|
||||||
|
|
||||||
const dumpRenderCacheRet = self.dumpToRenderCache(rdf);
|
const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2) ? self.recentRenderCache.setByFrameId(rdf, rdf.id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
|
||||||
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
|
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
|
||||||
console.error("Something is wrong while setting the RingBuffer by frameId!");
|
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||||
return dumpRenderCacheRet;
|
|
||||||
}
|
}
|
||||||
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) {
|
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) {
|
||||||
/*
|
/*
|
||||||
@@ -592,13 +586,7 @@ cc.Class({
|
|||||||
return dumpRenderCacheRet;
|
return dumpRenderCacheRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The logic below applies to ( || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
|
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
|
||||||
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
|
|
||||||
console.log('On battle started! renderFrameId=', rdf.id);
|
|
||||||
} else {
|
|
||||||
console.log('On battle resynced! renderFrameId=', rdf.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const players = rdf.players;
|
const players = rdf.players;
|
||||||
self._initPlayerRichInfoDict(players);
|
self._initPlayerRichInfoDict(players);
|
||||||
|
|
||||||
@@ -612,11 +600,24 @@ cc.Class({
|
|||||||
|
|
||||||
if (null == self.renderFrameId || self.renderFrameId <= rdf.id) {
|
if (null == self.renderFrameId || self.renderFrameId <= rdf.id) {
|
||||||
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
|
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
|
||||||
|
|
||||||
|
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
|
||||||
|
console.log('On battle started! renderFrameId=', rdf.id);
|
||||||
|
} else {
|
||||||
|
self.hideFindingPlayersGUI(rdf);
|
||||||
|
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
|
||||||
|
console.warn(`Got resync@localRenderFrameId=${self.renderFrameId} -> rdf.id=${rdf.id} & rdf.backendUnconfirmedMask=${rdf.backendUnconfirmedMask}, @lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}`);
|
||||||
|
}
|
||||||
|
|
||||||
self.renderFrameId = rdf.id;
|
self.renderFrameId = rdf.id;
|
||||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||||
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
|
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
|
||||||
self.lastAllConfirmedRenderFrameId = rdf.id;
|
self.lastAllConfirmedRenderFrameId = rdf.id;
|
||||||
self.chaserRenderFrameId = rdf.id;
|
self.chaserRenderFrameId = rdf.id;
|
||||||
|
const candidateLastAllConfirmedInputFrame = self._convertToInputFrameId(rdf.id - 1, self.inputDelayFrames);
|
||||||
|
if (self.lastAllConfirmedInputFrame < candidateLastAllConfirmedInputFrame) {
|
||||||
|
self.lastAllConfirmedInputFrame = candidateLastAllConfirmedInputFrame;
|
||||||
|
}
|
||||||
|
|
||||||
const canvasNode = self.canvasNode;
|
const canvasNode = self.canvasNode;
|
||||||
self.ctrl = canvasNode.getComponent("TouchEventsManager");
|
self.ctrl = canvasNode.getComponent("TouchEventsManager");
|
||||||
@@ -650,9 +651,12 @@ cc.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onInputFrameDownsyncBatch(batch) {
|
onInputFrameDownsyncBatch(batch) {
|
||||||
|
// TODO: find some kind of synchronization mechanism against "_generateInputFrameUpsync"!
|
||||||
const self = this;
|
const self = this;
|
||||||
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState
|
if (!self.recentInputCache) {
|
||||||
&& ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) {
|
return;
|
||||||
|
}
|
||||||
|
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,6 +667,7 @@ cc.Class({
|
|||||||
if (inputFrameDownsyncId < self.lastAllConfirmedInputFrameId) {
|
if (inputFrameDownsyncId < self.lastAllConfirmedInputFrameId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
|
||||||
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
|
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
|
||||||
if (null != localInputFrame
|
if (null != localInputFrame
|
||||||
&&
|
&&
|
||||||
@@ -672,10 +677,12 @@ cc.Class({
|
|||||||
) {
|
) {
|
||||||
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
|
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
|
||||||
}
|
}
|
||||||
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
|
|
||||||
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
|
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
|
||||||
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
|
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
|
||||||
self.dumpToInputCache(inputFrameDownsync);
|
const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
|
||||||
|
if (window.RING_BUFF_FAILED_TO_SET == ret) {
|
||||||
|
throw `Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null == firstPredictedYetIncorrectInputFrameId) return;
|
if (null == firstPredictedYetIncorrectInputFrameId) return;
|
||||||
@@ -716,7 +723,7 @@ cc.Class({
|
|||||||
logBattleStats() {
|
logBattleStats() {
|
||||||
const self = this;
|
const self = this;
|
||||||
let s = [];
|
let s = [];
|
||||||
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}`);
|
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
||||||
|
|
||||||
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) {
|
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) {
|
||||||
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
|
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
|
||||||
@@ -761,7 +768,8 @@ cc.Class({
|
|||||||
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
|
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
|
||||||
newPlayerNode.setPosition(wx, wy);
|
newPlayerNode.setPosition(wx, wy);
|
||||||
playerScriptIns.mapNode = self.node;
|
playerScriptIns.mapNode = self.node;
|
||||||
const colliderWidth = playerDownsyncInfo.colliderRadius * 2, colliderHeight = playerDownsyncInfo.colliderRadius * 3;
|
const colliderWidth = playerDownsyncInfo.colliderRadius * 2,
|
||||||
|
colliderHeight = playerDownsyncInfo.colliderRadius * 3;
|
||||||
const [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, colliderWidth, colliderHeight),
|
const [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, colliderWidth, colliderHeight),
|
||||||
pts = [[0, 0], [colliderWidth, 0], [colliderWidth, colliderHeight], [0, colliderHeight]];
|
pts = [[0, 0], [colliderWidth, 0], [colliderWidth, colliderHeight], [0, colliderHeight]];
|
||||||
|
|
||||||
@@ -770,6 +778,8 @@ cc.Class({
|
|||||||
newPlayerCollider.data = playerDownsyncInfo;
|
newPlayerCollider.data = playerDownsyncInfo;
|
||||||
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
|
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
|
||||||
|
|
||||||
|
console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.colliderRadius}`);
|
||||||
|
|
||||||
safelyAddChild(self.node, newPlayerNode);
|
safelyAddChild(self.node, newPlayerNode);
|
||||||
setLocalZOrder(newPlayerNode, 5);
|
setLocalZOrder(newPlayerNode, 5);
|
||||||
|
|
||||||
@@ -783,7 +793,8 @@ cc.Class({
|
|||||||
const self = this;
|
const self = this;
|
||||||
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
|
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
|
||||||
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
|
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
|
||||||
if (elapsedMillisSinceLastFrameIdTriggered < (self.rollbackEstimatedDtMillis)) {
|
if (elapsedMillisSinceLastFrameIdTriggered < self.tooFastDtIntervalMillis) {
|
||||||
|
// [WARNING] We should avoid a frontend ticking too fast to prevent cheating, as well as ticking too slow to cause a "resync avalanche" that impacts user experience!
|
||||||
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
|
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -822,17 +833,13 @@ cc.Class({
|
|||||||
*/
|
*/
|
||||||
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
|
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
|
||||||
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
||||||
|
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
|
||||||
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||||
let t3 = performance.now();
|
let t3 = performance.now();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error during Map.update", err);
|
console.error("Error during Map.update", err);
|
||||||
|
self.onBattleStopped(); // TODO: Popup to ask player to refresh browser
|
||||||
} finally {
|
} finally {
|
||||||
// Update countdown
|
|
||||||
self.countdownNanos = self.battleDurationNanos - self.renderFrameId * self.rollbackEstimatedDtNanos;
|
|
||||||
if (self.countdownNanos <= 0) {
|
|
||||||
self.onBattleStopped(self.playerRichInfoDict);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const countdownSeconds = parseInt(self.countdownNanos / 1000000000);
|
const countdownSeconds = parseInt(self.countdownNanos / 1000000000);
|
||||||
if (isNaN(countdownSeconds)) {
|
if (isNaN(countdownSeconds)) {
|
||||||
console.warn(`countdownSeconds is NaN for countdownNanos == ${self.countdownNanos}.`);
|
console.warn(`countdownSeconds is NaN for countdownNanos == ${self.countdownNanos}.`);
|
||||||
@@ -840,8 +847,6 @@ cc.Class({
|
|||||||
if (null != self.countdownLabel) {
|
if (null != self.countdownLabel) {
|
||||||
self.countdownLabel.string = countdownSeconds;
|
self.countdownLabel.string = countdownSeconds;
|
||||||
}
|
}
|
||||||
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
|
|
||||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -921,9 +926,6 @@ cc.Class({
|
|||||||
const self = this;
|
const self = this;
|
||||||
if (null == self.findingPlayerNode.parent) return;
|
if (null == self.findingPlayerNode.parent) return;
|
||||||
self.findingPlayerNode.parent.removeChild(self.findingPlayerNode);
|
self.findingPlayerNode.parent.removeChild(self.findingPlayerNode);
|
||||||
if (null != rdf) {
|
|
||||||
self._initPlayerRichInfoDict(rdf.players);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onBattleReadyToStart(rdf) {
|
onBattleReadyToStart(rdf) {
|
||||||
@@ -967,15 +969,21 @@ cc.Class({
|
|||||||
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
|
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
|
||||||
playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false);
|
playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update countdown
|
||||||
|
self.countdownNanos = self.battleDurationNanos - self.renderFrameId * self.rollbackEstimatedDtNanos;
|
||||||
|
if (self.countdownNanos <= 0) {
|
||||||
|
self.onBattleStopped(self.playerRichInfoDict);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
|
getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
|
||||||
const self = this;
|
const self = this;
|
||||||
let inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
|
const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
|
||||||
if (null != inputFrameDownsync && -1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) {
|
|
||||||
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
|
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
|
||||||
|
if (null != inputFrameDownsync && null != lastAllConfirmedInputFrame && inputFrameId > self.lastAllConfirmedInputFrameId) {
|
||||||
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
|
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
|
||||||
if (i == self.selfPlayerInfo.joinIndex - 1) continue;
|
if (i == (self.selfPlayerInfo.joinIndex - 1)) continue;
|
||||||
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input!
|
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,11 +1015,7 @@ cc.Class({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const toRet = {
|
const nextRenderFrameMeleeBullets = [];
|
||||||
id: currRenderFrame.id + 1,
|
|
||||||
players: nextRenderFramePlayers,
|
|
||||||
meleeBullets: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const bulletPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
const bulletPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
||||||
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
||||||
@@ -1104,7 +1108,7 @@ cc.Class({
|
|||||||
collisionSysMap.delete(collisionBulletIndex);
|
collisionSysMap.delete(collisionBulletIndex);
|
||||||
}
|
}
|
||||||
if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue;
|
if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue;
|
||||||
toRet.meleeBullets.push(meleeBullet);
|
nextRenderFrameMeleeBullets.push(meleeBullet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process player inputs
|
// Process player inputs
|
||||||
@@ -1145,7 +1149,7 @@ cc.Class({
|
|||||||
punch.offenderJoinIndex = joinIndex;
|
punch.offenderJoinIndex = joinIndex;
|
||||||
punch.offenderPlayerId = playerId;
|
punch.offenderPlayerId = playerId;
|
||||||
punch.originatedRenderFrameId = currRenderFrame.id;
|
punch.originatedRenderFrameId = currRenderFrame.id;
|
||||||
toRet.meleeBullets.push(punch);
|
nextRenderFrameMeleeBullets.push(punch);
|
||||||
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
|
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
|
||||||
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
|
|
||||||
@@ -1198,7 +1202,11 @@ cc.Class({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return toRet;
|
return window.pb.protos.RoomDownsyncFrame.create({
|
||||||
|
id: currRenderFrame.id + 1,
|
||||||
|
players: nextRenderFramePlayers,
|
||||||
|
meleeBullets: nextRenderFrameMeleeBullets,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) {
|
rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) {
|
||||||
@@ -1206,31 +1214,24 @@ cc.Class({
|
|||||||
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
|
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
|
||||||
*/
|
*/
|
||||||
const self = this;
|
const self = this;
|
||||||
let prevLatestRdf = null;
|
let i = renderFrameIdSt,
|
||||||
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
|
prevLatestRdf = null,
|
||||||
|
latestRdf = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
latestRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
|
||||||
if (null == latestRdf) {
|
if (null == latestRdf) {
|
||||||
console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
|
|
||||||
return [prevLatestRdf, latestRdf];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (renderFrameIdSt >= renderFrameIdEd) {
|
|
||||||
return [prevLatestRdf, latestRdf];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
|
|
||||||
const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
|
|
||||||
if (null == currRenderFrame) {
|
|
||||||
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
|
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
|
||||||
return [prevLatestRdf, latestRdf];
|
return [prevLatestRdf, latestRdf];
|
||||||
}
|
}
|
||||||
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
|
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
|
||||||
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
|
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
|
||||||
if (null == delayedInputFrame) {
|
if (null == delayedInputFrame) {
|
||||||
console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`);
|
// Shouldn't happen!
|
||||||
return [prevLatestRdf, latestRdf];
|
throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
|
||||||
}
|
}
|
||||||
prevLatestRdf = latestRdf;
|
prevLatestRdf = latestRdf;
|
||||||
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap);
|
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, prevLatestRdf, collisionSys, collisionSysMap);
|
||||||
if (
|
if (
|
||||||
self._allConfirmed(delayedInputFrame.confirmedList)
|
self._allConfirmed(delayedInputFrame.confirmedList)
|
||||||
&&
|
&&
|
||||||
@@ -1248,8 +1249,9 @@ cc.Class({
|
|||||||
// Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
|
// Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
|
||||||
self.chaserRenderFrameId = latestRdf.id;
|
self.chaserRenderFrameId = latestRdf.id;
|
||||||
}
|
}
|
||||||
self.dumpToRenderCache(latestRdf);
|
self.recentRenderCache.setByFrameId(latestRdf, latestRdf.id);
|
||||||
}
|
++i;
|
||||||
|
} while (i < renderFrameIdEd);
|
||||||
|
|
||||||
return [prevLatestRdf, latestRdf];
|
return [prevLatestRdf, latestRdf];
|
||||||
},
|
},
|
||||||
|
@@ -33,6 +33,7 @@ cc.Class({
|
|||||||
self.inputScaleFrames = 2;
|
self.inputScaleFrames = 2;
|
||||||
self.inputFrameUpsyncDelayTolerance = 2;
|
self.inputFrameUpsyncDelayTolerance = 2;
|
||||||
|
|
||||||
|
self.renderCacheSize = 1024;
|
||||||
self.rollbackEstimatedDt = 0.016667;
|
self.rollbackEstimatedDt = 0.016667;
|
||||||
self.rollbackEstimatedDtMillis = 16.667;
|
self.rollbackEstimatedDtMillis = 16.667;
|
||||||
self.rollbackEstimatedDtNanos = 16666666;
|
self.rollbackEstimatedDtNanos = 16666666;
|
||||||
|
@@ -13,6 +13,10 @@ var RingBuffer = function(capacity) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
RingBuffer.prototype.put = function(item) {
|
RingBuffer.prototype.put = function(item) {
|
||||||
|
while (0 < this.cnt && this.cnt >= this.n) {
|
||||||
|
// Make room for the new element
|
||||||
|
this.pop();
|
||||||
|
}
|
||||||
this.eles[this.ed] = item
|
this.eles[this.ed] = item
|
||||||
this.edFrameId++;
|
this.edFrameId++;
|
||||||
this.cnt++;
|
this.cnt++;
|
||||||
@@ -61,40 +65,41 @@ RingBuffer.prototype.getArrIdxByOffset = function(offsetFromSt) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
RingBuffer.prototype.getByFrameId = function(frameId) {
|
RingBuffer.prototype.getByFrameId = function(frameId) {
|
||||||
|
if (frameId >= this.edFrameId || frameId < this.stFrameId) return null;
|
||||||
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
|
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
|
||||||
return (null == arrIdx ? null : this.eles[arrIdx]);
|
return (null == arrIdx ? null : this.eles[arrIdx]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
|
// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
|
||||||
RingBuffer.prototype.setByFrameId = function(item, frameId) {
|
RingBuffer.prototype.setByFrameId = function(item, frameId) {
|
||||||
if (frameId < this.stFrameId) {
|
const oldStFrameId = this.stFrameId,
|
||||||
console.error("Invalid putByFrameId#1: stFrameId=", this.stFrameId, ", edFrameId=", this.edFrameId, ", incoming item=", item);
|
oldEdFrameId = this.edFrameId;
|
||||||
return window.RING_BUFF_FAILED_TO_SET;
|
if (frameId < oldStFrameId) {
|
||||||
|
return [window.RING_BUFF_FAILED_TO_SET, oldStFrameId, oldEdFrameId];
|
||||||
}
|
}
|
||||||
|
// By now "this.stFrameId <= frameId"
|
||||||
|
|
||||||
|
if (oldEdFrameId > frameId) {
|
||||||
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
|
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
|
||||||
if (null != arrIdx) {
|
if (null != arrIdx) {
|
||||||
this.eles[arrIdx] = item;
|
this.eles[arrIdx] = item;
|
||||||
return window.RING_BUFF_CONSECUTIVE_SET;
|
return [window.RING_BUFF_CONSECUTIVE_SET, oldStFrameId, oldEdFrameId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When "null == arrIdx", should it still be deemed consecutive if "frameId == edFrameId" prior to the reset?
|
// By now "this.edFrameId <= frameId"
|
||||||
let ret = window.RING_BUFF_CONSECUTIVE_SET;
|
let ret = window.RING_BUFF_CONSECUTIVE_SET;
|
||||||
if (this.edFrameId < frameId) {
|
if (oldEdFrameId < frameId) {
|
||||||
this.st = this.ed = 0;
|
this.st = this.ed = 0;
|
||||||
this.stFrameId = this.edFrameId = frameId;
|
this.stFrameId = this.edFrameId = frameId;
|
||||||
this.cnt = 0;
|
this.cnt = 0;
|
||||||
ret = window.RING_BUFF_NON_CONSECUTIVE_SET;
|
ret = window.RING_BUFF_NON_CONSECUTIVE_SET;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eles[this.ed] = item
|
// By now "this.edFrameId == frameId"
|
||||||
this.edFrameId++;
|
this.put(item);
|
||||||
this.cnt++;
|
|
||||||
this.ed++;
|
|
||||||
if (this.ed >= this.n) {
|
|
||||||
this.ed -= this.n; // Deliberately not using "%" operator for performance concern
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return [ret, oldStFrameId, oldEdFrameId];
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = RingBuffer;
|
module.exports = RingBuffer;
|
||||||
|
@@ -5,7 +5,6 @@ window.UPSYNC_MSG_ACT_PLAYER_CMD = 2;
|
|||||||
window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = 3;
|
window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = 3;
|
||||||
|
|
||||||
window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED = -98;
|
window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED = -98;
|
||||||
window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED = -97;
|
|
||||||
window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = -1;
|
window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = -1;
|
||||||
window.DOWNSYNC_MSG_ACT_BATTLE_START = 0;
|
window.DOWNSYNC_MSG_ACT_BATTLE_START = 0;
|
||||||
window.DOWNSYNC_MSG_ACT_HB_REQ = 1;
|
window.DOWNSYNC_MSG_ACT_HB_REQ = 1;
|
||||||
@@ -135,7 +134,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
|||||||
clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
|
clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
|
||||||
|
|
||||||
clientSession.onopen = function(evt) {
|
clientSession.onopen = function(evt) {
|
||||||
console.log("The WS clientSession is opened. clientSession.id=", clientSession.id);
|
console.log("The WS clientSession is opened.");
|
||||||
window.clientSession = clientSession;
|
window.clientSession = clientSession;
|
||||||
if (null == onopenCb) return;
|
if (null == onopenCb) return;
|
||||||
onopenCb();
|
onopenCb();
|
||||||
@@ -147,17 +146,14 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const resp = window.pb.protos.WsResp.decode(new Uint8Array(evt.data));
|
const resp = window.pb.protos.WsResp.decode(new Uint8Array(evt.data));
|
||||||
|
// console.log(`Got non-empty onmessage decoded: resp.act=${resp.act}`);
|
||||||
switch (resp.act) {
|
switch (resp.act) {
|
||||||
case window.DOWNSYNC_MSG_ACT_HB_REQ:
|
case window.DOWNSYNC_MSG_ACT_HB_REQ:
|
||||||
window.handleHbRequirements(resp); // 获取boundRoomId并存储到localStorage
|
window.handleHbRequirements(resp);
|
||||||
break;
|
break;
|
||||||
case window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED:
|
case window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED:
|
||||||
mapIns.onPlayerAdded(resp.rdf);
|
mapIns.onPlayerAdded(resp.rdf);
|
||||||
break;
|
break;
|
||||||
case window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED:
|
|
||||||
// Deliberately left blank for now
|
|
||||||
mapIns.hideFindingPlayersGUI(resp.rdf);
|
|
||||||
break;
|
|
||||||
case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START:
|
case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START:
|
||||||
mapIns.onBattleReadyToStart(resp.rdf);
|
mapIns.onBattleReadyToStart(resp.rdf);
|
||||||
break;
|
break;
|
||||||
@@ -172,16 +168,10 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
|||||||
break;
|
break;
|
||||||
case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC:
|
case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC:
|
||||||
if (null == resp.inputFrameDownsyncBatch || 0 >= resp.inputFrameDownsyncBatch.length) {
|
if (null == resp.inputFrameDownsyncBatch || 0 >= resp.inputFrameDownsyncBatch.length) {
|
||||||
console.error(`Got empty inputFrameDownsyncBatch upon resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, the incoming resp=
|
console.error(`Got empty inputFrameDownsyncBatch upon resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, the incoming resp=${JSON.stringify(resp, null, 2)}`);
|
||||||
${JSON.stringify(resp, null, 2)}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const inputFrameIdConsecutive = (resp.inputFrameDownsyncBatch[0].inputFrameId == mapIns.lastAllConfirmedInputFrameId + 1);
|
mapIns.onRoomDownsyncFrame(resp.rdf, resp.inputFrameDownsyncBatch);
|
||||||
const renderFrameIdConsecutive = (resp.rdf.id <= mapIns.renderFrameId + mapIns.renderFrameIdLagTolerance);
|
|
||||||
console.warn(`Got resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, inputFrameIdConsecutive=${inputFrameIdConsecutive}, renderFrameIdConsecutive=${renderFrameIdConsecutive}`);
|
|
||||||
// The following order of execution is important
|
|
||||||
mapIns.onRoomDownsyncFrame(resp.rdf);
|
|
||||||
mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@@ -5082,6 +5082,7 @@ $root.protos = (function() {
|
|||||||
* @property {Object.<string,protos.PlayerDownsync>|null} [players] RoomDownsyncFrame players
|
* @property {Object.<string,protos.PlayerDownsync>|null} [players] RoomDownsyncFrame players
|
||||||
* @property {number|Long|null} [countdownNanos] RoomDownsyncFrame countdownNanos
|
* @property {number|Long|null} [countdownNanos] RoomDownsyncFrame countdownNanos
|
||||||
* @property {Array.<protos.MeleeBullet>|null} [meleeBullets] RoomDownsyncFrame meleeBullets
|
* @property {Array.<protos.MeleeBullet>|null} [meleeBullets] RoomDownsyncFrame meleeBullets
|
||||||
|
* @property {number|Long|null} [backendUnconfirmedMask] RoomDownsyncFrame backendUnconfirmedMask
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5133,6 +5134,14 @@ $root.protos = (function() {
|
|||||||
*/
|
*/
|
||||||
RoomDownsyncFrame.prototype.meleeBullets = $util.emptyArray;
|
RoomDownsyncFrame.prototype.meleeBullets = $util.emptyArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomDownsyncFrame backendUnconfirmedMask.
|
||||||
|
* @member {number|Long} backendUnconfirmedMask
|
||||||
|
* @memberof protos.RoomDownsyncFrame
|
||||||
|
* @instance
|
||||||
|
*/
|
||||||
|
RoomDownsyncFrame.prototype.backendUnconfirmedMask = $util.Long ? $util.Long.fromBits(0,0,true) : 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new RoomDownsyncFrame instance using the specified properties.
|
* Creates a new RoomDownsyncFrame instance using the specified properties.
|
||||||
* @function create
|
* @function create
|
||||||
@@ -5169,6 +5178,8 @@ $root.protos = (function() {
|
|||||||
if (message.meleeBullets != null && message.meleeBullets.length)
|
if (message.meleeBullets != null && message.meleeBullets.length)
|
||||||
for (var i = 0; i < message.meleeBullets.length; ++i)
|
for (var i = 0; i < message.meleeBullets.length; ++i)
|
||||||
$root.protos.MeleeBullet.encode(message.meleeBullets[i], writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
|
$root.protos.MeleeBullet.encode(message.meleeBullets[i], writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
|
||||||
|
if (message.backendUnconfirmedMask != null && Object.hasOwnProperty.call(message, "backendUnconfirmedMask"))
|
||||||
|
writer.uint32(/* id 5, wireType 0 =*/40).uint64(message.backendUnconfirmedMask);
|
||||||
return writer;
|
return writer;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5240,6 +5251,10 @@ $root.protos = (function() {
|
|||||||
message.meleeBullets.push($root.protos.MeleeBullet.decode(reader, reader.uint32()));
|
message.meleeBullets.push($root.protos.MeleeBullet.decode(reader, reader.uint32()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 5: {
|
||||||
|
message.backendUnconfirmedMask = reader.uint64();
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
reader.skipType(tag & 7);
|
reader.skipType(tag & 7);
|
||||||
break;
|
break;
|
||||||
@@ -5304,6 +5319,9 @@ $root.protos = (function() {
|
|||||||
return "meleeBullets." + error;
|
return "meleeBullets." + error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (message.backendUnconfirmedMask != null && message.hasOwnProperty("backendUnconfirmedMask"))
|
||||||
|
if (!$util.isInteger(message.backendUnconfirmedMask) && !(message.backendUnconfirmedMask && $util.isInteger(message.backendUnconfirmedMask.low) && $util.isInteger(message.backendUnconfirmedMask.high)))
|
||||||
|
return "backendUnconfirmedMask: integer|Long expected";
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5350,6 +5368,15 @@ $root.protos = (function() {
|
|||||||
message.meleeBullets[i] = $root.protos.MeleeBullet.fromObject(object.meleeBullets[i]);
|
message.meleeBullets[i] = $root.protos.MeleeBullet.fromObject(object.meleeBullets[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (object.backendUnconfirmedMask != null)
|
||||||
|
if ($util.Long)
|
||||||
|
(message.backendUnconfirmedMask = $util.Long.fromValue(object.backendUnconfirmedMask)).unsigned = true;
|
||||||
|
else if (typeof object.backendUnconfirmedMask === "string")
|
||||||
|
message.backendUnconfirmedMask = parseInt(object.backendUnconfirmedMask, 10);
|
||||||
|
else if (typeof object.backendUnconfirmedMask === "number")
|
||||||
|
message.backendUnconfirmedMask = object.backendUnconfirmedMask;
|
||||||
|
else if (typeof object.backendUnconfirmedMask === "object")
|
||||||
|
message.backendUnconfirmedMask = new $util.LongBits(object.backendUnconfirmedMask.low >>> 0, object.backendUnconfirmedMask.high >>> 0).toNumber(true);
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5377,6 +5404,11 @@ $root.protos = (function() {
|
|||||||
object.countdownNanos = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
|
object.countdownNanos = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
|
||||||
} else
|
} else
|
||||||
object.countdownNanos = options.longs === String ? "0" : 0;
|
object.countdownNanos = options.longs === String ? "0" : 0;
|
||||||
|
if ($util.Long) {
|
||||||
|
var long = new $util.Long(0, 0, true);
|
||||||
|
object.backendUnconfirmedMask = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
|
||||||
|
} else
|
||||||
|
object.backendUnconfirmedMask = options.longs === String ? "0" : 0;
|
||||||
}
|
}
|
||||||
if (message.id != null && message.hasOwnProperty("id"))
|
if (message.id != null && message.hasOwnProperty("id"))
|
||||||
object.id = message.id;
|
object.id = message.id;
|
||||||
@@ -5396,6 +5428,11 @@ $root.protos = (function() {
|
|||||||
for (var j = 0; j < message.meleeBullets.length; ++j)
|
for (var j = 0; j < message.meleeBullets.length; ++j)
|
||||||
object.meleeBullets[j] = $root.protos.MeleeBullet.toObject(message.meleeBullets[j], options);
|
object.meleeBullets[j] = $root.protos.MeleeBullet.toObject(message.meleeBullets[j], options);
|
||||||
}
|
}
|
||||||
|
if (message.backendUnconfirmedMask != null && message.hasOwnProperty("backendUnconfirmedMask"))
|
||||||
|
if (typeof message.backendUnconfirmedMask === "number")
|
||||||
|
object.backendUnconfirmedMask = options.longs === String ? String(message.backendUnconfirmedMask) : message.backendUnconfirmedMask;
|
||||||
|
else
|
||||||
|
object.backendUnconfirmedMask = options.longs === String ? $util.Long.prototype.toString.call(message.backendUnconfirmedMask) : options.longs === Number ? new $util.LongBits(message.backendUnconfirmedMask.low >>> 0, message.backendUnconfirmedMask.high >>> 0).toNumber(true) : message.backendUnconfirmedMask;
|
||||||
return object;
|
return object;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user