flexi

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 11 Imported by: 0

README

flexi

Go Reference

flexi is an in-memory matchmaking library for Go that is compatible with Amazon GameLift FlexMatch rule sets.

It accepts the same JSON rule set you would pass to AWS's CreateMatchmakingRuleSet API and evaluates tickets locally — no GameLift hosting, no AWS connectivity, no networking. Drop the rule set in, enqueue tickets, drive the matchmaker, get matches.

Features

  • AWS-compatible JSON: feed the same rule set documented in the FlexMatch developer guide.
  • All seven rule kinds: comparison, distance, absoluteSort, batchDistance, collection, latency, compound.
  • All four player attribute types: string, number, string_list, string_number_map.
  • Algorithm strategies: exhaustiveSearch and balanced (with balancedAttribute).
  • Expansions: rule values loosen automatically as tickets wait.
  • Ticket status & player acceptance: FlexMatch-compatible lifecycle (QUEUEDREQUIRES_ACCEPTANCEPLACINGCOMPLETED, plus CANCELLED / TIMED_OUT) driven by acceptanceRequired / acceptanceTimeoutSeconds on the rule set.
  • Injectable clock: tests advance time deterministically, no time.Sleep.
  • Zero external dependencies at runtime (testify is test-only). No network or persistence.
  • Goroutine-safe: producers may Enqueue / Cancel / Accept / Reject while another goroutine drives Tick.

Backfill of in-progress matches is intentionally out of scope.

Installation

go get github.com/moepig/flexi

Requires Go 1.26 or later.

Quick start

package main

import (
    "fmt"
    "github.com/moepig/flexi"
)

const ruleset = `{
  "name": "skill-balance",
  "ruleLanguageVersion": "1.0",
  "playerAttributes": [{"name": "skill", "type": "number"}],
  "teams": [
    {"name": "red",  "minPlayers": 2, "maxPlayers": 2},
    {"name": "blue", "minPlayers": 2, "maxPlayers": 2}
  ],
  "rules": [
    {
      "name": "FairSkill",
      "type": "distance",
      "measurements": ["avg(teams[red].players.skill)"],
      "referenceValue": "avg(teams[blue].players.skill)",
      "maxDistance": 10
    }
  ]
}`

func main() {
    mm, err := flexi.New([]byte(ruleset))
    if err != nil {
        panic(err)
    }

    for i, skill := range []float64{50, 52, 49, 51} {
        _ = mm.Enqueue(flexi.Ticket{
            ID: fmt.Sprintf("t%d", i),
            Players: []flexi.Player{{
                ID:         fmt.Sprintf("p%d", i),
                Attributes: flexi.Attributes{"skill": flexi.Number(skill)},
            }},
        })
    }

    matches, _ := mm.Tick()
    for _, m := range matches {
        fmt.Println("red:",  m.Teams["red"])
        fmt.Println("blue:", m.Teams["blue"])
    }
}

Driving the matchmaker

Matchmaker does not start any goroutines or timers — the caller drives it by invoking Tick(). This keeps tests deterministic and lets you integrate with whatever scheduling, observability, and shutdown story you already have.

ticker := time.NewTicker(time.Second)
defer ticker.Stop()

for range ticker.C {
    matches, err := mm.Tick()
    if err != nil {
        log.Printf("flexi tick: %v", err)
        continue
    }
    for _, m := range matches {
        dispatch(m) // hand off to your game server
    }
}

Tick returns every match that can be formed at this moment. Tickets consumed by a returned match are removed from the queue atomically; everything else stays queued for a future tick.

Time and expansions

Anything time-dependent — most importantly the FlexMatch expansions block — reads the current time through a Clock. The default is the system clock; tests should pass WithClock(NewFakeClock(...)) so they can advance time without sleeping.

clock := flexi.NewFakeClock(time.Now())
mm, _ := flexi.New(rulesetJSON, flexi.WithClock(clock))

mm.Enqueue(ticket)
matches, _ := mm.Tick()           // before any expansion step

clock.Advance(60 * time.Second)
matches, _ = mm.Tick()            // expansion steps with waitTimeSeconds<=60 applied

A richer rule set example

