minimatch

package module
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2024 License: Apache-2.0 Imports: 26 Imported by: 2

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 only)
    • 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.

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/castaneai/minimatch"
)

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.

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 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
})

Functions

func NewStateStoreWithMiniRedis added in v0.2.6

func NewStateStoreWithMiniRedis(t *testing.T) (statestore.StateStore, *miniredis.Miniredis)

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.StateStore, assigner Assigner, opts ...BackendOption) (*Backend, error)

func (*Backend) AddMatchFunction added in v0.2.0

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

func (*Backend) Start

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

func (*Backend) Tick

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

type BackendOption added in v0.2.0

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

func WithBackendLogger added in v0.2.7

func WithBackendLogger(logger *slog.Logger) BackendOption

func WithBackendMeterProvider added in v0.2.0

func WithBackendMeterProvider(provider metric.MeterProvider) BackendOption

func WithEvaluator added in v0.2.0

func WithEvaluator(evaluator Evaluator) BackendOption

func WithFetchTicketsLimit added in v0.2.0

func WithFetchTicketsLimit(limit int64) BackendOption

type BackendOptionFunc added in v0.2.0

type BackendOptionFunc func(options *backendOptions)

type Evaluator added in v0.2.0

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

type EvaluatorFunc added in v0.2.0

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

func (EvaluatorFunc) Evaluate added in v0.2.0

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

type FrontendService

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

func NewFrontendService

func NewFrontendService(store statestore.StateStore) *FrontendService

func (*FrontendService) AcknowledgeBackfill

func (*FrontendService) CreateBackfill

func (s *FrontendService) CreateBackfill(ctx context.Context, request *pb.CreateBackfillRequest) (*pb.Backfill, error)

func (*FrontendService) CreateTicket

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

func (*FrontendService) DeleteBackfill

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

func (*FrontendService) DeleteTicket

func (s *FrontendService) DeleteTicket(ctx context.Context, req *pb.DeleteTicketRequest) (*emptypb.Empty, error)

func (*FrontendService) GetBackfill

func (s *FrontendService) GetBackfill(ctx context.Context, request *pb.GetBackfillRequest) (*pb.Backfill, error)

func (*FrontendService) GetTicket

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

func (*FrontendService) UpdateBackfill

func (s *FrontendService) UpdateBackfill(ctx context.Context, request *pb.UpdateBackfillRequest) (*pb.Backfill, error)

func (*FrontendService) WatchAssignments

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(store statestore.StateStore) *MiniMatch

func NewMiniMatchWithRedis

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

func (*MiniMatch) AddMatchFunction added in v0.2.0

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

func (*MiniMatch) FrontendService

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

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 added in v0.2.6

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

func NewTestFrontendServer added in v0.2.6

func NewTestFrontendServer(t *testing.T, store statestore.StateStore, addr string) *TestFrontendServer

func (*TestFrontendServer) Addr added in v0.2.6

func (ts *TestFrontendServer) Addr() string

func (*TestFrontendServer) Dial added in v0.2.6

func (*TestFrontendServer) Start added in v0.2.6

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

func (*TestFrontendServer) Stop added in v0.2.6

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 (ts *TestServer) DialFrontend(t *testing.T) pb.FrontendServiceClient

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 added in v0.2.0

func WithTestServerBackendOptions(backendOptions ...BackendOption) TestServerOption

func WithTestServerBackendTick added in v0.2.0

func WithTestServerBackendTick(tick time.Duration) TestServerOption

func WithTestServerListenAddr

func WithTestServerListenAddr(addr string) TestServerOption

type TestServerOptionFunc

type TestServerOptionFunc func(*testServerOptions)

Directories

Path Synopsis
examples
loadtest module
pkg

Jump to

Keyboard shortcuts

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