Skip to content

Commit

Permalink
Update custom data (#106)
Browse files Browse the repository at this point in the history
Co-authored-by: Koen Bollen <[email protected]>
  • Loading branch information
erikdubbelboer and koenbollen authored Jul 1, 2024
1 parent a24c718 commit 8dfca0a
Show file tree
Hide file tree
Showing 18 changed files with 483 additions and 29 deletions.
173 changes: 173 additions & 0 deletions features/custom-data.feature
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ Feature: customData on lobbies can be used for filtering and extra information
"h5yzwyizlwao"
],
"playerCount": 1,
"creator": "h5yzwyizlwao",
"public": true,
"maxPlayers": 0,
"customData": {
"gameMode": "deathmatch",
"map": "de_dust2"
},
"canUpdateBy": "creator",
"leader": "h5yzwyizlwao",
"term": 1
}
Expand All @@ -52,14 +54,185 @@ Feature: customData on lobbies can be used for filtering and extra information
"h5yzwyizlwao"
],
"playerCount": 2,
"creator": "h5yzwyizlwao",
"public": true,
"maxPlayers": 0,
"customData": {
"gameMode": "deathmatch",
"map": "de_dust2"
},
"canUpdateBy": "creator",
"leader": "h5yzwyizlwao",
"term": 1
}
]
"""


Scenario: The creator can edit a lobby
Given "blue" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "yellow" is connected as "3t3cfgcqup9e" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "yellow" creates a lobby with these settings:
"""json
{
"public": true,
"customData": {
"status": "open"
}
}
"""
And "yellow" receives the network event "lobby" with the argument "prb67ouj837u"

When "blue" requests lobbies with this filter:
"""json
{
"status": "open"
}
"""
Then "blue" should have received only these lobbies:
| code |
| prb67ouj837u |

When "yellow" updates the lobby with these settings:
"""json
{
"customData": {
"status": "started"
}
}
"""

When "blue" requests lobbies with this filter:
"""json
{
"status": "open"
}
"""
Then "blue" should have received only these lobbies:
| code |


Scenario: The creator can set can_update_by
Given "blue" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "blue" creates a lobby with these settings:
"""json
{
"public": true,
"canUpdateBy": "creator"
}
"""
And "blue" receives the network event "lobby" with the argument "19yrzmetd2bn7"

When "blue" updates the lobby with these settings:
"""json
{
"canUpdateBy": "none"
}
"""
Then "blue" fails to update the lobby with these settings:
"""json
{
"customData": {
"status": "started"
}
}
"""


Scenario: Other players can update the lobby if canUpdateBy is 'anyone'
Given "blue" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "yellow" is connected as "3t3cfgcqup9e" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "blue" creates a lobby with these settings:
"""json
{
"canUpdateBy": "anyone"
}
"""
And "blue" receives the network event "lobby" with the argument "prb67ouj837u"
And "yellow" connects to the lobby "prb67ouj837u"
And "yellow" receives the network event "lobby" with the argument "prb67ouj837u"

When "yellow" updates the lobby with these settings:
"""json
{
"customData": {
"status": "started"
}
}
"""
Then "yellow" receives the network event "lobbyUpdated" with the argument "prb67ouj837u"


Scenario: The creator can update the lobby when canUpdateBy is 'creator' and they are not the leader
Given "blue" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "yellow" is connected as "3t3cfgcqup9e" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "blue" creates a lobby with these settings:
"""json
{
"canUpdateBy": "creator"
}
"""
And "blue" receives the network event "lobby" with the argument "prb67ouj837u"
And "yellow" connects to the lobby "prb67ouj837u"
And "blue" disconnected from the signaling server
And "yellow" becomes the leader of the lobby
And "blue" receives the network event "signalingreconnected"

When "blue" updates the lobby with these settings:
"""json
{
"customData": {
"status": "started"
}
}
"""
Then "blue" receives the network event "lobbyUpdated" with the argument "prb67ouj837u"
And "yellow" receives the network event "lobbyUpdated" with the arguments:
"""json
[
"prb67ouj837u",
{
"code": "prb67ouj837u",
"peers": [
"3t3cfgcqup9e",
"h5yzwyizlwao"
],
"playerCount": 2,
"creator": "h5yzwyizlwao",
"public": false,
"maxPlayers": 0,
"customData": {
"status": "started"
},
"canUpdateBy": "creator",
"leader": "3t3cfgcqup9e",
"term": 2
}
]
"""


Scenario: The leader can update the lobby if they are not the creator
Given "blue" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "yellow" is connected as "3t3cfgcqup9e" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "blue" creates a lobby with these settings:
"""json
{
"canUpdateBy": "leader"
}
"""
And "blue" receives the network event "lobby" with the argument "prb67ouj837u"
And "yellow" connects to the lobby "prb67ouj837u"
And "blue" disconnects
And "blue" receives the network event "close"
And "yellow" becomes the leader of the lobby

When "yellow" updates the lobby with these settings:
"""json
{
"customData": {
"status": "started"
}
}
"""
Then "yellow" receives the network event "lobbyUpdated" with the argument "prb67ouj837u"
8 changes: 6 additions & 2 deletions features/leader.feature
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ Feature: Lobbies have a leader that can control the lobby
"h5yzwyizlwao"
],
"playerCount": 2,
"creator": "h5yzwyizlwao",
"public": false,
"maxPlayers": 0,
"customData": null,
"canUpdateBy": "creator",
"leader": "h5yzwyizlwao",
"term": 1
}
Expand All @@ -46,8 +48,8 @@ Feature: Lobbies have a leader that can control the lobby
Scenario: Joining an empty lobby makes you the leader
Given "blue" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And these lobbies exist:
| code | game | playerCount | public | custom_data |
| 1qva9vyurwbb | 4307bd86-e1df-41b8-b9df-e22afcf084bd | 0 | true | {"map": "de_nuke"} |
| code | game | playerCount | public | custom_data | creator |
| 1qva9vyurwbb | 4307bd86-e1df-41b8-b9df-e22afcf084bd | 0 | true | {"map": "de_nuke"} | foo |

When "blue" connects to the lobby "1qva9vyurwbb"
And "blue" receives the network event "lobby" with the arguments:
Expand All @@ -60,11 +62,13 @@ Feature: Lobbies have a leader that can control the lobby
"h5yzwyizlwao"
],
"playerCount": 1,
"creator": "foo",
"public": true,
"maxPlayers": 0,
"customData": {
"map": "de_nuke"
},
"canUpdateBy": "creator",
"leader": "h5yzwyizlwao",
"term": 1
}
Expand Down
24 changes: 24 additions & 0 deletions features/support/steps/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,27 @@ Given('{string} becomes the leader of the lobby', async function (this: World, p
throw new Error('player is not the leader')
}
})

When('{string} updates the lobby with these settings:', async function (this: World, playerName: string, settings: string) {
const player = this.players.get(playerName)
if (player == null) {
throw new Error('no such player')
}
const r = await player.network.setLobbySettings(JSON.parse(settings))
if (r !== true) {
throw new Error(`failed to update lobby: ${r.message}`)
}
})

When('{string} fails to update the lobby with these settings:', async function (this: World, playerName: string, settings: string) {
const player = this.players.get(playerName)
if (player == null) {
throw new Error('no such player')
}
try {
await player.network.setLobbySettings(JSON.parse(settings))
} catch (e) {
return // we expect this to fail
}
throw new Error('no error thrown')
})
2 changes: 1 addition & 1 deletion features/support/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface RecordedEvent {
eventPayload: IArguments
}

const allEvents = ['close', 'ready', 'lobby', 'connected', 'disconnected', 'reconnecting', 'reconnected', 'message', 'signalingerror', 'signalingreconnected', 'leader']
const allEvents = ['close', 'ready', 'lobby', 'connected', 'disconnected', 'reconnecting', 'reconnected', 'message', 'signalingerror', 'signalingreconnected', 'leader', 'lobbyUpdated']

export class Player {
public lastReceivedLobbies: LobbyListEntry[] = []
Expand Down
5 changes: 5 additions & 0 deletions features/support/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ AfterAll(function (this: World) {
// a quick workaround to make sure the process is killed neatly.
// source: https://github.com/node-webrtc/node-webrtc/issues/636#issuecomment-774171409
process.on('beforeExit', (code) => process.exit(code))

setTimeout(() => {
console.log('cucumber did not exit cleanly, forcing exit')
process.exit(0)
}, 1000).unref()
})

Before(function (this: World) {
Expand Down
4 changes: 4 additions & 0 deletions internal/signaling/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ func Handler(ctx context.Context, store stores.Store, cloudflare *cloudflare.Cre
util.ErrorAndDisconnect(ctx, conn, err)
}

if base.RequestID != "" {
ctx = util.WithRequestID(ctx, base.RequestID)
}

if peer.closedPacketReceived {
if base.Type != "disconnect" && base.Type != "disconnected" { // expected lingering packets after closure.
logger.Warn("received packet after close", zap.String("peer", peer.ID), zap.String("type", base.Type))
Expand Down
66 changes: 65 additions & 1 deletion internal/signaling/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ func (p *Peer) HandlePacket(ctx context.Context, typ string, raw []byte) error {
return fmt.Errorf("unable to handle packet: %w", err)
}

case "lobbyUpdate":
packet := LobbyUpdatePacket{}
if err := json.Unmarshal(raw, &packet); err != nil {
return fmt.Errorf("unable to unmarshal json: %w", err)
}
err = p.HandleUpdatePacket(ctx, packet)
if err != nil {
return fmt.Errorf("unable to handle packet: %w", err)
}

// case "leave":

case "connected": // TODO: Do we want to keep track of connections between peers?
Expand Down Expand Up @@ -325,6 +335,17 @@ func (p *Peer) HandleCreatePacket(ctx context.Context, packet CreatePacket) erro
return fmt.Errorf("already in a lobby %s:%s as %s", p.Game, p.Lobby, p.ID)
}

if packet.CanUpdateBy == "" {
packet.CanUpdateBy = stores.CanUpdateByCreator
} else if packet.CanUpdateBy != "" {
if packet.CanUpdateBy != stores.CanUpdateByCreator &&
packet.CanUpdateBy != stores.CanUpdateByLeader &&
packet.CanUpdateBy != stores.CanUpdateByAnyone &&
packet.CanUpdateBy != stores.CanUpdateByNone {
return fmt.Errorf("invalid canUpdateBy value")
}
}

attempts := 20
for ; attempts > 0; attempts-- {
switch packet.CodeFormat {
Expand All @@ -334,7 +355,7 @@ func (p *Peer) HandleCreatePacket(ctx context.Context, packet CreatePacket) erro
p.Lobby = util.GenerateLobbyCode(ctx)
}

err := p.store.CreateLobby(ctx, p.Game, p.Lobby, p.ID, packet.Public, packet.CustomData)
err := p.store.CreateLobby(ctx, p.Game, p.Lobby, p.ID, packet.Public, packet.CustomData, packet.CanUpdateBy)
if err != nil {
if err == stores.ErrLobbyExists {
continue
Expand Down Expand Up @@ -430,6 +451,49 @@ func (p *Peer) HandleJoinPacket(ctx context.Context, packet JoinPacket) error {
return nil
}

func (p *Peer) HandleUpdatePacket(ctx context.Context, packet LobbyUpdatePacket) error {
logger := logging.GetLogger(ctx)
if p.ID == "" {
return fmt.Errorf("peer not connected")
}
if p.Lobby == "" {
return fmt.Errorf("not in a lobby")
}
if packet.CanUpdateBy != nil {
if *packet.CanUpdateBy != stores.CanUpdateByCreator &&
*packet.CanUpdateBy != stores.CanUpdateByLeader &&
*packet.CanUpdateBy != stores.CanUpdateByAnyone &&
*packet.CanUpdateBy != stores.CanUpdateByNone {
return fmt.Errorf("invalid canUpdateBy value")
}
}

err := p.store.UpdateCustomData(ctx, p.Game, p.Lobby, p.ID, packet.Public, packet.CustomData, packet.CanUpdateBy)
if err != nil {
logger.Error("failed to update lobby", zap.Error(err), zap.Any("customData", packet.CustomData))
util.ReplyError(ctx, p.conn, fmt.Errorf("unable to update lobby: %v", err))
return nil
}

lobbyInfo, err := p.store.GetLobby(ctx, p.Game, p.Lobby)
if err != nil {
return err
}

data, err := json.Marshal(LobbyUpdatedPacket{
// Include the request ID for the peer that requested the update.
// Other peers will ignore this.
RequestID: packet.RequestID,

Type: "lobbyUpdated",
LobbyInfo: lobbyInfo,
})
if err != nil {
return err
}
return p.store.Publish(ctx, p.Game+p.Lobby, data)
}

// doLeaderElectionAndPublish will do a leader election and publish the result if a new leader was elected.
// It returns true if a new leader was elected, false if not.
func (p *Peer) doLeaderElectionAndPublish(ctx context.Context) (bool, error) {
Expand Down
Loading

0 comments on commit 8dfca0a

Please sign in to comment.