open-match.dev/open-match@v1.8.1/examples/scale/scenarios/backfill/backfill.go (about) 1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package backfill 16 17 import ( 18 "fmt" 19 "io" 20 "time" 21 22 "google.golang.org/protobuf/types/known/anypb" 23 "google.golang.org/protobuf/types/known/wrapperspb" 24 "open-match.dev/open-match/pkg/pb" 25 ) 26 27 const ( 28 poolName = "all" 29 openSlotsKey = "open-slots" 30 ) 31 32 func Scenario() *BackfillScenario { 33 ticketsPerMatch := 4 34 return &BackfillScenario{ 35 TicketsPerMatch: ticketsPerMatch, 36 MaxTicketsPerNotFullMatch: 3, 37 BackfillDeleteCond: func(b *pb.Backfill) bool { 38 openSlots := getOpenSlots(b, ticketsPerMatch) 39 return openSlots <= 0 40 }, 41 } 42 } 43 44 type BackfillScenario struct { 45 TicketsPerMatch int 46 MaxTicketsPerNotFullMatch int 47 BackfillDeleteCond func(*pb.Backfill) bool 48 } 49 50 func (s *BackfillScenario) Profiles() []*pb.MatchProfile { 51 return []*pb.MatchProfile{ 52 { 53 Name: "entirePool", 54 Pools: []*pb.Pool{ 55 { 56 Name: poolName, 57 }, 58 }, 59 }, 60 } 61 } 62 63 func (s *BackfillScenario) Ticket() *pb.Ticket { 64 return &pb.Ticket{} 65 } 66 67 func (s *BackfillScenario) Backfill() *pb.Backfill { 68 return &pb.Backfill{} 69 } 70 71 func (s *BackfillScenario) MatchFunction(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) { 72 return statefullMMF(p, poolBackfills, poolTickets, s.TicketsPerMatch, s.MaxTicketsPerNotFullMatch) 73 } 74 75 // statefullMMF is a MMF implementation which is used in scenario when we want MMF to create not full match and fill it later. 76 // 1. The first FetchMatches is called 77 // 2. MMF grabs maxTicketsPerNotFullMatch tickets and makes a match and new backfill for it 78 // 3. MMF sets backfill's open slots to ticketsPerMatch - maxTicketsPerNotFullMatch 79 // 4. MMF returns the match as a result 80 // 5. The second FetchMatches is called 81 // 6. MMF gets previously created backfill 82 // 7. MMF gets backfill's open slots value 83 // 8. MMF grabs openSlots tickets and makes a match with previously created backfill 84 // 9. MMF sets backfill's open slots to 0 85 // 10. MMF returns the match as a result 86 func statefullMMF(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket, ticketsPerMatch int, maxTicketsPerNotFullMatch int) ([]*pb.Match, error) { 87 var matches []*pb.Match 88 89 for pool, backfills := range poolBackfills { 90 tickets, ok := poolTickets[pool] 91 92 if !ok || len(tickets) == 0 { 93 // no tickets in pool 94 continue 95 } 96 97 // process backfills first 98 for _, b := range backfills { 99 l := len(tickets) 100 if l == 0 { 101 // no tickets left 102 break 103 } 104 105 openSlots := getOpenSlots(b, ticketsPerMatch) 106 if openSlots <= 0 { 107 // no free open slots 108 continue 109 } 110 111 if l > openSlots { 112 l = openSlots 113 } 114 115 setOpenSlots(b, openSlots-l) 116 matches = append(matches, &pb.Match{ 117 MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)), 118 Tickets: tickets[0:l], 119 MatchProfile: p.GetName(), 120 MatchFunction: "backfill", 121 Backfill: b, 122 }) 123 tickets = tickets[l:] 124 } 125 126 // create not full matches with backfill 127 for { 128 l := len(tickets) 129 if l == 0 { 130 // no tickets left 131 break 132 } 133 134 if l > maxTicketsPerNotFullMatch { 135 l = maxTicketsPerNotFullMatch 136 } 137 b := pb.Backfill{} 138 setOpenSlots(&b, ticketsPerMatch-l) 139 matches = append(matches, &pb.Match{ 140 MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)), 141 Tickets: tickets[0:l], 142 MatchProfile: p.GetName(), 143 MatchFunction: "backfill", 144 Backfill: &b, 145 AllocateGameserver: true, 146 }) 147 tickets = tickets[l:] 148 } 149 } 150 151 return matches, nil 152 } 153 154 func getOpenSlots(b *pb.Backfill, defaultVal int) int { 155 if b.Extensions == nil { 156 return defaultVal 157 } 158 159 any, ok := b.Extensions[openSlotsKey] 160 if !ok { 161 return defaultVal 162 } 163 164 var val wrapperspb.Int32Value 165 err := any.UnmarshalTo(&val) 166 if err != nil { 167 panic(err) 168 } 169 170 return int(val.Value) 171 } 172 173 func setOpenSlots(b *pb.Backfill, val int) { 174 if b.Extensions == nil { 175 b.Extensions = make(map[string]*anypb.Any) 176 } 177 178 any, err := anypb.New(&wrapperspb.Int32Value{Value: int32(val)}) 179 if err != nil { 180 panic(err) 181 } 182 183 b.Extensions[openSlotsKey] = any 184 } 185 186 // statelessMMF is a MMF implementation which is used in scenario when we want MMF to fill backfills created by a Gameserver. It doesn't create 187 // or update any backfill. 188 // 1. FetchMatches is called 189 // 2. MMF gets a backfill 190 // 3. MMF grabs ticketsPerMatch tickets and makes a match with the backfill 191 // 4. MMF returns the match as a result 192 func statelessMMF(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket, ticketsPerMatch int) ([]*pb.Match, error) { 193 var matches []*pb.Match 194 195 for pool, backfills := range poolBackfills { 196 tickets, ok := poolTickets[pool] 197 198 if !ok || len(tickets) == 0 { 199 // no tickets in pool 200 continue 201 } 202 203 for _, b := range backfills { 204 l := len(tickets) 205 if l == 0 { 206 // no tickets left 207 break 208 } 209 210 if l > ticketsPerMatch && ticketsPerMatch > 0 { 211 l = ticketsPerMatch 212 } 213 214 matches = append(matches, &pb.Match{ 215 MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)), 216 Tickets: tickets[0:l], 217 MatchProfile: p.GetName(), 218 MatchFunction: "backfill", 219 Backfill: b, 220 }) 221 tickets = tickets[l:] 222 } 223 } 224 225 return matches, nil 226 } 227 228 func (s *BackfillScenario) Evaluate(stream pb.Evaluator_EvaluateServer) error { 229 tickets := map[string]struct{}{} 230 backfills := map[string]struct{}{} 231 matchIds := []string{} 232 233 outer: 234 for { 235 req, err := stream.Recv() 236 if err == io.EOF { 237 break 238 } 239 if err != nil { 240 return fmt.Errorf("failed to read evaluator input stream: %w", err) 241 } 242 243 m := req.GetMatch() 244 245 if _, ok := backfills[m.Backfill.Id]; ok { 246 continue outer 247 } 248 249 for _, t := range m.Tickets { 250 if _, ok := tickets[t.Id]; ok { 251 continue outer 252 } 253 } 254 255 for _, t := range m.Tickets { 256 tickets[t.Id] = struct{}{} 257 } 258 259 matchIds = append(matchIds, m.GetMatchId()) 260 } 261 262 for _, id := range matchIds { 263 err := stream.Send(&pb.EvaluateResponse{MatchId: id}) 264 if err != nil { 265 return fmt.Errorf("failed to sending evaluator output stream: %w", err) 266 } 267 } 268 269 return nil 270 }