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
- Variables
- type Attribute
- type AttributeKind
- type Attributes
- type Clock
- type FakeClock
- type Match
- type Matchmaker
- func (m *Matchmaker) Accept(ticketID, playerID string) error
- func (m *Matchmaker) Cancel(ticketID string) error
- func (m *Matchmaker) Enqueue(t Ticket) error
- func (m *Matchmaker) MarkCompleted(ticketID string) error
- func (m *Matchmaker) Pending() int
- func (m *Matchmaker) PendingAcceptances() []Proposal
- func (m *Matchmaker) Reject(ticketID, playerID string) error
- func (m *Matchmaker) RuleMetrics(ticketID string) ([]RuleMetric, bool)
- func (m *Matchmaker) Status(ticketID string) (TicketStatus, error)
- func (m *Matchmaker) Tick() ([]Match, error)
- type Option
- type Player
- type Proposal
- type RuleMetric
- type SystemClock
- type Ticket
- type TicketStatus
Constants ¶
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.
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 ¶
var ErrDuplicateTicket = queue.ErrDuplicateTicket
ErrDuplicateTicket is returned by Matchmaker.Enqueue when a ticket with the same ID is already in the queue.
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) { ... }
var ErrTicketNotPlacing = errors.New("flexi: ticket is not in PLACING")
ErrTicketNotPlacing is returned by MarkCompleted when the ticket is not in StatusPlacing.
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.
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).
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 ¶
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 ¶
Number returns an Attribute of kind AttrNumber holding v.
func String ¶
String returns an Attribute of kind AttrString holding v.
func StringList ¶
StringList returns an Attribute of kind AttrStringList holding a copy of v. Subsequent mutations of v will not affect the returned attribute.
func StringNumberMap ¶
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 ¶
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 ¶
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 ¶
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.
type 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:
- Expires any proposals whose acceptanceTimeoutSeconds has elapsed, moving their tickets to StatusTimedOut.
- Resolves any proposals that have been fully accepted, moving tickets to StatusPlacing and returning the corresponding Match values.
- Applies FlexMatch expansions based on the oldest queued ticket's wait time.
- 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.
type 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.
type 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). |