Compare commits

...

7 Commits

Author SHA1 Message Date
genxium
7b878ff947 Fixes for backend Golang select-multi-channel implementation. 2023-02-21 22:07:48 +08:00
genxium
c78c480f99 Enhanced UDP session resource management. 2023-02-21 15:21:15 +08:00
genxium
b50874f5c4 Enhanced RecvRingBuff in cpp. 2023-02-21 11:54:06 +08:00
genxium
f1db2972fd Updates for RecvRingBuff. 2023-02-20 08:53:06 +08:00
genxium
16c27b0ce0 Minor fix. 2023-02-19 21:26:49 +08:00
genxium
a44535cad2 Fixes for revival dynamics. 2023-02-19 21:05:25 +08:00
genxium
8b5a96e825 Enhanced exception handling on frontend. 2023-02-19 13:42:25 +08:00
19 changed files with 271 additions and 172 deletions

View File

@@ -4,9 +4,24 @@ ROOT_DIR=.
GOPROXY=https://goproxy.io
all: help
# To install `gojson` executable
# ```
# go install github.com/ChimeraCoder/gojson/gojson@latest
# ```
#
# OS detection reference https://stackoverflow.com/a/12099167
gen-constants:
gojson -pkg common -name constants -input common/constants.json -o common/constants_struct.go
ifeq ($(OS),Windows_NT)
sed -i 's/int64/int/g' common/constants_struct.go
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
sed -i '' -e 's/int64/int/g' common/constants_struct.go
else
sed -i 's/int64/int/g' common/constants_struct.go
endif
endif
run-test: build
ServerEnv=TEST ./$(PROJECTNAME)

View File

@@ -44,10 +44,10 @@
"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY": 2020,
"TRADE_CREATION_TOO_FREQUENTLY": 2021,
"MAP_NOT_UNLOCKED": 2022,
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
"SMS_CAPTCHA_NOT_MATCH": 2025,
"SAME_PLAYER_ALREADY_IN_SAME_ROOM": 2026,
"NOT_IMPLEMENTED_YET": 65535
},

View File