{
  "name": "example",
  "ruleLanguageVersion": "1.0",
  "playerAttributes": [
    {"name": "skill", "type": "number"},
    {"name": "modes", "type": "string_list"}
  ],
  "algorithm": {
    "strategy": "balanced",
    "balancedAttribute": "skill"
  },
  "teams": [
    {"name": "team", "minPlayers": 3, "maxPlayers": 3, "quantity": 2}
  ],
  "rules": [
    {
      "name": "FairSkill",
      "type": "distance",
      "measurements": ["avg(teams[team_1].players.skill)"],
      "referenceValue": "avg(teams[team_2].players.skill)",
      "maxDistance": 10
    },
    {
      "name": "ModeOverlap",
      "type": "collection",
      "measurements": ["set_intersection(players.modes)"],
      "operation": "reference_intersection_count",
      "referenceValue": ["TDM", "CTF", "FFA"],
      "minCount": 1
    },
    {"name": "Ping", "type": "latency", "maxLatency": 150},
    {
      "name": "All",
      "type": "compound",
      "statement": {"condition": "and", "rules": ["FairSkill", "ModeOverlap", "Ping"]}
    }
  ],
  "expansions": [
    {
      "target": "rules[FairSkill].maxDistance",
      "steps": [
        {"waitTimeSeconds": 30, "value": 50},
        {"waitTimeSeconds": 60, "value": 200}
      ]
    }
  ]
}

Ticket status and player acceptance

Every ticket has a FlexMatch-compatible TicketStatus, queryable with Matchmaker.Status(id):

Enqueue                        → QUEUED
Tick (acceptanceRequired=false) → PLACING                           (Match returned)
Tick (acceptanceRequired=true)  → REQUIRES_ACCEPTANCE               (Proposal created)
  all Accept, then Tick        → PLACING                           (Match returned)
  any Reject, or Cancel        → CANCELLED
  timeout, then Tick           → TIMED_OUT
MarkCompleted (from PLACING)   → COMPLETED

SEARCHING and FAILED are defined for parity with the AWS API but are not produced by the current implementation.

Because this library operates as FlexMatch standalone (no game-session placement), the terminal success status Tick assigns is PLACING. Promote a ticket to COMPLETED with MarkCompleted(id) once your own placement pipeline has attached connection information.

Enable the acceptance flow by setting the two standard FlexMatch fields on the rule set:

{
  ...
  "acceptanceRequired": true,
  "acceptanceTimeoutSeconds": 60
}

Then drive the extra state machine:

matches, _ := mm.Tick()            // acceptanceRequired=true → no matches yet
for _, p := range mm.PendingAcceptances() {
    for _, id := range p.TicketIDs {
        for _, pl := range p.Teams[...] /* surface to your players */ {
            if accepted { mm.Accept(id, pl.ID) } else { mm.Reject(id, pl.ID) }
        }
    }
}
matches, _ = mm.Tick()             // fully-accepted proposals are now returned

A single Reject, or a Cancel on any participating ticket, dissolves the whole proposal (every involved ticket transitions to CANCELLED). After acceptanceTimeoutSeconds elapses, the next Tick discards the proposal and its tickets become TIMED_OUT. Per FlexMatch, tickets in CANCELLED / TIMED_OUT / FAILED are not re-queued — resubmit with a fresh ticket ID if you need another attempt.

Rule evaluation metrics

FlexMatch's match events (PotentialMatchCreated, MatchmakingTimedOut, MatchmakingCancelled) include ruleEvaluationMetrics — per-rule {ruleName, passedCount, failedCount} tallies. flexi reproduces these:

  • Every Match and Proposal carries RuleEvaluationMetrics, the pass/fail counts accumulated by the search that formed that (candidate) match. This maps to PotentialMatchCreated.
  • Matchmaker.RuleMetrics(id) returns the cumulative per-rule tallies for a ticket across every Tick it took part in, retained through terminal states. Use it for the MatchmakingTimedOut / MatchmakingCancelled events of tickets that never made it into a match.
matches, _ := mm.Tick()
for _, rm := range matches[0].RuleEvaluationMetrics {
    fmt.Printf("%s: passed=%d failed=%d\n", rm.RuleName, rm.PassedCount, rm.FailedCount)
}

// For a timed-out / cancelled ticket:
if metrics, ok := mm.RuleMetrics("ticket-1"); ok {
    // metrics[i].RuleName / PassedCount / FailedCount
}

