Skip to content

Commit 85e107e

Browse files
committed
Feature: Use proposal groups to better handle majority and accepting events
1 parent d2f0928 commit 85e107e

21 files changed

+1150
-808
lines changed
Lines changed: 39 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,57 @@
11
package engine
22

33
import (
4-
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/geom"
54
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/state"
65
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/statemachine"
76
"log"
87
"math"
9-
"sort"
108
"time"
119
)
1210

13-
const similarLocationTolerance = 0.5
14-
1511
func (e *Engine) processProposals() {
1612

17-
deadline := e.timeProvider().Add(-e.gameConfig.AutoRefProposalTimeout)
18-
matchingProposals := collectMatchingProposals(e.currentState.GameEventProposals, deadline)
19-
numProposals := len(matchingProposals)
20-
if numProposals == 0 {
21-
return
22-
}
23-
24-
sort.Slice(matchingProposals, func(i, j int) bool {
25-
return goTime(matchingProposals[i].Timestamp).Before(goTime(matchingProposals[j].Timestamp))
26-
})
13+
now := e.timeProvider()
14+
proposalTimeout := e.gameConfig.AutoRefProposalTimeout
15+
minTime := now.Add(-proposalTimeout)
2716

28-
firstGameEvent := matchingProposals[0].GameEvent
29-
numAutoRefs := e.numAutoRefs(firstGameEvent)
30-
majority := int(math.Floor(float64(numAutoRefs) / 2.0))
31-
32-
if numProposals > majority {
33-
log.Printf("Majority of %v reached with %v out of %v for %v.", majority, numProposals, numAutoRefs, firstGameEvent.Type)
34-
e.Enqueue(&statemachine.Change{
35-
Change: &statemachine.Change_AcceptGameEventProposals{
36-
AcceptGameEventProposals: &statemachine.AcceptGameEventProposals{
37-
Proposals: matchingProposals,
17+
for i, group := range e.currentState.ProposalGroups {
18+
if *group.Accepted || groupHasRecentProposal(group, minTime) {
19+
continue
20+
}
21+
numProposals := uniqueOrigins(group)
22+
latestGameEvent := group.Proposals[len(group.Proposals)-1].GameEvent
23+
numAutoRefs := e.numAutoRefs(latestGameEvent)
24+
majority := int(math.Floor(float64(numAutoRefs) / 2.0))
25+
26+
if numProposals > majority {
27+
log.Printf("Majority of %v reached with %v out of %v for %v.", majority, numProposals, numAutoRefs, latestGameEvent.Type)
28+
groupId := uint32(i)
29+
acceptedBy := "Majority"
30+
e.Enqueue(&statemachine.Change{
31+
Change: &statemachine.Change_AcceptProposalGroup{
32+
AcceptProposalGroup: &statemachine.AcceptProposalGroup{
33+
GroupId: &groupId,
34+
AcceptedBy: &acceptedBy,
35+
},
3836
},
39-
},
40-
})
37+
})
38+
}
39+
}
40+
}
41+
42+
func uniqueOrigins(group *state.ProposalGroup) int {
43+
origins := map[string]bool{}
44+
for _, p := range group.Proposals {
45+
for _, o := range p.GameEvent.Origin {
46+
origins[o] = true
47+
}
4148
}
49+
return len(origins)
50+
}
51+
52+
func groupHasRecentProposal(group *state.ProposalGroup, minTime time.Time) bool {
53+
latestProposal := group.Proposals[len(group.Proposals)-1]
54+
return goTime(latestProposal.Timestamp).Before(minTime)
4255
}
4356

4457
func (e *Engine) numAutoRefs(gameEvent *state.GameEvent) (n int) {
@@ -53,99 +66,3 @@ func (e *Engine) numAutoRefs(gameEvent *state.GameEvent) (n int) {
5366
}
5467
return
5568
}
56-
57-
func collectMatchingProposals(events []*state.GameEventProposal, deadline time.Time) []*state.GameEventProposal {
58-
proposals := [][]*state.GameEventProposal{}
59-
for _, proposal := range events {
60-
if (proposal.Accepted != nil && *proposal.Accepted) ||
61-
goTime(proposal.Timestamp).After(deadline) {
62-
continue
63-
}
64-
if ok, pid := findGroup(proposal, proposals); ok {
65-
proposals[pid] = append(proposals[pid], proposal)
66-
continue
67-
}
68-
proposals = append(proposals, []*state.GameEventProposal{proposal})
69-
}
70-
71-
if len(proposals) == 0 {
72-
return []*state.GameEventProposal{}
73-
}
74-
75-
sort.SliceStable(proposals, func(i, j int) bool {
76-
return len(proposals[i]) > len(proposals[j])
77-
})
78-
79-
return proposals[0]
80-
}
81-
82-
func findGroup(proposal *state.GameEventProposal, proposals [][]*state.GameEventProposal) (bool, int) {
83-
for i, p := range proposals {
84-
if gameEventsSimilar(proposal.GameEvent, p[0].GameEvent) {
85-
return true, i
86-
}
87-
}
88-
return false, -1
89-
}
90-
91-
func gameEventsSimilar(e1, e2 *state.GameEvent) bool {
92-
if *e1.Type != *e2.Type {
93-
return false
94-
}
95-
// check similarity based on details for some events:
96-
switch *e1.Type {
97-
case state.GameEvent_BOT_TOO_FAST_IN_STOP:
98-
return similarTeam(e1.GetBotTooFastInStop().ByTeam, e2.GetBotTooFastInStop().ByTeam) &&
99-
similarBot(e1.GetBotTooFastInStop().ByBot, e2.GetBotTooFastInStop().ByBot)
100-
case state.GameEvent_DEFENDER_TOO_CLOSE_TO_KICK_POINT:
101-
return similarTeam(e1.GetDefenderTooCloseToKickPoint().ByTeam, e2.GetDefenderTooCloseToKickPoint().ByTeam) &&
102-
similarBot(e1.GetDefenderTooCloseToKickPoint().ByBot, e2.GetDefenderTooCloseToKickPoint().ByBot)
103-
case state.GameEvent_BOT_CRASH_DRAWN:
104-
return similarBot(e1.GetBotCrashDrawn().BotYellow, e2.GetBotCrashDrawn().BotYellow) &&
105-
similarBot(e1.GetBotCrashDrawn().BotBlue, e2.GetBotCrashDrawn().BotBlue) &&
106-
similarLocation(e1.GetBotCrashDrawn().Location, e2.GetBotCrashDrawn().Location, similarLocationTolerance)
107-
case state.GameEvent_BOT_CRASH_UNIQUE:
108-
return similarTeam(e1.GetBotCrashUnique().ByTeam, e2.GetBotCrashUnique().ByTeam) &&
109-
similarBot(e1.GetBotCrashUnique().Violator, e2.GetBotCrashUnique().Violator) &&
110-
similarBot(e1.GetBotCrashUnique().Victim, e2.GetBotCrashUnique().Victim) &&
111-
similarLocation(e1.GetBotCrashUnique().Location, e2.GetBotCrashUnique().Location, similarLocationTolerance)
112-
case state.GameEvent_BOT_PUSHED_BOT:
113-
return similarTeam(e1.GetBotPushedBot().ByTeam, e2.GetBotPushedBot().ByTeam) &&
114-
similarBot(e1.GetBotPushedBot().Violator, e2.GetBotPushedBot().Violator) &&
115-
similarBot(e1.GetBotPushedBot().Victim, e2.GetBotPushedBot().Victim) &&
116-
similarLocation(e1.GetBotPushedBot().Location, e2.GetBotPushedBot().Location, similarLocationTolerance)
117-
case state.GameEvent_DEFENDER_IN_DEFENSE_AREA:
118-
return similarTeam(e1.GetDefenderInDefenseArea().ByTeam, e2.GetDefenderInDefenseArea().ByTeam) &&
119-
similarBot(e1.GetDefenderInDefenseArea().ByBot, e2.GetDefenderInDefenseArea().ByBot)
120-
case state.GameEvent_ATTACKER_TOUCHED_BALL_IN_DEFENSE_AREA:
121-
return similarTeam(e1.GetAttackerTouchedBallInDefenseArea().ByTeam, e2.GetAttackerTouchedBallInDefenseArea().ByTeam) &&
122-
similarBot(e1.GetAttackerTouchedBallInDefenseArea().ByBot, e2.GetAttackerTouchedBallInDefenseArea().ByBot)
123-
case state.GameEvent_BOT_KICKED_BALL_TOO_FAST:
124-
return similarTeam(e1.GetBotKickedBallTooFast().ByTeam, e2.GetBotKickedBallTooFast().ByTeam)
125-
case state.GameEvent_BOT_DRIBBLED_BALL_TOO_FAR:
126-
return similarTeam(e1.GetBotDribbledBallTooFar().ByTeam, e2.GetBotDribbledBallTooFar().ByTeam)
127-
case state.GameEvent_ATTACKER_DOUBLE_TOUCHED_BALL:
128-
return similarTeam(e1.GetAttackerDoubleTouchedBall().ByTeam, e2.GetAttackerDoubleTouchedBall().ByTeam)
129-
case state.GameEvent_ATTACKER_TOO_CLOSE_TO_DEFENSE_AREA:
130-
return similarTeam(e1.GetAttackerTooCloseToDefenseArea().ByTeam, e2.GetAttackerTooCloseToDefenseArea().ByTeam) &&
131-
similarBot(e1.GetAttackerTooCloseToDefenseArea().ByBot, e2.GetAttackerTooCloseToDefenseArea().ByBot)
132-
case state.GameEvent_BOT_INTERFERED_PLACEMENT:
133-
return similarTeam(e1.GetBotInterferedPlacement().ByTeam, e2.GetBotInterferedPlacement().ByTeam) &&
134-
similarBot(e1.GetBotInterferedPlacement().ByBot, e2.GetBotInterferedPlacement().ByBot)
135-
}
136-
137-
// all others are considered similar based on the type only
138-
return true
139-
}
140-
141-
func similarTeam(t1, t2 *state.Team) bool {
142-
return (t1 == nil && t2 == nil) || (t1 != nil && t2 != nil && *t1 == *t2)
143-
}
144-
145-
func similarBot(b1, b2 *uint32) bool {
146-
return (b1 == nil && b2 == nil) || (b1 != nil && b2 != nil && *b1 == *b2)
147-
}
148-
149-
func similarLocation(l1, l2 *geom.Vector2, tolerance float64) bool {
150-
return (l1 == nil && l2 == nil) || (l1 != nil && l2 != nil && l1.DistanceTo(l2) <= tolerance)
151-
}

internal/app/engine/proposals.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ func (e *Engine) EnqueueGameEvent(gameEvent *state.GameEvent) {
3434
timestamp, _ := ptypes.TimestampProto(e.timeProvider())
3535
e.Enqueue(&statemachine.Change{
3636
Origin: &origin,
37-
Change: &statemachine.Change_AddProposedGameEvent{
38-
AddProposedGameEvent: &statemachine.AddProposedGameEvent{
39-
Proposal: &state.GameEventProposal{
37+
Change: &statemachine.Change_AddProposal{
38+
AddProposal: &statemachine.AddProposal{
39+
Proposal: &state.Proposal{
4040
Timestamp: timestamp,
4141
GameEvent: gameEvent,
4242
},

internal/app/publish/datamapper.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@ import (
88
"time"
99
)
1010

11-
func mapProposedGameEvents(events []*state.GameEventProposal) []*state.ProposedGameEvent {
12-
mappedEvents := make([]*state.ProposedGameEvent, len(events))
13-
for i, e := range events {
14-
proposer := ""
15-
if len(e.GameEvent.Origin) > 0 {
16-
proposer = e.GameEvent.Origin[0]
17-
}
18-
var validUntil uint64
19-
mappedEvents[i] = &state.ProposedGameEvent{
20-
ValidUntil: &validUntil, // required in protobuf ref msg... Add zero value
21-
ProposerId: &proposer,
22-
GameEvent: e.GameEvent,
11+
func mapProposals(groups []*state.ProposalGroup) []*state.ProposedGameEvent {
12+
var mappedEvents []*state.ProposedGameEvent
13+
for _, group := range groups {
14+
for _, proposal := range group.Proposals {
15+
proposer := ""
16+
if len(proposal.GameEvent.Origin) > 0 {
17+
proposer = proposal.GameEvent.Origin[0]
18+
}
19+
var validUntil uint64
20+
//noinspection GoDeprecation
21+
mappedEvents = append(mappedEvents, &state.ProposedGameEvent{
22+
ValidUntil: &validUntil, // required in protobuf ref msg... Add zero value
23+
ProposerId: &proposer,
24+
GameEvent: proposal.GameEvent,
25+
})
2326
}
2427
}
2528
return mappedEvents

internal/app/publish/messagegenerator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (g *MessageGenerator) StateToRefereeMessage(matchState *state.State) (r *st
9393
r = newRefereeMessage()
9494
r.DesignatedPosition = mapLocation(matchState.PlacementPos)
9595
r.GameEvents = matchState.GameEvents
96-
r.ProposedGameEvents = mapProposedGameEvents(matchState.GameEventProposals)
96+
r.ProposedGameEvents = mapProposals(matchState.ProposalGroups)
9797

9898
r.Command = mapCommand(matchState.Command)
9999
*r.CommandCounter = g.commandCounter

internal/app/rcon/autoref.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func (c *AutoRefClient) receiveRegistration(server *AutoRefServer) error {
4343
for k := range server.clients {
4444
clients = append(clients, k)
4545
}
46-
return errors.Errorf("Client with given identifier already registered: %v", clients)
46+
return errors.Errorf("AutoRef Client with given identifier already registered: %v", clients)
4747
}
4848
c.pubKey = server.trustedKeys[c.id]
4949
if c.pubKey != nil {
@@ -68,7 +68,7 @@ func (c *AutoRefClient) verifyRegistration(registration AutoRefRegistration) err
6868
if registration.Signature.Token != nil {
6969
sendToken = *registration.Signature.Token
7070
}
71-
return errors.Errorf("Client %v sent an invalid token: %v != %v", c.id, sendToken, c.token)
71+
return errors.Errorf("AutoRef Client %v sent an invalid token: %v != %v", c.id, sendToken, c.token)
7272
}
7373
signature := registration.Signature.Pkcs1V15
7474
registration.Signature.Pkcs1V15 = []byte{}
@@ -127,7 +127,7 @@ func (s *AutoRefServer) handleClientConnection(conn net.Conn) {
127127
s.CloseConnection(client.id)
128128
}()
129129

130-
log.Printf("Client %v connected", client.id)
130+
log.Printf("AutoRef Client %v connected", client.id)
131131
s.gcEngine.UpdateGcState(func(gcState *engine.GcState) {
132132
s := new(engine.GcStateAutoRef)
133133
gcState.AutoRefState[client.id] = s

internal/app/rcon/team.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (c *TeamClient) verifyRegistration(registration TeamRegistration) error {
8686
if registration.Signature.Token != nil {
8787
sendToken = *registration.Signature.Token
8888
}
89-
return errors.Errorf("Client %v sent an invalid token: %v != %v", c.id, sendToken, c.token)
89+
return errors.Errorf("Team Client %v sent an invalid token: %v != %v", c.id, sendToken, c.token)
9090
}
9191
signature := registration.Signature.Pkcs1V15
9292
registration.Signature.Pkcs1V15 = []byte{}
@@ -150,7 +150,7 @@ func (s *TeamServer) handleClientConnection(conn net.Conn) {
150150
s.CloseConnection(client.id)
151151
}()
152152

153-
log.Printf("Client %v connected", client.id)
153+
log.Printf("Team Client %v connected", client.id)
154154
team := s.gcEngine.CurrentState().TeamByName(client.id)
155155
s.gcEngine.UpdateGcState(func(gcState *engine.GcState) {
156156
if teamState, ok := gcState.TeamState[team.String()]; ok {
@@ -187,7 +187,7 @@ func (s *TeamServer) SendRequest(teamName string, request ControllerToTeam) erro
187187
if client, ok := s.clients[teamName]; ok {
188188
return client.SendRequest(request)
189189
}
190-
return errors.Errorf("Client '%v' not connected", teamName)
190+
return errors.Errorf("Team Client '%v' not connected", teamName)
191191
}
192192

193193
func (c *Client) SendRequest(request ControllerToTeam) error {

0 commit comments

Comments
 (0)