@@ -17,6 +17,7 @@ type constants struct {
RetCode struct {
ActiveWatchdog int `json:"ACTIVE_WATCHDOG"`
BattleStopped int `json:"BATTLE_STOPPED"`
ClientMismatchedRenderFrame int `json:"CLIENT_MISMATCHED_RENDER_FRAME"`
Duplicated int `json:"DUPLICATED"`
FailedToCreate int `json:"FAILED_TO_CREATE"`
FailedToDelete int `json:"FAILED_TO_DELETE"`
@@ -51,6 +52,7 @@ type constants struct {
PlayerNotAddableToRoom int `json:"PLAYER_NOT_ADDABLE_TO_ROOM"`
PlayerNotFound int `json:"PLAYER_NOT_FOUND"`
PlayerNotReaddableToRoom int `json:"PLAYER_NOT_READDABLE_TO_ROOM"`
SamePlayerAlreadyInSameRoom int `json:"SAME_PLAYER_ALREADY_IN_SAME_ROOM"`
SendEmailTimeout int `json:"SEND_EMAIL_TIMEOUT"`
SmsCaptchaNotMatch int `json:"SMS_CAPTCHA_NOT_MATCH"`
SmsCaptchaRequestedTooFrequently int `json:"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY"`

View File

@@ -3,6 +3,7 @@ module battle_srv
go 1.18
require (
dnmshared v0.0.0
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414
github.com/davecgh/go-spew v1.1.1
github.com/gin-contrib/cors v0.0.0-20180514151808-6f0a820f94be
@@ -19,14 +20,12 @@ require (
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713
go.uber.org/zap v1.9.1
google.golang.org/protobuf v1.28.1
dnmshared v0.0.0
jsexport v0.0.0
resolv v0.0.0
jsexport v0.0.0
resolv v0.0.0
)
require (
github.com/ChimeraCoder/gojson v1.0.0 // indirect
github.com/ChimeraCoder/gojson v1.1.0 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
github.com/githubnemo/CompileDaemon v1.0.0 // indirect
@@ -44,11 +43,11 @@ require (
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
gopkg.in/yaml.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
replace (
dnmshared => ../dnmshared
jsexport => ../jsexport
resolv => ../resolv_tailored
jsexport => ../jsexport
resolv => ../resolv_tailored
)

View File

@@ -2,6 +2,8 @@ github.com/ByteArena/box2d v1.0.2 h1:f7f9KEQWhCs1n516DMLzi5w6u0MeeE78Mes4fWMcj9k
github.com/ByteArena/box2d v1.0.2/go.mod h1:LzEuxY9iCz+tskfWCY3o0ywYBRafDDugdSj+/YGI6sE=
github.com/ChimeraCoder/gojson v1.0.0 h1:gAYKGTV+xfQ4+l/4C/nazPbiQDUidG0G3ukAJnE7LNE=
github.com/ChimeraCoder/gojson v1.0.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw=
github.com/ChimeraCoder/gojson v1.1.0 h1:/6S8djl/jColpJGTYniA3xrqJWuKeyEozzPtpr5L4Pw=
github.com/ChimeraCoder/gojson v1.1.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw=
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414 h1:VHdYPA0V0YgL97gdjHevN6IVPRX+fOoNMqcYvUAzwNU=
github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM=
github.com/Pallinder/go-randomdata v0.0.0-20180616180521-15df0648130a h1:0OnS8GR4uI3nau+f/juCZlAq+zCrsHXRJlENrUQ4eU8=
@@ -92,3 +94,5 @@ gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2G
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@@ -25,9 +25,16 @@ import (
"go.uber.org/zap"
"net"
// _ "net/http/pprof"
)
func main() {
/*
// Only used for profiling
go func() {
http.ListenAndServe("0.0.0.0:6060", nil)
}()
*/
MustParseConfig()
MustParseConstants()
storage.Init()

View File

@@ -174,16 +174,16 @@ func (pR *Room) updateScore() {
pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State)
}
func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) bool {
func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) int {
playerId := pPlayerFromDbInit.Id
// TODO: Any thread-safety concern for accessing "pR" here?
if RoomBattleStateIns.IDLE != pR.State && RoomBattleStateIns.WAITING != pR.State {
Logger.Warn("AddPlayerIfPossible error, roomState:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
return false
return Constants.RetCode.PlayerNotAddableToRoom
}
if _, existent := pR.Players[playerId]; existent {
Logger.Warn("AddPlayerIfPossible error, existing in the room.PlayersDict:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
return false
return Constants.RetCode.SamePlayerAlreadyInSameRoom
}
defer pR.onPlayerAdded(playerId, speciesId)
@@ -210,19 +210,19 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, se
})
newWatchdog.Stop()
pR.PlayerActiveWatchdogDict[playerId] = newWatchdog
return true
return Constants.RetCode.Ok
}
func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) bool {
func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *websocket.Conn, signalToCloseConnOfThisPlayer SignalToCloseConnCbType) int {
playerId := pTmpPlayerInstance.Id
// TODO: Any thread-safety concern for accessing "pR" and "pEffectiveInRoomPlayerInstance" here?
if RoomBattleStateIns.PREPARE != pR.State && RoomBattleStateIns.WAITING != pR.State && RoomBattleStateIns.IN_BATTLE != pR.State && RoomBattleStateIns.IN_SETTLEMENT != pR.State && RoomBattleStateIns.IN_DISMISSAL != pR.State {
Logger.Warn("ReAddPlayerIfPossible error due to roomState:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
return false
return Constants.RetCode.PlayerNotReaddableToRoom
}
if _, existent := pR.Players[playerId]; !existent {
Logger.Warn("ReAddPlayerIfPossible error due to player nonexistent for room:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
return false
return Constants.RetCode.PlayerNotReaddableToRoom
}
/*
* WARNING: The "pTmpPlayerInstance *Player" used here is a temporarily constructed
@@ -251,7 +251,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
}) // For ReAdded player the new watchdog starts immediately
Logger.Warn("ReAddPlayerIfPossible finished.", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("joinIndex", pEffectiveInRoomPlayerInstance.JoinIndex), zap.Any("playerBattleState", pEffectiveInRoomPlayerInstance.BattleState), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("AckingFrameId", pEffectiveInRoomPlayerInstance.AckingFrameId), zap.Any("AckingInputFrameId", pEffectiveInRoomPlayerInstance.AckingInputFrameId), zap.Any("LastSentInputFrameId", pEffectiveInRoomPlayerInstance.LastSentInputFrameId))
return true
return Constants.RetCode.Ok
}
func (pR *Room) ChooseStage() error {
@@ -483,7 +483,7 @@ func (pR *Room) StartBattle() {
*/
totalElapsedNanos := utils.UnixtimeNano() - battleStartedAt
nextRenderFrameId := int32((totalElapsedNanos + pR.dilutedRollbackEstimatedDtNanos - 1) / pR.dilutedRollbackEstimatedDtNanos) // fast ceiling
toSleepNanos := int64(0)
toSleepNanos := int64(pR.dilutedRollbackEstimatedDtNanos >> 1) // Sleep half-frame time by default
if nextRenderFrameId > pR.RenderFrameId {
if 0 == pR.RenderFrameId {
// It's important to send kickoff frame iff "0 == pR.RenderFrameId && nextRenderFrameId > pR.RenderFrameId", otherwise it might send duplicate kickoff frames
@@ -515,7 +515,7 @@ func (pR *Room) StartBattle() {
pR.LastRenderFrameIdTriggeredAt = utils.UnixtimeNano()
elapsedInCalculation := (utils.UnixtimeNano() - stCalculation)
toSleepNanos = pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation // don't sleep if "nextRenderFrame == pR.RenderFrameId"
toSleepNanos = pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation
if elapsedInCalculation > pR.RollbackEstimatedDtNanos {
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, RollbackEstimatedDtNanos=%v, dilutedRollbackEstimatedDtNanos=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, pR.RollbackEstimatedDtNanos, pR.dilutedRollbackEstimatedDtNanos))
}
@@ -544,13 +544,13 @@ func (pR *Room) StartBattle() {
}
select {
// [WARNING] DON'T put a "default" block here! Otherwise "for { select {... default: } }" pattern would NEVER block on empty channel and thus consume a lot of CPU time unnecessarily!
case inputsBufferSnapshot := <-playerDownsyncChan:
pR.downsyncToSinglePlayer(playerId, player, inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, inputsBufferSnapshot.ToSendInputFrameDownsyncs, inputsBufferSnapshot.ShouldForceResync)
//Logger.Info(fmt.Sprintf("Sent inputsBufferSnapshot(refRenderFrameId:%d, unconfirmedMask:%v) to for (roomId: %d, playerId:%d)#2", inputsBufferSnapshot.RefRenderFrameId, inputsBufferSnapshot.UnconfirmedMask, pR.Id, playerId))
case inputsBufferSnapshot2 := <-playerSecondaryDownsyncChan:
pR.downsyncPeerInputFrameUpsyncToSinglePlayer(playerId, player, inputsBufferSnapshot2.ToSendInputFrameDownsyncs, inputsBufferSnapshot2.PeerJoinIndex)
//Logger.Info(fmt.Sprintf("Sent secondary inputsBufferSnapshot to for (roomId: %d, playerId:%d)#2", pR.Id, playerId))
default:
}
}
}

View File

@@ -188,34 +188,33 @@ func Serve(c *gin.Context) {
}()
Logger.Debug("Acquired RoomHeapMux for player:", zap.Any("playerId", playerId))
// Logger.Info("The RoomHeapManagerIns has:", zap.Any("addr", fmt.Sprintf("%p", models.RoomHeapManagerIns)), zap.Any("size", len(*(models.RoomHeapManagerIns))))
playerSuccessfullyAddedToRoom := false
playerRoomRelation := Constants.RetCode.UnknownError
if 0 < boundRoomId {
if tmpPRoom, existent := (*models.RoomMapManagerIns)[int32(boundRoomId)]; existent {
pRoom = tmpPRoom
res := pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if !res {
playerRoomRelation = pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if Constants.RetCode.Ok != playerRoomRelation {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forBoundRoomId", boundRoomId))
} else {
playerSuccessfullyAddedToRoom = true
}
}
} else if 0 < expectedRoomId {
if tmpRoom, existent := (*models.RoomMapManagerIns)[int32(expectedRoomId)]; existent {
pRoom = tmpRoom
if pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true
} else if pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer) {
playerSuccessfullyAddedToRoom = true
} else {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
playerSuccessfullyAddedToRoom = false
playerRoomRelation = pRoom.ReAddPlayerIfPossible(pPlayer, conn, signalToCloseConnOfThisPlayer)
if Constants.RetCode.Ok != playerRoomRelation {
playerRoomRelation = pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
}
if Constants.RetCode.Ok != playerRoomRelation {
Logger.Warn("Failed to get:\n", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Any("forExpectedRoomId", expectedRoomId))
}
}
}
if false == playerSuccessfullyAddedToRoom {
if Constants.RetCode.SamePlayerAlreadyInSameRoom == playerRoomRelation {
signalToCloseConnOfThisPlayer(playerRoomRelation, fmt.Sprintf("playerId == %v is already in a room, this account is possibly stolen!", playerId))
}
if Constants.RetCode.Ok != playerRoomRelation {
defer func() {
if pRoom != nil {
heap.Push(models.RoomHeapManagerIns, pRoom)
@@ -229,9 +228,9 @@ func Serve(c *gin.Context) {
} else {
pRoom = tmpRoom
Logger.Info("Successfully popped:\n", zap.Any("roomId", pRoom.Id), zap.Any("forPlayerId", playerId))
res := pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
if !res {
signalToCloseConnOfThisPlayer(Constants.RetCode.PlayerNotAddableToRoom, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
playerRoomRelation = pRoom.AddPlayerIfPossible(pPlayer, speciesId, conn, signalToCloseConnOfThisPlayer)
if Constants.RetCode.Ok != playerRoomRelation {
signalToCloseConnOfThisPlayer(playerRoomRelation, fmt.Sprintf("AddPlayerIfPossible returns false for roomId == %v, playerId == %v!", pRoom.Id, playerId))
}
}
}

View File

@@ -109,6 +109,7 @@ var constants = {
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
"SMS_CAPTCHA_NOT_MATCH": 2025,
"SAME_PLAYER_ALREADY_IN_SAME_ROOM": 2026,
"NOT_IMPLEMENTED_YET": 65535
},

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="40" tilewidth="16" tileheight="16" infinite="0" nextlayerid="8" nextobjectid="138">
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="64" tilewidth="16" tileheight="16" infinite="0" nextlayerid="9" nextobjectid="138">
<tileset firstgid="1" source="tiles0.tsx"/>
<tileset firstgid="65" source="tiles1.tsx"/>
<tileset firstgid="129" source="tiles2.tsx"/>
<layer id="7" name="Ground" width="128" height="40">
<layer id="8" name="Ground" width="128" height="64">
<data encoding="base64" compression="zlib">
eJzt2k0OgjAQQGECccMOo+5JvIlx484LeP9jaAxNSEOhlMGZOG/xbfyl81rc2FRV1QAAAAAAAAAAAADAD9zxpd1Bs7/2NWjzPAPPa2cGvtfODHyvnRn4XjszKFv7KdOW6zoKvYb+9vpf6W+G5f5zfZeep7/82kM3qf6Hj25C3DhFYwb/pqT/VlKfQ///7z91f+g2fj/9y/tLWbqfp7rvsQ/ob0tue6l9YHEGlvv3Qq+hv77x2tuBdv/SPVC6P7z3v0XCc/HjQZ8p9X5rwgy0W2j1rwfh/L8+Hgm1gNQZlfjsku/2fv7j/uPfgPFjUv010T/d/xH1niLd47KDNfcc+s/3aRdml2Opv/R+ov+6/ufB3Ny2nP+5PbD3+c/Zn/RP95f8zaW/Pdr/u7ZCu4Nm/6dz9Pdtz/5vZSq2hg==
eJzt2j1uwkAQgFEESkNHlKRHyk2iNHRcIPc/RlCEJWSxtrHHmRX7itfw651v1zTsNpvNDgAAAAAAAOAffPMnu0Nm/+xryNbyDFpeuxm0vXYzaHvtZtD22s1g3trfJlpyXa9Br9G/vv6f+lej5v5Dfcee1z9+7V23qP4vF4c7+o1LMmbwbOb0Xyrqc/R//v737g+Hhd+v//z+Ucbu56Xua+wD/esytX3UPqhxBjX3Pwa9Rv98t2vfX2X3n7sH5u6P1vt/9XTP9R/vHCcqvb823QyyW2T131515//n4lSwDVA6oxGfPee7Wz///f63vwG3j0X1z6R/uf+p1/ue6B4fK3jknqP/cJ/9yOymGOsfvZ/0f6z/+9XQ3Jac/6E9sPb5n7I/9S/3j/zN1b8+2f+7rkV2h8z+58bp37Y1+x8qlz37WmR30F9//fXXX38AAAAAAAAAAAAg1y8jXMSV
</data>
</layer>
<objectgroup id="1" name="PlayerStartingPos">

View File

@@ -22,6 +22,9 @@
},
{
"__id__": 5
},
{
"__id__": 11
}
],
"_active": true,
@@ -192,102 +195,28 @@
"fileId": "ab6G+s0otA4rXhUsO3czRN",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "VerticalLayout",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 6
},
{
"__id__": 12
}
],
"_active": true,
"_components": [
{
"__id__": 53
},
{
"__id__": 54
}
],
"_prefab": {
"__id__": 55
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 960,
"height": 265
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-9.924,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "exitButton",
"_objFlags": 0,
"_parent": {
"__id__": 5
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 6
},
{
"__id__": 7
},
{
"__id__": 8
},
{
"__id__": 10
"__id__": 9
}
],
"_prefab": {
"__id__": 11
"__id__": 10
},
"_opacity": 255,
"_color": {
@@ -312,7 +241,7 @@
"ctor": "Float64Array",
"array": [
-379.577,
100.5,
90.576,
0,
0,
0,
@@ -341,7 +270,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 6
"__id__": 5
},
"_enabled": true,
"_materials": [
@@ -375,7 +304,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 6
"__id__": 5
},
"_enabled": true,
"_normalMaterial": null,
@@ -384,7 +313,7 @@
"zoomScale": 1.2,
"clickEvents": [
{
"__id__": 9
"__id__": 8
}
],
"_N$interactable": true,
@@ -440,7 +369,7 @@
"hoverSprite": null,
"_N$disabledSprite": null,
"_N$target": {
"__id__": 6
"__id__": 5
},
"_id": ""
},
@@ -459,7 +388,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 6
"__id__": 5
},
"_enabled": true,
"alignMode": 1,
@@ -489,15 +418,86 @@
"asset": {
"__uuid__": "dc804c5c-ff76-445e-ac69-52269055c3c5"
},
"fileId": "1cUg34ZtdK9JfdGIG+lpdF",
"fileId": "3cdlb7LxhMzLDzxdQv8Z/x",
"sync": false
},
{
"__type__": "cc.Node",
"_name": "VerticalLayout",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 12
}
],
"_active": true,
"_components": [
{
"__id__": 53
},
{
"__id__": 54
}
],
"_prefab": {
"__id__": 55
},
"_opacity": 255,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_contentSize": {
"__type__": "cc.Size",
"width": 960,
"height": 137
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_trs": {
"__type__": "TypedArray",
"ctor": "Float64Array",
"array": [
0,
-68.939,
0,
0,
0,
0,
1,
1,
1,
1
]
},
"_eulerAngles": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_skewX": 0,
"_skewY": 0,
"_is3DNode": false,
"_groupIndex": 0,
"groupIndex": 0,
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "HorizontalLayout",
"_objFlags": 0,
"_parent": {
"__id__": 5
"__id__": 11
},
"_children": [
{
@@ -545,7 +545,7 @@
"ctor": "Float64Array",
"array": [
0,
-64,
0,
0,
0,
0,
@@ -1953,7 +1953,7 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 5
"__id__": 11
},
"_enabled": true,
"_materials": [
@@ -1983,13 +1983,13 @@
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 5
"__id__": 11
},
"_enabled": true,
"_layoutSize": {
"__type__": "cc.Size",
"width": 960,
"height": 265
"height": 137
},
"_resize": 1,
"_N$layoutType": 2,
@@ -2040,7 +2040,7 @@
"__id__": 43
},
"exitBtnNode": {
"__id__": 6
"__id__": 5
},
"_id": ""
},

View File

@@ -536,7 +536,7 @@
"array": [
0,
0,
216.65450766436658,
216.05530045313827,
0,
0,
0,

View File

@@ -461,7 +461,7 @@
"array": [
0,
0,
209.66956379694378,
210.43877906529718,
0,
0,
0,

View File

@@ -293,7 +293,7 @@ cc.Class({
}
},
popupSimplePressToGo(labelString, hideYesButton) {
popupSimplePressToGo(labelString, hideYesButton, additionalOnDismissalCb) {
const self = this;
self.state = ALL_MAP_STATES.SHOWING_MODAL_POPUP;
@@ -306,6 +306,9 @@ cc.Class({
const postDismissalByYes = () => {
self.transitToState(ALL_MAP_STATES.VISUAL);
canvasNode.removeChild(simplePressToGoDialogNode);
if (additionalOnDismissalCb) {
additionalOnDismissalCb();
}
}
simplePressToGoDialogNode.getChildByName("Hint").getComponent(cc.Label).string = labelString;
yesButton.once("click", simplePressToGoDialogScriptIns.dismissDialog.bind(simplePressToGoDialogScriptIns, postDismissalByYes));

View File

@@ -288,13 +288,21 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
`);
}
break;
case constants.RET_CODE.SAME_PLAYER_ALREADY_IN_SAME_ROOM:
mapIns.popupSimplePressToGo("You just logged into a conflicting account, please use a different account to retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
break;
case constants.RET_CODE.PLAYER_NOT_ADDABLE_TO_ROOM:
case constants.RET_CODE.PLAYER_NOT_READDABLE_TO_ROOM:
window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); // To favor the player to join other rooms
mapIns.onManualRejoinRequired("Couldn't join any room at the moment, please retry");
mapIns.popupSimplePressToGo("Couldn't join any room at the moment, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
break;
case constants.RET_CODE.ACTIVE_WATCHDOG:
mapIns.onManualRejoinRequired("Disconnected due to long-time inactivity, please rejoin");
mapIns.popupSimplePressToGo("Couldn't join any room at the moment, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
break;
case constants.RET_CODE.UNKNOWN_ERROR:
case constants.RET_CODE.MYSQL_ERROR:
@@ -305,9 +313,19 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}
`);
}
window.clearLocalStorageAndBackToLoginScene(true);
break;
mapIns.popupSimplePressToGo("Disconnected unexpectedly, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
default:
if (cc.sys.isNative) {
// [WARNING] This could be a BUG in CocosCreator JSB implementation of WebSocket client, the "evt.code" is always "undefined" in the "onclose" callback!
if (window.ALL_BATTLE_STATES.IN_SETTLEMENT != mapIns.battleState && window.ALL_BATTLE_STATES.IN_DISMISSAL != mapIns.battleState) {
mapIns.popupSimplePressToGo("Disconnected unexpectedly, please retry", false, () => {
window.clearLocalStorageAndBackToLoginScene(true);
});
}
}
break;
}
};

File diff suppressed because one or more lines are too long

View File

@@ -32,20 +32,43 @@ SendWork* SendRingBuff::pop() {
}
// Recving
bool isFullWithLoadedVals(int n, int oldCnt, int oldSt, int oldEd) {
return (n <= oldCnt && oldEd == oldSt) || (n > oldCnt && 0 < oldCnt && oldEd == oldSt);
}
void RecvRingBuff::put(char* newBytes, size_t newBytesLen) {
RecvWork* slotEle = (&eles[ed.load()]); // Save for later update
// "RecvRingBuff.ed" is only accessed in "UvRecvThread", thus the order of it relative to the other two is not important.
int oldEd = ed.load();
// We want to increase the success rate of "pop()" if it's being executed by "GameThread/pollUdpRecvRingBuff", thus the below order of loading is IMPORTANT, i.e. load "cnt" first because it's decremented earlier than "st" being incremented.
int oldCnt = cnt.load();
/*
[WARNING]
Note that "RecvRingBuff.st" might have decremented in "GameThread" by a successful "pop()" between "cnt.load()" and "st.load()" here in "UvRecvThread"! Therefore "n <= oldCnt" doesn't necessarily imply "oldEd == oldSt"!
*/
int oldSt = st.load(); // Used to guard against "cnt decremented in pop(...), but st not yet incremented and thus return value not yet copied to avoid contamination"
int tried = 0;
while (n <= oldCnt && !ed.compare_exchange_weak(oldSt, oldSt) && 3 > tried) {
/*
1. When "n <= oldCnt", it might still be true "oldEd != oldSt" (see the note above);
2. When "n > oldCnt", it might still be true that "oldEd == oldSt" if "pop()" hasn't successfully incremented "st" due to any reason;
3. When "oldEd == oldSt", it doesn't imply anything useful, because any of the following could be true
- a. "n <= oldCnt", i.e. the ringbuff is full
- b. "n > oldCnt && 0 < oldCnt" during the execution of "pop()", i.e. the ringbuff is still effectively full
- c. "n > oldCnt && 0 == oldCnt", i.e. the ringbuff is empty
*/
bool isFull = isFullWithLoadedVals(n, oldCnt, oldSt, oldEd);
while (isFull && 3 > tried) {
// Make room for the new element
this->pop(NULL);
oldCnt = cnt.load(); // If "pop()" above failed, it'd only be due to concurrent calls to "pop()", either way the updated "cnt" should be good to go
oldSt = st.load();
isFull = isFullWithLoadedVals(n, oldCnt, oldSt, oldEd);
++tried;
}
if (n <= oldCnt && !ed.compare_exchange_weak(oldSt, oldSt) && 3 == tried) {
if (isFull && 3 == tried) {
// Failed silently, UDP packet can be dropped.
return;
}
@@ -56,11 +79,13 @@ void RecvRingBuff::put(char* newBytes, size_t newBytesLen) {
}
// No need to compare-and-swap, only "UvRecvThread" will access "RecvRingBuff.ed".
ed++;
if (ed >= n) {
ed -= n; // Deliberately not using "%" operator for performance concern
int newEd = oldEd+1;
if (newEd >= n) {
newEd -= n; // Deliberately not using "%" operator for performance concern
}
ed.compare_exchange_weak(oldEd, newEd); // Definitely succeeds because "RecvRingBuff.ed" is only accessed in "UvRecvThread"
// Only increment cnt when the putting of new element is fully done.
cnt++;
}

View File

@@ -160,7 +160,6 @@ void startRecvLoop(void* arg) {
int uvCloseRet = uv_loop_close(l);
CCLOG("UDP recv loop is closed in UvRecvThread, uvCloseRet=%d", uvCloseRet);
uv_mutex_destroy(&recvRingBuffLock);
}
void startSendLoop(void* arg) {
@@ -174,7 +173,6 @@ void startSendLoop(void* arg) {
int uvCloseRet = uv_loop_close(l);
CCLOG("UDP send loop is closed in UvSendThread, uvCloseRet=%d", uvCloseRet);
uv_mutex_destroy(&sendRingBuffLock);
}
int initSendLoop(struct sockaddr const* pUdpAddr) {
@@ -189,9 +187,6 @@ int initSendLoop(struct sockaddr const* pUdpAddr) {
uv_mutex_init(&sendRingBuffLock);
sendRingBuff = new SendRingBuff(maxBuffedMsgs);
uv_mutex_init(&recvRingBuffLock);
recvRingBuff = new RecvRingBuff(maxBuffedMsgs);
uv_async_init(sendLoop, &uvSendLoopStopSig, _onUvStopSig);
uv_async_init(sendLoop, &uvSendLoopTriggerSig, _onUvSthNewToSend);
@@ -208,6 +203,9 @@ bool initRecvLoop(struct sockaddr const* pUdpAddr) {
CCLOGERROR("Failed to bind recv; recvSockInitRes=%d, recvbindRes=%d, reason=%s", recvSockInitRes, recvbindRes, uv_strerror(recvbindRes));
exit(-1);
}
uv_mutex_init(&recvRingBuffLock);
recvRingBuff = new RecvRingBuff(maxBuffedMsgs);
uv_udp_recv_start(udpRecvSocket, _allocBuffer, _onRead);
uv_async_init(recvLoop, &uvRecvLoopStopSig, _onUvStopSig);
@@ -248,20 +246,41 @@ bool DelayNoMore::UdpSession::openUdpSession(int port) {
bool DelayNoMore::UdpSession::closeUdpSession() {
CCLOG("About to close udp session and dealloc all resources...");
/*
[WARNING] It's possible that "closeUdpSession" is called when "openUdpSession" was NEVER CALLED, thus we have to avoid program crash in this case.
uv_async_send(&uvSendLoopStopSig);
CCLOG("Signaling UvSendThread to end in GameThread...");
uv_thread_join(&sendTid);
free(udpSendSocket);
free(sendLoop);
delete sendRingBuff;
In general one shouldn't just check the state of "sendTid" by whether or not "NULL == sendLoop", but in this particular game, both "openUdpSession" and "closeUdpSession" are only called from "GameThread", no thread-safety concern here, i.e. if "openUdpSession" was ever called earlier, then "sendLoop" wouldn't be NULL when "closeUdpSession" is later called.
*/
if (NULL != sendLoop) {
uv_async_send(&uvSendLoopStopSig);
CCLOG("Signaling UvSendThread to end in GameThread...");
uv_thread_join(&sendTid);
free(udpSendSocket);
free(sendLoop);
delete sendRingBuff;
udpSendSocket = NULL;
sendLoop = NULL;
sendRingBuff = NULL;
uv_async_send(&uvRecvLoopStopSig); // The few if not only guaranteed thread safe utility of libuv :) See http://docs.libuv.org/en/v1.x/async.html#c.uv_async_send
CCLOG("Signaling UvRecvThread to end in GameThread...");
uv_thread_join(&recvTid);
free(udpRecvSocket);
free(recvLoop);
delete recvRingBuff;
uv_mutex_destroy(&sendRingBuffLock);
}
if (NULL != recvLoop) {
uv_async_send(&uvRecvLoopStopSig); // The few if not only guaranteed thread safe utility of libuv :) See http://docs.libuv.org/en/v1.x/async.html#c.uv_async_send
CCLOG("Signaling UvRecvThread to end in GameThread...");
uv_thread_join(&recvTid);
free(udpRecvSocket);
free(recvLoop);
delete recvRingBuff;
udpRecvSocket = NULL;
recvLoop = NULL;
recvRingBuff = NULL;
uv_mutex_destroy(&recvRingBuffLock);
}
CCLOG("Closed udp session and dealloc all resources in GameThread...");
@@ -356,8 +375,10 @@ bool DelayNoMore::UdpSession::pollUdpRecvRingBuff() {
while (true) {
RecvWork f;
bool res = recvRingBuff->pop(&f);
if (!res) return false;
if (!res) {
// Deliberately returning "true" here to prevent "jswrapper" from printing "Failed to invoke Xxx..." too frequently
return true;
}
// [WARNING] Declaring "AutoHandleScope" is critical here, otherwise "onUdpMessageCb.toObject()" wouldn't be recognized as a function of the ScriptEngine!
se::AutoHandleScope hs;
// [WARNING] Use of the "ScriptEngine" is only allowed in "GameThread a.k.a. CocosThread"!
@@ -381,4 +402,4 @@ bool DelayNoMore::UdpSession::pollUdpRecvRingBuff() {
}
//uv_mutex_unlock(&recvRingBuffLock);
return true;
}
}

View File

@@ -698,6 +698,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
} else if stoppingFromWalking {
thatPlayerInNextFrame.FramesToRecover = chConfig.InertiaFramesToRecover
} else {
// Updates CharacterState and thus the animation to make user see graphical feedback asap.
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
thatPlayerInNextFrame.FramesToRecover = ((chConfig.InertiaFramesToRecover >> 1) + (chConfig.InertiaFramesToRecover >> 2))
}
} else {
@@ -959,6 +961,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
thatPlayerInNextFrame.VelY = 0
thatPlayerInNextFrame.VelX = 0
if ATK_CHARACTER_STATE_DYING == thatPlayerInNextFrame.CharacterState {
// No update needed for Dying
} else if ATK_CHARACTER_STATE_BLOWN_UP1 == thatPlayerInNextFrame.CharacterState {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_LAY_DOWN1
thatPlayerInNextFrame.FramesToRecover = chConfig.LayDownFramesToRecover
@@ -977,7 +981,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
// landedOnGravityPushback not fallStopping, could be in LayDown or GetUp or Dying
if _, existent := nonAttackingSet[thatPlayerInNextFrame.CharacterState]; existent {
if ATK_CHARACTER_STATE_DYING == thatPlayerInNextFrame.CharacterState {
// No update needed for Dying
thatPlayerInNextFrame.VelY = 0
thatPlayerInNextFrame.VelX = 0
} else if ATK_CHARACTER_STATE_LAY_DOWN1 == thatPlayerInNextFrame.CharacterState {
if 0 == thatPlayerInNextFrame.FramesToRecover {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_GET_UP1
@@ -1074,15 +1079,15 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
}
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.Hp -= bulletStaticAttr.Damage
pushbackVelX, pushbackVelY := xfac*bulletStaticAttr.PushbackVelX, bulletStaticAttr.PushbackVelY
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if 0 >= atkedPlayerInNextFrame.Hp {
// [WARNING] We don't have "dying in air" animation for now, and for better graphical recognition, play the same dying animation even in air
atkedPlayerInNextFrame.Hp = 0
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_DYING
atkedPlayerInNextFrame.FramesToRecover = DYING_FRAMES_TO_RECOVER
} else {
pushbackVelX, pushbackVelY := xfac*bulletStaticAttr.PushbackVelX, bulletStaticAttr.PushbackVelY
atkedPlayerInNextFrame.VelX = pushbackVelX
atkedPlayerInNextFrame.VelY = pushbackVelY
if bulletStaticAttr.BlowUp {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
} else {