Rule names match those declared in the rule set's rules block; a compound rule is reported once (its child evaluations are not listed separately). Each rule-set evaluation counts every rule (no short-circuit) so failedCount is complete. Tickets never involved in an evaluation report no metrics (the slice is nil / RuleMetrics returns false), keeping the addition backward compatible.

API at a glance

Full reference is on pkg.go.dev. The most-used surface:

Symbol Description
flexi.New(rulesetJSON, opts...) Parse a rule set JSON document and return a Matchmaker.
flexi.WithClock(c) Option that overrides the time source.
Matchmaker.Enqueue(t) Add a ticket to the queue.
Matchmaker.Cancel(id) Remove a queued ticket, or dissolve a proposal it is part of.
Matchmaker.Tick() Expire timed-out proposals, resolve accepted ones, and form new matches.
Matchmaker.Pending() Count of tickets currently in QUEUED.
Matchmaker.Status(id) Current TicketStatus for a ticket.
Matchmaker.RuleMetrics(id) Cumulative per-rule pass/fail tallies for a ticket (ruleEvaluationMetrics).
Matchmaker.PendingAcceptances() Snapshot of proposals in REQUIRES_ACCEPTANCE.
Matchmaker.Accept(id, playerID) / Reject(id, playerID) Record a player's decision on a proposed match.
Matchmaker.MarkCompleted(id) Promote a PLACING ticket to COMPLETED.
flexi.Number / String / StringList / StringNumberMap Constructors for the four Attribute variants.
flexi.NewFakeClock(t) Test clock you can Advance or Set.

Testing

go test ./... -race

Each layer (rule set parser, expression evaluator, rule evaluators, expansions, algorithm, queue) has its own unit tests. End-to-end scenarios driven through the public API live in examples_test.go. Tests use stretchr/testify.

License

See LICENSE for license terms.

Documentation

Overview

Package flexi implements an in-memory matchmaking engine compatible with Amazon GameLift FlexMatch rule sets.

The engine accepts the AWS FlexMatch rule set JSON document — the same payload passed to CreateMatchmakingRuleSet's RuleSetBody parameter — and evaluates matchmaking tickets against it, producing matches whose teams satisfy every configured rule.

Scope

flexi targets FlexMatch's "standalone" use case: pure rule evaluation with no GameLift hosting integration, no networking, and no persistence. The ticket queue is held in memory only.

Supported rule set features:

  • Player attribute types: string, number, string_list, string_number_map.
  • Algorithm strategies: exhaustiveSearch, balanced (with balancedAttribute).
  • Algorithm batching preferences: largestPopulation, fastestRegion, balanced (parsed; influences team-fill ordering for balanced).
  • Teams with minPlayers, maxPlayers, and quantity (multi-instance teams).
  • All seven FlexMatch rule kinds: comparison, distance, absoluteSort, batchDistance, collection, latency, compound.
  • Time-driven expansions that loosen rule values once a ticket has been waiting long enough.
  • Rule evaluation metrics (FlexMatch's ruleEvaluationMetrics): per-rule pass/fail tallies on each Match and Proposal, plus cumulative per-ticket totals via Matchmaker.RuleMetrics.

Backfill of in-progress matches is intentionally out of scope.

Quick start

mm, err := flexi.New(rulesetJSON)
if err != nil { ... }

mm.Enqueue(flexi.Ticket{
    ID: "ticket-1",
    Players: []flexi.Player{{
        ID: "alice",
        Attributes: flexi.Attributes{"skill": flexi.Number(1500)},
        Latencies:  map[string]int{"us-east-1": 35},
    }},
})

matches, err := mm.Tick()
for _, m := range matches {
    // m.Teams maps team name -> assigned players
    // m.TicketIDs lists tickets consumed by the match
}

Driving the matchmaker

Matchmaker has no internal goroutines or timers. Callers drive it by invoking Matchmaker.Tick, which returns every match that can be formed against the current queue. This keeps tests deterministic and lets callers integrate with whatever scheduling, observability, or shutdown story they already have. A typical production loop looks like:

t := time.NewTicker(time.Second)
for range t.C {
    matches, err := mm.Tick()
    // dispatch matches, log err
}

Time and expansions

Anything that depends on elapsed time (most importantly the FlexMatch "expansions" block) reads the current time through a Clock. The default is SystemClock; tests should pass WithClock with a FakeClock so they can advance time deterministically without sleeping.

Concurrency

All Matchmaker methods are safe for concurrent use. The queue is protected by an internal mutex, so producers may Enqueue/Cancel from any goroutine while another goroutine drives Tick. Tick itself is intended to be called from a single goroutine — concurrent ticks are safe but compete for the same ticket pool.

Index

Constants

View Source
const (
	// AttrUnknown is the zero value and should not appear in well-formed input.
	AttrUnknown = core.AttrUnknown
	// AttrString corresponds to FlexMatch's "string" attribute type.
	AttrString = core.AttrString
	// AttrNumber corresponds to FlexMatch's "number" attribute type.
	AttrNumber = core.AttrNumber
	// AttrStringList corresponds to FlexMatch's "string_list" attribute type.
	AttrStringList = core.AttrStringList
	// AttrStringNumberMap corresponds to FlexMatch's "string_number_map" type.
	AttrStringNumberMap = core.AttrStringNumberMap
)

AttributeKind constants. Use String, Number, StringList, or StringNumberMap to construct attributes rather than setting Kind by hand.

View Source
const (
	StatusQueued             = core.StatusQueued
	StatusSearching          = core.StatusSearching
	StatusRequiresAcceptance = core.StatusRequiresAcceptance
	StatusPlacing            = core.StatusPlacing
	StatusCompleted          = core.StatusCompleted
	StatusFailed             = core.StatusFailed
	StatusCancelled          = core.StatusCancelled
	StatusTimedOut           = core.StatusTimedOut
)

Variables

View Source
var ErrDuplicateTicket = queue.ErrDuplicateTicket

ErrDuplicateTicket is returned by Matchmaker.Enqueue when a ticket with the same ID is already in the queue.

View Source
var ErrInvalidRuleSet = ruleset.ErrInvalidRuleSet

ErrInvalidRuleSet is returned by New when the rule set JSON is malformed or fails semantic validation. It is suitable for use with errors.Is:

if errors.Is(err, flexi.ErrInvalidRuleSet) { ... }
View Source
var ErrTicketNotPlacing = errors.New("flexi: ticket is not in PLACING")

ErrTicketNotPlacing is returned by MarkCompleted when the ticket is not in StatusPlacing.

View Source
var ErrUnknownPlayer = errors.New("flexi: player is not part of ticket")

ErrUnknownPlayer is returned by Accept / Reject when the player id is not part of the referenced ticket.

View Source
var ErrUnknownProposal = errors.New("flexi: ticket is not in a pending proposal")

ErrUnknownProposal is returned by Accept / Reject when the ticket is not part of any active proposal (either it was never proposed, or the proposal has already been resolved).

View Source
var ErrUnknownTicket = queue.ErrUnknownTicket

ErrUnknownTicket is returned by Matchmaker.Cancel or Matchmaker.Status when no ticket with the given ID is currently tracked.

Functions

This section is empty.

Types

type Attribute

type Attribute = core.Attribute

Attribute is a tagged union mirroring a single FlexMatch player attribute value. Only the field selected by Kind is meaningful; other fields are zero.

Construct attributes with String, Number, StringList, or StringNumberMap rather than building the struct literally.

func Number

func Number(v float64) Attribute

Number returns an Attribute of kind AttrNumber holding v.

func String

func String(v string) Attribute

String returns an Attribute of kind AttrString holding v.

func StringList

func StringList(v ...string) Attribute

StringList returns an Attribute of kind AttrStringList holding a copy of v. Subsequent mutations of v will not affect the returned attribute.

func StringNumberMap

func StringNumberMap(v map[string]float64) Attribute

StringNumberMap returns an Attribute of kind AttrStringNumberMap holding a copy of v. Subsequent mutations of v will not affect the returned attribute.

type AttributeKind

type AttributeKind = core.AttributeKind

AttributeKind identifies which variant an Attribute holds. It mirrors the four player attribute types FlexMatch supports: string, number, string_list, and string_number_map.

type Attributes

type Attributes = core.Attributes

Attributes is a player's attribute bag, keyed by attribute name. The keys must match the names declared in the rule set's playerAttributes block for the rules to find them.

type Clock

type Clock interface {
	// Now returns the current time as observed by this Clock.
	Now() time.Time
}

Clock is the source of "now" used by the matchmaker.

It exists so that callers — and especially tests — can control what time looks like. All time-dependent behaviour in flexi (computing how long a ticket has been waiting, deciding which expansion step is active) reads the current time through this interface rather than calling time.Now directly.

Implementations must be safe for concurrent use.

type FakeClock

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

FakeClock is a Clock whose value is set explicitly and never advances on its own. It is intended for tests: construct one with NewFakeClock, then call FakeClock.Advance or FakeClock.Set to move time forward between matchmaker ticks.

FakeClock is safe for concurrent use.

func NewFakeClock

func NewFakeClock(t time.Time) *FakeClock

NewFakeClock returns a FakeClock anchored at t.

Choose any wall-clock value you find convenient for diagnostics; flexi only looks at differences between Now() readings, never at absolute time.

func (*FakeClock) Advance

func (c *FakeClock) Advance(d time.Duration)

Advance moves the FakeClock forward by d.

Use this between calls to Matchmaker.Tick in tests to simulate the passage of time and trigger expansion steps.

func (*FakeClock) Now

func (c *FakeClock) Now() time.Time

Now returns the FakeClock's current value.

func (*FakeClock) Set

func (c *FakeClock) Set(t time.Time)

Set replaces the FakeClock's current value with t.

Useful when a test needs to jump to an absolute moment rather than a relative offset.

type Match

type Match = core.Match

Match is a successful pairing of tickets into a complete game.

Teams maps the team name (as it appeared in the rule set) to the players assigned to that team. When a rule set declares quantity > 1 for a team, the resulting names are suffixed with "_1", "_2", and so on. TicketIDs lists every ticket consumed to form the match, sorted lexicographically for stable test output.

type Matchmaker

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

Matchmaker forms matches from in-memory tickets according to a parsed FlexMatch rule set.

Construct one with New, add tickets with Matchmaker.Enqueue, and call Matchmaker.Tick periodically to drain matches that are now satisfiable.

When the rule set sets acceptanceRequired=true, Tick produces Proposal candidates instead of final [Match]es. Callers route each proposal through Matchmaker.Accept / Matchmaker.Reject and drive Matchmaker.Tick again; once every player on every ticket of a proposal has accepted, the next Tick returns the corresponding Match.

Matchmaker has no internal goroutines or timers — all work happens on the goroutine that calls Tick. The queue, status map, and proposals are protected by a mutex so producers may Enqueue/Cancel/Accept concurrently with a ticking loop.

func New

func New(rulesetJSON []byte, opts ...Option) (*Matchmaker, error)

New parses a FlexMatch rule set JSON document and returns a Matchmaker ready to accept tickets.

rulesetJSON must be the same JSON body accepted by GameLift's CreateMatchmakingRuleSet API (the RuleSetBody parameter). Parsing or validation failures are reported as errors that wrap ErrInvalidRuleSet.

Options may be supplied to override defaults; see WithClock.

func (*Matchmaker) Accept

func (m *Matchmaker) Accept(ticketID, playerID string) error

Accept records that playerID, who must be part of the ticket ticketID, has accepted the proposed match. Once every player on every ticket in the proposal has accepted, the proposal is resolved on the next [Tick]: the tickets move to StatusPlacing and the corresponding Match is returned.

Returns ErrUnknownTicket if the ticket is not tracked, ErrUnknownProposal if the ticket is not currently in a pending proposal, or ErrUnknownPlayer if playerID is not a member of the ticket.

func (*Matchmaker) Cancel

func (m *Matchmaker) Cancel(ticketID string) error

Cancel removes the ticket with the given ID from the matchmaker and marks it StatusCancelled. It returns ErrUnknownTicket if no such ticket is currently tracked.

If the ticket is part of an active proposal, the entire proposal is torn down: every sibling ticket is also marked StatusCancelled, matching FlexMatch's behaviour when any member of a proposed match drops out.

Cancelling a ticket that has already been consumed by a match (and is now StatusPlacing or StatusCompleted) is rejected with ErrUnknownTicket.

func (*Matchmaker) Enqueue

func (m *Matchmaker) Enqueue(t Ticket) error

Enqueue adds t to the matchmaking queue and records its status as StatusQueued.

The ticket's EnqueuedAt field is set from the configured Clock; any value supplied by the caller is overwritten so that wait-time calculations remain consistent. The ticket must have a non-empty ID and at least one player; otherwise an error is returned and the ticket is not enqueued.

Enqueue returns ErrDuplicateTicket (wrapped) if a ticket with the same ID is already queued, in a proposal, or in a recent terminal state.

func (*Matchmaker) MarkCompleted

func (m *Matchmaker) MarkCompleted(ticketID string) error

MarkCompleted transitions ticketID from StatusPlacing to StatusCompleted. This is the equivalent of a standalone-mode caller reporting that they have finished placing the match into a game session and connection details are now available on their side.

Returns ErrUnknownTicket if the ticket is not tracked or ErrTicketNotPlacing if it is in any other status.

func (*Matchmaker) Pending

func (m *Matchmaker) Pending() int

Pending returns the number of tickets currently in StatusQueued. Tickets held in a proposal (StatusRequiresAcceptance) are not counted.

func (*Matchmaker) PendingAcceptances

func (m *Matchmaker) PendingAcceptances() []Proposal

PendingAcceptances returns a snapshot of every proposal currently awaiting acceptance. The slice is newly allocated and safe for the caller to mutate.

func (*Matchmaker) Reject

func (m *Matchmaker) Reject(ticketID, playerID string) error

Reject records a player's rejection of a proposed match. A single rejection dissolves the entire proposal: every ticket in it moves to StatusCancelled, matching the FlexMatch behaviour documented for the CANCELLED status.

Errors match Matchmaker.Accept.

func (*Matchmaker) RuleMetrics added in v0.0.2

func (m *Matchmaker) RuleMetrics(ticketID string) ([]RuleMetric, bool)

RuleMetrics returns the cumulative rule-evaluation metrics accumulated for ticketID across every Tick in which it participated in match formation, together with true. If the ticket has never been involved in an evaluation (for example it was cancelled while still only queued, or no such ticket is tracked), it returns nil and false.

The metrics support FlexMatch's MatchmakingTimedOut and MatchmakingCancelled parity: like [Status], they are retained through terminal states (TIMED_OUT, CANCELLED, PLACING, COMPLETED) and are not evicted automatically. Each entry is named after a top-level rule in the rule set; the returned slice is a copy the caller may mutate freely.

func (*Matchmaker) Status

func (m *Matchmaker) Status(ticketID string) (TicketStatus, error)

Status returns the current TicketStatus for ticketID. If no ticket with that id is currently tracked by the matchmaker, ErrUnknownTicket is returned.

Note that the matchmaker retains status for tickets past queue removal only while they are in a terminal state reachable from this matchmaker (CANCELLED, TIMED_OUT, PLACING, COMPLETED). Eviction of terminal state is not automatic; call sites should not rely on long-term retention.

func (*Matchmaker) Tick

func (m *Matchmaker) Tick() ([]Match, error)

Tick drives the matchmaker forward by one step.

In order, Tick:

  1. Expires any proposals whose acceptanceTimeoutSeconds has elapsed, moving their tickets to StatusTimedOut.
  2. Resolves any proposals that have been fully accepted, moving tickets to StatusPlacing and returning the corresponding Match values.
  3. Applies FlexMatch expansions based on the oldest queued ticket's wait time.
  4. Runs the matching algorithm over the remaining queued tickets. When acceptanceRequired=false, each result becomes a Match returned in this tick; when true, each result is held as a new proposal and its tickets move to StatusRequiresAcceptance.

Tick returns nil, nil when nothing resolves in this step. A non-nil error indicates a configuration problem (for example, an expansion targeting a field that no longer exists).

type Option

type Option func(*config)

Option configures a Matchmaker at construction time. Pass any number of options to New.

func WithClock

func WithClock(c Clock) Option

WithClock injects a custom Clock in place of the default SystemClock. Tests should pass a FakeClock so that expansions and ticket wait times can be controlled deterministically.

type Player

type Player = core.Player

Player is a single participant in a Ticket. ID identifies the player within a match (it is echoed back in Match.Teams). Attributes carry the values referenced by rule expressions; their keys must match names declared in the rule set's playerAttributes block. Latencies maps an AWS region name to the player's measured latency in milliseconds; it is consulted by latency rules.

Players are passed by value and may be safely re-used across tickets.

type Proposal

type Proposal struct {
	Teams     map[string][]core.Player
	TicketIDs []string
	CreatedAt time.Time
	// RuleEvaluationMetrics holds the per-rule pass/fail tallies accumulated
	// by the search that formed this candidate match, for PotentialMatchCreated
	// parity. It is nil when the rule set declares no rules.
	RuleEvaluationMetrics []core.RuleMetric
}

Proposal describes a candidate match awaiting player acceptance. It is returned by Matchmaker.PendingAcceptances so callers can surface the pending decision to their players.

Teams mirrors [Match.Teams] for the candidate. TicketIDs lists every ticket participating in the proposal. CreatedAt is the clock time at which the proposal was formed; together with the rule set's acceptanceTimeoutSeconds it determines when the proposal times out.

type RuleMetric added in v0.0.2

type RuleMetric = core.RuleMetric

RuleMetric reports how many times a rule passed or failed while matchmaking processed a set of tickets. It corresponds to one entry of the ruleEvaluationMetrics array in FlexMatch's PotentialMatchCreated, MatchmakingTimedOut, and MatchmakingCancelled events. RuleName matches a rule name declared in the rule set. It is exposed on Match and Proposal for a formed/candidate match, and via Matchmaker.RuleMetrics for the cumulative per-ticket totals.

type SystemClock

type SystemClock struct{}

SystemClock is the default Clock; it returns the wall-clock time from the standard library's time.Now.

SystemClock has no state and a zero value is ready to use.

func (SystemClock) Now

func (SystemClock) Now() time.Time

Now returns time.Now().

type Ticket

type Ticket = core.Ticket

Ticket is a matchmaking request submitted to the engine. A ticket may contain a single player (a "solo" request) or several players who must be matched together as a party.

ID must be unique among queued tickets. EnqueuedAt is filled in by Matchmaker.Enqueue from the configured Clock; any value set by the caller is overwritten.

type TicketStatus

type TicketStatus = core.TicketStatus

TicketStatus is the FlexMatch-compatible ticket lifecycle state.

Values mirror the MatchmakingTicket.Status values in the AWS GameLift API. Because this library runs FlexMatch in standalone mode (no game session placement), the terminal success status is StatusPlacing; callers that have attached connection information to the ticket may promote it to StatusCompleted via Matchmaker.MarkCompleted.

Transitions:

  • Enqueue → StatusQueued
  • Tick forms a candidate (acceptanceRequired=true) → StatusRequiresAcceptance
  • All players Accept → StatusPlacing (Match returned)
  • acceptanceRequired=false + Tick → StatusPlacing (Match returned)
  • MarkCompleted → StatusCompleted
  • Cancel on queued / rejected → StatusCancelled
  • Acceptance timeout → StatusTimedOut

StatusSearching and StatusFailed are defined for parity with FlexMatch but are not produced by the current implementation; they are reserved for future use.

Directories

Path Synopsis
internal
algorithm
Package algorithm builds matches from a queue of tickets according to a FlexMatch rule set's algorithm settings.
Package algorithm builds matches from a queue of tickets according to a FlexMatch rule set's algorithm settings.
core
Package core holds value types shared between the public flexi package and the internal sub-packages, defined here to break an import cycle.
Package core holds value types shared between the public flexi package and the internal sub-packages, defined here to break an import cycle.
expansion
Package expansion applies time-driven loosening of rule set values, as declared by the FlexMatch "expansions" block.
Package expansion applies time-driven loosening of rule set values, as declared by the FlexMatch "expansions" block.
expr
Package expr implements a small parser and evaluator for the subset of FlexMatch property expressions used inside rule measurements and reference values, e.g.
Package expr implements a small parser and evaluator for the subset of FlexMatch property expressions used inside rule measurements and reference values, e.g.
queue
Package queue is the in-memory ticket store used by the matchmaker.
Package queue is the in-memory ticket store used by the matchmaker.
rule
Package rule converts parsed ruleset.Rule entries into evaluators that answer "does this candidate match satisfy me?".
Package rule converts parsed ruleset.Rule entries into evaluators that answer "does this candidate match satisfy me?".
ruleset
Package ruleset parses and validates AWS GameLift FlexMatch rule set JSON documents (the same payload accepted by CreateMatchmakingRuleSet).
Package ruleset parses and validates AWS GameLift FlexMatch rule set JSON documents (the same payload accepted by CreateMatchmakingRuleSet).

Jump to

Keyboard shortcuts

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