...

Package matching

import "github.com/Diarkis/diarkis/matching"
Overview
Index

Overview ▾

Package matching ▷

Distributed memory matchmaking module

Highly scalable and extremely fast matchmaking based on application defined rules (matchmaking profiles).

MatchMaker scales in and out as you scale your Diarkis server cluster and it maintains both the performance and flexibility.

Configurations

MatchMaker configurations are explained below.

{
  "distributionRate": 30,
  "targetNodeType": "HTTP"
  "profiles": {
    "$(matchmakingProfileID)": { "$(propertyA)": $(1), "$(propertyB)":$(100) }
  }
}

distributionRate - Default is 30

Configures how many servers to distribute the matchmaking data duplicates by percentage.

If set to 30, it will MatchMaker will distribute data duplicates to the 30% of the servers in the Diarkis server cluster.

targetNodeType - Default is "HTTP"

Configures which servers to store matchmaking data by server type.

profiles - Optional configuration to define matchmaking profiles from the configuration file.

"profiles" an optional configuration that allows you to define matchmaking profiles.

The format of the JSON is as shown below:

$(...) represents a variable

{
  "profiles":
    {
      "$(profileID)": { "$(profileName)": $(profileValue) ... }
      ...
    }
}

"$(profileID)" is the unique matchmaking profile ID
and the object that follows will define the properties of that profile as key and value pairs.

Matchmaking Rule Examples

In order to perform matchmaking, you must define the rules for matchmakings.

The matchmaking rules are called profiles.

These rules will dictate how matchmaking should be conditioned.

You may combine multiple matchmaking rules and create more complex matchmaking conditions as well.

You must define matchmaking rule profiles before invoking diarkis.Start.

The example below shows a matchmaking rule that uses level and creates buckets of matchmaking pools by the range of 10.

With this profile, each level bucket will pool users with level 0 to 10, 11 to 20, 21 to 30 and so forth...

The string name given as LevelMatch is the unique ID to represents the profile.

levelMatchProfile := make(map[string]int)

levelMatchProfile["level"] = 10

matching.Define("LevelMatch", levelMatchProfile)

The diagrams below explain how matchmaking profiles work:

   ┏━━━┓
   ┃ 8 ┃ 8 falls between 0 and 10, so it matches with items in this bucket
   ┗━┳━┛
     ┃
│    ▼    │         │         │
│  0 ~ 10 │ 11 ~ 20 │ 21 ~ 30 │
└─────────┴─────────┴─────────┘

                       ┏━━━┓
                       ┃ 3 ┃ 3 falls on 3, so it matches with items in this bucket
                       ┗━┳━┛
                         ┃
│         │         │    ▼    │
│    1    │    2    │    3    │
└─────────┴─────────┴─────────┘

▶︎ Multiple properties:

Tables below explains how matchmaking profile with multiple properties work.

┌──────────┬──────┬────────┐
│  Player  │ Rank │ League │
╞══════════╪══════╪════════╡
│ Player A │    5 │      1 │
│ Player B │   10 │      2 │
│ Player C │    9 │      1 │
│ Player D │   30 │      2 │
│ Player E │    7 │      2 │
│ Player F │   24 │      2 │
└──────────┴──────┴────────┘

┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│░░░░░░░░░░│ Player A │ Player B │ Player C │ Player D │ Player E │ Player F │
╞══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╡
│ Player A │░░░░░░░░░░│ x        │ ○        │ x        │ x        │ x        │
│ Player B │ x        │░░░░░░░░░░│ x        │ x        │ ○        │ x        │
│ Player C │ ○        │ x        │░░░░░░░░░░│ x        │ x        │ x        │
│ Player D │ x        │ x        │ x        │░░░░░░░░░░│ x        │ ○        │
│ Player E │ x        │ ○        │ x        │ x        │░░░░░░░░░░│ x        │
│ Player F │ x        │ x        │ x        │ ○        │ x        │░░░░░░░░░░│
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘

▶︎ Range search

All matchmaking profile properties are "bucket". What it means is that it does not allow search by ranged values.

For example, if search value is 10 and a matchmaking bucket is 0 ~ 10, it will match items of 0 to 10, but it does not allow search by ±5.

In order for MatchMaker to search by range, you must use SearchWithRange function.

Diagram below shows how it works:

     ┏━━━┓
     ┃ 8 ┃ 8 with ±3 would fall into 5 ~ 11 and that means it matches with items in the bucket of 0 to 10 and 11 to 20
     ┗━┳━┛
     ┏━┻━━━━┓
│    ▼    │ ▼       │         │
│  0 ~ 10 │ 11 ~ 20 │ 21 ~ 30 │
└─────────┴─────────┴─────────┘

╒═══════════════════════════════════════════════════════════════════════╕
│ NOTE                                                                  │
├───────────────────────────────────────────────────────────────────────┤
│ To make ranged search more precise,                                   │
│ consider using SetOnTicketAllowMatchIf callback for MatchMaker Ticket.│
│                                                                       │
│ SetOnTicketAllowMatchIf callback is invoked on every match found      │
│ and it allows the application's custom logic                          │
│ via callback to either accept or reject the match found.              │
╘═══════════════════════════════════════════════════════════════════════╛

Debugging

MatchMaker allows you to see what is held in memory by sending a SIGUSR1 signal to a target server process.

Follow the steps below to output all matchmaking data in stdout stream:

1. Create a file called DIARKIS_SIGUSR1 in /tmp/ directory and write MatchMakerDump in the file.

2. Send SIGUSR1 signal to the target server process. By Default, HTTP servers act as memory storage for MatchMaker.

3. All data held in memory for MatchMaker will be dumped in stdout stream.

How to read the dumped matchmaking data:

=-=-=-=-=-=[ RankMatch2\t\tv100\t3\t1 ]=-=-=-=-=-=
1 => ID:68d425979add41e28f2ecc4993cfde2e7f0000011fa5cae3767e Value:map[maxMembers:2 roomID:68d425979add41e28f2ecc4993cfde2e7f0000011fa5cae3767e uniqueID:111] TTL:1675730081 TTLRemaining:2 seconds Expired:false

Above example output explains how to read and understand the matchmaking data held in memory.

=-=-=-=-=-=[ RankMatch2\t\tv100\t3\t1 ]=-=-=-=-=-=

RankMatch2:v100/3/1 is the matching condition key with a tag. Let's break it down:

RanMatch2 - Matching schema (definition) ID.
v100      - Tag.
3\t1       - This key is generated by the condition values given to Add function along with the schema values defined by Define function.

The search conditions must match the key in order to find matches.

ID:68d425979add41e28f2ecc4993cfde2e7f0000011fa5cae3767e

ID represents the matchmaking data to identify the entry. Each ID is provided by the application code.

Value:map[maxMembers:2 roomID:68d425979add41e28f2ecc4993cfde2e7f0000011fa5cae3767e uniqueID:111]

The value of the matchmaking data provided by the application.

TTL:1675730081 TTLRemaining:2 seconds Expired:false

TTL of the matchmaking data entry and maximum TTL lasts 60 seconds.

MatchMaker Ticket Custom Logic

MatchMaker offers plethora of callbacks to add customized logic for your applications.

▶︎ Create/Issue a new MatchMaker Ticket

If you are writing your own custom MatchMaker Ticket logic and command, you will need to use StartTicket(ticketType uint8, userData *user.User).

The example below shows you how it is done.

// SetOnIssueTicket callback is required to create a new ticket when executing StartTicket
// The callback will be invoked when you execute StartTicket with matching ticketType
matching.SetOnIssueTicket(ticketType, func(userData *user.User) *matching.TicketParams {
	return &matching.TicketParams{
		ProfileIDs:       []string{"RankMatch"},
		MaxMembers:       2,
		SearchInterval:   300, // 300 milliseconds interval of search
		SearchTries:      4,   // allow 4 consecutive empty search results up to 4 times before moving on to wait
		TicketDuration:   20,  // ticket lasts for 20 seconds
		HowMany:          20,  // up to 20 search results
		Tag:              "",  // if we want to group tickets using tag add the string value here
		AddProperties:    &map[string]int{ "Rank": 3 }, // wait for other users to find me and my rank is 3
		SearchProperties: &map[string][]int{ "Rank": &[]int{ 1, 2, 3, 4, 5 } }, // search for other users within the range of 1 to 5 and property is "Rank"
	}
})

// Example of custom command implementation to create a new ticket
// This example custom command expects the client to send ticket type in the payload
server.HandleCommand(ver, cmd, func(ver uint8, cmd uint16, payload []byte, userData *user.User, next func(error)) {
  ticketType := uint8(0)

  if len(payload) > 0 {
	  ticketType = uint8(payload[0])
  }

  err := StartTicket(ticketType, userData)

  if err != nil {
	  next(err)
	  return
  }

  next(nil)
})

Once you create/issue a new ticket, the rest will be handled by MatchMaker Ticket.

Control matches

MatchMaker Ticket allows the application to decide if found matches should proceed to become actual matches or not by adding a custom callback.

// This callback is applied to all potential matches found for the given ticketType.
matching.SetOnTicketAllowMatchIf(ticketType, func (ticketProps *TicketProperties, owner, candidate *user.User) bool) {
	// Add your custom logic here to determine if this potential match should become an actual match or not.
	// By returning true, MatchMaker will proceed to make this candidate as an actual match.
	return true
})

Control completion of matchmaking ticket

The callback is invoked on every match made and allows you to execute your custom logic to decide if the application should mark the ticket as complete or not. This allows the application to complete a matchmaking ticket even if you do not have enough number of matches required by the ticket.

// This callback is applied all tickets with the given ticketType.
matching.SetOnTicketMatch(ticketType, func (ticket *Ticket, matchedUser, ownerUser *user.User, roomID string, memberIDs []string) bool {
	// By returning true, the ticket will be marked as complete and notifies the matched users.
	return true
})

Additional custom logic on every match made

You may execute additional custom logic on every match made. This maybe useful if you want to send a message to all matched members when a new match is made to show a progress of matchmaking until it is complete.

// This callback is applied all tickets with the given ticketType.
matching.SetOnTicketMemberJoined(ticketType, func (ticket *Ticket, joinedUser, ownerUser *user.User, memberIDs []string) {
	// This example sends a message to all matched users
	ver := 2   // server to client command version
	cmd := 100 // server to client command ID
	msg := []byte(fmt.Sprintf("Hello I am %s", userData.ID)) // a message to be sent to all matched users
	matching.TicketBroadcast(ticketType, userData, ver, cmd, msg)
})

Detect when a matched user has left the match

You may execute a custom callback function when a matched user leaves a match.

// This callback is applied all tickets with the given ticketType.
matching.SetOnTicketMemberLeave(ticketType, func (ticket *Ticket, leftUserData, ownerUser *user.User, roomID string, memberIDs []string) {
	// This example sends a message to all matched users except for the leftUserData
	ver := 2   // server to client command version
	cmd := 100 // server to client command ID
	msg := []byte(fmt.Sprintf("Bye bye from %s", userData.ID)) // a message to be sent to all matched users
	matching.TicketBroadcast(ticketType, userData, ver, cmd, msg)
})

Matchmaking ticket completion notification

[IMPORTANT] This callback must be assigned in order to send matchmaking completion notification to all matched client users.

MatchMaker Ticket offers a callback to send a notification message to all matched users when a matchmaking ticket is marked as complete (either it has matched required number of users or matching.SetOnTicketMatch returned true). The callback returns a byte array to be sent as a notification message so that you may customize the message to be sent.

// This callback is applied all tickets with the given ticketType.
matching.SetOnTicketComplete(ticketType, func (ticketProps *TicketProperties, owner *user.User) []byte {
	message := []byte("Matchmaking ticket has completed")
	return message
})

Callback when canceling a ticket

You may assign a callback to be invoked when the owner (creator) of the ticket is canceled.

This callback is invoked for the owner of the ticket only.

// This callback is applied all tickets with the given ticketType.
// The callback is invoked for the owner user of the ticket ONLY.
matching.SetOnTicketCanceled(ticketType uint8, cb func(owner *user.User) {
	// custom logic here.
})

Callback when the owner of the ticket cancels the ticket you matched

You may assign a callback to be invoked when the owner (creator) of the ticket cancels the ticket that you matched.

This callback is invoked for the non-owner users of the ticket that has been canceled.

// This callback is applied all tickets with the given ticketType.
// The callback is invoked for the non-owner users of the ticket ONLY.
matching.SetOnMatchedTicketCanceled(ticketType uint8, cb func(ownerID string, userData *user.User) {
	// custom log here.
})

Detect and handle Matchmaking ticket timeout

You may assign a callback when your matchmaking ticket is timed out.

This callback is invoked for the owner of the ticket only. It is useful when you want to notify all the matched users of your ticket that the ticket has timed out before completing.

// This callback is applied all tickets with the given ticketType.
// The callback is invoked for the owner user of the ticket ONLY.
matching.SetOnTicketTimeout(ticketType, func (owner *user.User) {
	// custom log here.
})

Index ▾

func Add(mid, uqid, tag string, props map[string]int, value map[string]interface{}, ttl int64, limit int) error
func CancelTicket(ticketType uint8, userData *user.User) error
func ClearDefinition(matchingID string)
func ControlTicketParams(params *TicketParams) (int64, int64)
func CreateKeyByProperties(matchingID string, tag string, properties map[string]int) string
func DebugDataDump() (map[string][]*searchItem, error)
func DebugDataDumpWriter(writer io.Writer) error
func Define(profileID string, props map[string]int)
func DefineByJSON(jsonBytes []byte)
func ExposeCommands()
func GetTicketMemberIDs(ticketType uint8, userData *user.User) ([]string, bool)
func GetTicketMemberSIDs(ticketType uint8, userData *user.User) ([]string, bool)
func GetTicketProperties(ticketType uint8, userData *user.User, keys []string) (map[string]interface{}, bool)
func GetTicketProperty(ticketType uint8, userData *user.User, key string) (interface{}, bool)
func HasTicket(ticketType uint8, userData *user.User) bool
func IsTicketOwner(ticketType uint8, userData *user.User) bool
func IsUserInTicketMatchmaking(ticketType uint8, userData *user.User) bool
func JoinRoomByID(ticketType uint8, id string, userData *user.User, cb func(error))
func KickoutFromTicket(ticketType uint8, owner *user.User, targetUserID string, cb func(err error))
func LeaveFromTicketMatchmaking(ticketType uint8, userData *user.User) bool
func LeaveFromTicketMatchmakingWithCallback(ticketType uint8, userData *user.User, cb func(err error))
func MarkTicketAsComplete(ticketType uint8, userData *user.User) error
func MarkTicketAsCompleteWhenExpire(ticketType uint8, userData *user.User) bool
func Remove(matchingID string, uniqueIDList []string, limit int)
func Search(profileIDList []string, tag string, props map[string]int, limit int, callback func(err error, results []interface{}))
func SearchWithRange(profileIDList []string, tag string, searchProps map[string][]int, limit int, callback func(err error, results []interface{}))
func SetCustomJoinCondition(callback func(string, *user.User) bool)
func SetOnDeleteTicketRoom(ticketType uint8, userData *user.User, callback func(id string)) bool
func SetOnIssueTicket(ticketType uint8, cb func(userData *user.User) *TicketParams) bool
func SetOnLeaveTicketRoom(ticketType uint8, userData *user.User, callback func(id string, userData *user.User)) bool
func SetOnMatchedTicketCanceled(ticketType uint8, cb func(ownerID string, userData *user.User)) bool
func SetOnTicketAllowMatchIf(ticketType uint8, cb func(ticketProps *TicketProperties, owner, candidate *user.User) bool) bool
func SetOnTicketCanceled(ticketType uint8, cb func(owner *user.User)) bool
func SetOnTicketComplete(ticketType uint8, cb func(ticketProps *TicketProperties, owner *user.User) []byte) bool
func SetOnTicketMatch(ticketType uint8, cb func(ticket *Ticket, matchedUser, ownerUser *user.User, roomID string, memberIDs []string) bool) bool
func SetOnTicketMemberJoined(ticketType uint8, cb func(ticket *Ticket, joinedUser, ownerUser *user.User, memberIDs []string)) bool
func SetOnTicketMemberJoinedAnnounce(ticketType uint8, cb func(ticket *Ticket, joinedUser, ownerUser *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte)) bool
func SetOnTicketMemberLeave(ticketType uint8, cb func(ticket *Ticket, leftUserData, ownerUser *user.User, roomID string, memberIDs []string)) bool
func SetOnTicketMemberLeaveAnnounce(ticketType uint8, cb func(ticket *Ticket, joinedUser, ownerUser *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte)) bool
func SetOnTicketTimeout(ticketType uint8, cb func(owner *user.User)) bool
func SetTicketProperties(ticketType uint8, userData *user.User, data map[string]interface{}) error
func SetTicketProperty(ticketType uint8, userData *user.User, key string, value interface{}) error
func SetTicketPropertyIfNotExists(ticketType uint8, userData *user.User, key string, value interface{}) bool
func Setup(confpath string)
func StartTicket(ticketType uint8, userData *user.User) error
func StartTicketBackfill(ticketType uint8, owner *user.User) error
func StopTicketBackfill(ticketType uint8, owner *user.User) error
func TTLTest(src []int64) []int64
func Test(method string, data map[string]interface{}) ([]byte, error)
func TestDebugDataDump() map[string][]*searchItem
func TicketBroadcast(ticketType uint8, userData *user.User, ver uint8, cmd uint16, msg []byte) error
func UpdateTicketProperties(ticketType uint8, userData *user.User, data map[string]interface{}, cb func(exists bool, storedValue interface{}, newValue interface{}) (updateValue interface{})) error
func UpdateTicketProperty(ticketType uint8, userData *user.User, key string, value interface{}, cb func(exists bool, storedValue interface{}, newValue interface{}) (updateValue interface{})) error
type AddData
type Client
    func GetTicketMemberClients(ticketType uint8, userData *user.User) ([]*Client, bool)
type FindOwnerData
type JoinRoomData
type LeaveRoomData
type RemoveData
type Room
    func (r *Room) CancelReservation(userData *user.User) bool
    func (r *Room) GetID() string
    func (r *Room) GetMemberIDs() []string
    func (r *Room) GetMemberMeshAddrByUID(uid string) string
    func (r *Room) GetMemberMeshAddrList() []string
    func (r *Room) GetMemberSIDByUID(uid string) string
    func (r *Room) GetMemberSIDs() []string
    func (r *Room) GetMemberUsers() []*user.User
    func (r *Room) GetOwnerUser() (*user.User, bool)
    func (r *Room) GetProperties(keys []string) map[string]interface{}
    func (r *Room) GetProperty(key string) (interface{}, bool)
    func (r *Room) MakeReservation(userData *user.User) bool
    func (r *Room) SetOnDeleted(cb func(id string)) bool
    func (r *Room) SetOnJoin(cb func(id string, userData *user.User) bool) bool
    func (r *Room) SetOnJoined(cb func(id string, userData *user.User)) bool
    func (r *Room) SetOnLeft(cb func(id string, userData *user.User)) bool
    func (r *Room) SetOnTick(interval uint16, cb func(id string)) bool
    func (r *Room) SetOnTickStop(interval uint16, cb func(id string)) bool
    func (r *Room) SetProperties(data map[string]interface{})
    func (r *Room) SetProperty(key string, value interface{})
    func (r *Room) SetPropertyIfNotExists(key string, value interface{}) bool
    func (r *Room) StopAllTicks() bool
    func (r *Room) UpdateProperties(data map[string]interface{}, cb func(bool, interface{}, interface{}) interface{})
    func (r *Room) UpdateProperty(key string, value interface{}, cb func(bool, interface{}, interface{}) interface{})
type RoomBroadcastData
type RoomJoinReturnData
type SearchData
type SearchReturnData
type SearchReturnItemData
type Ticket
    func FindTicket(ticketType uint8, userData *user.User) *Ticket
    func (t *Ticket) GetRoomID() string
    func (t *Ticket) GetTicketType() uint8
    func (t *Ticket) IsTicketFinished() bool
    func (t *Ticket) Start() bool
    func (t *Ticket) Stop() bool
type TicketHolder
type TicketParams
type TicketProperties
type UpdateUserData

func Add

func Add(mid, uqid, tag string, props map[string]int, value map[string]interface{}, ttl int64, limit int) error

Add Adds a searchable matching candidate data.

[NOTE] In order to have long lasting (TTL longer than 60 seconds) searchable items,
       the application must "add" searchable items repeatedly with TTL=60.

[NOTE] Uses mutex lock internally.

Error Cases

╒═════════════════════╤══════════════════════════════════════════════════════════╕
│ Error               │ Reason                                                   │
╞═════════════════════╪══════════════════════════════════════════════════════════╡
│ Invalid profile IDs │ Either missing or invalid matchmaking profile IDs given. │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Unique ID missing   │ Unique ID must not be an empty string.                   │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Properties missing  │ Properties must not be a nil.                            │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Invalid limit value │ Limit must be greater than 1.                            │
╘═════════════════════╧══════════════════════════════════════════════════════════╛

Parameters

mid      - Matching profile ID to add the searchable data to.
unqiueID - Unique ID of the searchable ID.
tag      - Tag is used to isolate and group add and search of matchmaking.
           If an empty string is given, it will be ignored.
props    - Searchable condition properties.
value    - Searchable data to be returned along with the search results.
ttl      - TTL of the searchable item to be added in seconds. Maximum 60 seconds.
limit    - Number of search node to propagate the searchable item data at a time.

func CancelTicket

func CancelTicket(ticketType uint8, userData *user.User) error

CancelTicket stops ticket-based matchmaking started by StartTicket.

Canceling a ticket will disband the matchmaking and remove already-matched users from it.

[IMPORTANT] Only the owner user of the ticket are allowed to cancel the ticket.
[IMPORTANT] CancelTicket will fail after the completion of the ticket.

[NOTE] Uses mutex lock internally.

Error Cases

┌───────────────────────┬───────────────────────────────────────────────────────────┐
│ Error                 │ Reason                                                    │
╞═══════════════════════╪═══════════════════════════════════════════════════════════╡
│ Ticket not found      │ The ticket to cancel is not available.                    │
│ Ticket failed to stop │ The ticket is not active (already completed or canceled). │
└───────────────────────┴───────────────────────────────────────────────────────────┘

Parameters

ticketType - Ticket type of the ticket to cancel.
userData   - The owner user of the ticket to cancel.

func ClearDefinition

func ClearDefinition(matchingID string)

ClearDefinition clears already defined match making definition

matchingID - Matching profile ID to clear the definition.

func ControlTicketParams

func ControlTicketParams(params *TicketParams) (int64, int64)

ControlTicketParams [INTERNALLY USED ONLY]

func CreateKeyByProperties

func CreateKeyByProperties(matchingID string, tag string, properties map[string]int) string

CreateKeyByProperties generates a key from a tag and search or add properties.

This is meant to be used along with DebugDataDump for test and debugging.

The key generated is the actual search condition data used internally to find matches.

func DebugDataDump

func DebugDataDump() (map[string][]*searchItem, error)

DebugDataDump returns the entire matchmaking data held in memory of the server.

[IMPORTANT] This is a debug function and must NOT be used in production code at all.

[NOTE] In order to evaluate key, use CreateKeyByProperties to reproduce the same key with property values that match.
[NOTE] Uses mutex lock internally.

Example of evaluating the dump data keys:

dump := matching.DebugDataDump()

for key, searchItems := range dump {

	// expected key
	expectedKey := matching.CreateKeyByProperties(profileID, tag, expectedProperties)

	// check to see if the key matches the expected key
	if key == expectedKey {
		// good
	} else {
		// bad...
	}

}

func DebugDataDumpWriter

func DebugDataDumpWriter(writer io.Writer) error

DebugDataDumpWriter writes the entire matchmaking data held in memory of the server to io.Writer stream.

[IMPORTANT] This is a debug function and must NOT be used in production code at all.

[NOTE] In order to evaluate key, use CreateKeyByProperties to reproduce the same key with property values that match.
[NOTE] Uses mutex lock internally.

func Define

func Define(profileID string, props map[string]int)

Define defines a match making search schema:

[IMPORTANT] This must be defined on the server that is specified by "targetNodeType" configuration
            because all match making data is stored on those servers.
[IMPORTANT] Profile ID must not contain "\t".

Parameters

profileID  - Unique matching profile ID.
props      - Matching profile condition properties.

In order to perform matchmaking, you must define the rules for matchmakings.

The matchmaking rules are called profiles.

These rules will dictate how matchmaking should be conditioned.

You may combine multiple matchmaking rules and create more complex matchmaking conditions as well.

You must define matchmaking rule profiles before invoking diarkis.Start.

The example below shows a matchmaking rule that uses level and creates buckets of matchmaking pools by the range of 10.

With this profile, each level bucket will pool users with level 0 to 10, 11 to 20, 21 to 30 and so forth...

The string name given as LevelMatch is the unique ID to represents the profile.

levelMatchProfile := make(map[string]int)

levelMatchProfile["level"] = 10

matching.Define("LevelMatch", levelMatchProfile)

You may define as many matching definition as you require as long as each profileID is unique.

func DefineByJSON

func DefineByJSON(jsonBytes []byte)

DefineByJSON defines multiple match making definitions from JSON string.

$(...) represents a variable.

{
	"$(profile ID)": {
		"$(property name)": $(property value as int)
		"$(property name)": $(property value as int)
		"$(property name)": $(property value as int)
	}
}

jsonBytes - Matching profile byte array data to be used to define the profile.

func ExposeCommands

func ExposeCommands()

func GetTicketMemberIDs

func GetTicketMemberIDs(ticketType uint8, userData *user.User) ([]string, bool)

GetTicketMemberIDs returns the list of matched member user IDs.

Cases for the second return value to be false

func GetTicketMemberSIDs

func GetTicketMemberSIDs(ticketType uint8, userData *user.User) ([]string, bool)

GetTicketMemberSIDs returns the list of matched member user SIDs.

Cases for the second return value to be false

Highlights

[IMPORTANT] This function is available ONLY for the owner of the ticket.
[IMPORTANT] If non-user uses this function it returns an empty array.

[NOTE] Uses mutex lock internally.

func GetTicketProperties

func GetTicketProperties(ticketType uint8, userData *user.User, keys []string) (map[string]interface{}, bool)

GetTicketProperties returns key and value pairs as a map.

Cases for the second return value to be false

Example

values, ok := GetTicketProperties(ticketType, userData, []string{ "someKey" })

if !ok {
  // handle error here
}

for key, v := range values {
  // If the value data type is an uint8, of course ;)
  value, ok := util.ToUint8(v)
}

func GetTicketProperty

func GetTicketProperty(ticketType uint8, userData *user.User, key string) (interface{}, bool)

GetTicketProperty returns the value of the given key and if the key does not exist, the second return value will be a false.

Cases for the second return value to be false

Example

v, ok := GetTicketProperty(ticketType, userData, "someKey")

if !ok {
  // handle error here
}

// If the value data type is an uint8, of course ;)
v, ok := util.ToUint8(v)

func HasTicket

func HasTicket(ticketType uint8, userData *user.User) bool

HasTicket returns true if the user given has a matchmaking ticket of the given type in progress.

func IsTicketOwner

func IsTicketOwner(ticketType uint8, userData *user.User) bool

IsTicketOwner returns true if the given user is the owner of its ticket

[NOTE] Uses mutex lock internally.

func IsUserInTicketMatchmaking

func IsUserInTicketMatchmaking(ticketType uint8, userData *user.User) bool

IsUserInTicketMatchmaking returns true if the user is matched with at least one another user in ticket matchmaking of the given ticket type.

The function returns true when:

1. The ticket the user created moves to "waiting" phase.

2. The ticket finds and joins another ticket.

[NOTE] If the user is in a ticket matchmaking, the user is able to send and receive TicketBroadcast messages
       and perform other ticket-related operations.
[NOTE] Uses mutex lock internally.

func JoinRoomByID

func JoinRoomByID(ticketType uint8, id string, userData *user.User, cb func(error))

func KickoutFromTicket

func KickoutFromTicket(ticketType uint8, owner *user.User, targetUserID string, cb func(err error))

KickoutFromTicket forcefully removes a matched member from the matchmaking ticket.

[IMPORTANT] Only the owner of the ticket may execute this operation.

[NOTE] Uses mutex lock internally.

Error Cases

╒═══════════════════════════╤═════════════════════════════════════════════════════════════════════════════╕
│ Error                     │ Reason                                                                      │
╞═══════════════════════════╪═════════════════════════════════════════════════════════════════════════════╡
│ MatchMaker room not found │ The owner user is not in a matchmaking ticket room.                         │
├───────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ Must be the owner         │ Only the owner of the ticket may kick out matched members.                  │
├───────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ Target user not found     │ The target user to kick out is not a matched member.                        │
╘═══════════════════════════╧═════════════════════════════════════════════════════════════════════════════╛

func LeaveFromTicketMatchmaking

func LeaveFromTicketMatchmaking(ticketType uint8, userData *user.User) bool

LeaveFromTicketMatchmaking makes the target user leave the matchmaking that the user has matched and joined.

[NOTE] Uses mutex lock internally.

func LeaveFromTicketMatchmakingWithCallback

func LeaveFromTicketMatchmakingWithCallback(ticketType uint8, userData *user.User, cb func(err error))

LeaveFromTicketMatchmakingWithCallback makes the target user leave the matchmaking that the user has matched and joined.

[NOTE] Uses mutex lock internally.

func MarkTicketAsComplete

func MarkTicketAsComplete(ticketType uint8, userData *user.User) error

MarkTicketAsComplete finishes the ticket as complete immediately.

[IMPORTANT] Only the owner user of the ticket may execute this function.

[NOTE] Uses mutex lock internally.

Error Cases

╒═══════════════════════╤═════════════════════════════════════════════════════════════════════════════╕
│ Error                 │ Reason                                                                      │
╞═══════════════════════╪═════════════════════════════════════════════════════════════════════════════╡
│ Ticket not found      │ Either the ticket is not available or the given user does not own a ticket. │
├───────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ User is not owner     │ User given is not the owner of the ticket.                                  │
├───────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ Ticket room not found │ Internal room of the ticket is missing.                                     │
│                       │ Most likely due of an internal bug causing the ticket data to be corrupt.   │
╘═══════════════════════╧═════════════════════════════════════════════════════════════════════════════╛

Parameters

ticketType - Ticket type.
userData   - Owner user of the ticket to mark as complete.

func MarkTicketAsCompleteWhenExpire

func MarkTicketAsCompleteWhenExpire(ticketType uint8, userData *user.User) bool

MarkTicketAsCompleteWhenExpire marks the ticket as complete, but it will wait until the ticket expires.

[IMPORTANT] Only the owner of the ticket may execute this function.

[NOTE] Uses mutex lock internally.

This is useful when you need to have alternative conditions for matchmaking completion without having all expected members match.

func Remove

func Remove(matchingID string, uniqueIDList []string, limit int)

Remove removes a list of searchable matching candidate items by their unique IDs

[NOTE] This function is very expensive as it will send a message to all related mesh nodes
[NOTE] There maybe some delay with removal operation on multiple servers
       as the instruction to remove must traverse all servers in the cluster.

Parameters

matchingID   - Target matching profile ID to remove items from
uniqueIDList - A list of unique IDs of the searchable items to remove
limit        - Mesh network relay limit
func Search(profileIDList []string, tag string, props map[string]int, limit int, callback func(err error, results []interface{}))

Search searches for matched data based on the given props' values

[IMPORTANT] Search performs search operations on remote servers
            and the number of servers to perform the search is calculated based on the number of the server and distributionRate configuration.
            This means that there is a chance that Search function may miss the server(s) with the desired matchmaking data
            resulting in not finding the intended matchmaking data.

[NOTE] Uses mutex lock internally.

Error Cases

╒═════════════════════╤══════════════════════════════════════════════════════════════╕
│ Error               │ Reason                                                       │
╞═════════════════════╪══════════════════════════════════════════════════════════════╡
│ Invalid profile IDs │ Either missing or invalid matchmaking profile IDs give.      │
├─────────────────────┼──────────────────────────────────────────────────────────────┤
│ Invalid properties  │ Either missing or invalid properties given.                  │
├─────────────────────┼──────────────────────────────────────────────────────────────┤
│ Reliable timeout    │ Communication to another server process timed out or failed. │
╘═════════════════════╧══════════════════════════════════════════════════════════════╛

Parameters

profileIDList - A list of matchmaking profile IDs to search by. The order of the list is the order of search attempts.
tag           - A tag is used to isolate and group search meaning the search will not include tags that do not match.
props         - A property map to act as match making conditions.
limit         - A hard limit to the number of results.
callback      - A callback with the search results or an error.

func SearchWithRange

func SearchWithRange(profileIDList []string, tag string, searchProps map[string][]int, limit int, callback func(err error, results []interface{}))

SearchWithRange searches for matched data based on the given values of props in range.

Each props will have an array of elements with property values that should range from minimum value to maximum value. The function will use those values per property to performance the search.

[IMPORTANT] SearchWithRange performs search operations on remote servers
            and the number of servers to perform the search is calculated based on the number of the server and distributionRate configuration.
            This means that there is a chance that SearchWithRange function may miss the server(s) with the desired matchmaking data
            resulting in not finding the intended matchmaking data.

[IMPORTANT] The number of allowed range properties is limited to two.

Example:

searchProps["level"]     = []int{ 1, 2, 3, 4, 5 } // range property
searchProps["rank"]      = []int{ 1, 2, 3 } // range property
searchProps["matchType"] = []int{ 1 } // regular property
searchProps["league"]    = []int{ 10 } // regular property

Error Cases

╒═════════════════════╤══════════════════════════════════════════════════════════════╕
│ Error               │ Reason                                                       │
╞═════════════════════╪══════════════════════════════════════════════════════════════╡
│ Invalid profile IDs │ Either missing or invalid matchmaking profile IDs give.      │
├─────────────────────┼──────────────────────────────────────────────────────────────┤
│ Invalid properties  │ Either missing or invalid properties given.                  │
├─────────────────────┼──────────────────────────────────────────────────────────────┤
│ Reliable timeout    │ Communication to another server process timed out or failed. │
╘═════════════════════╧══════════════════════════════════════════════════════════════╛

Parameters

profileIDList - A list of matchmaking profile IDs to search by. The order of the list is the order of search attempts.
tag           - A tag is used to isolate and group search meaning the search will not include tags that do not match.
searchProps   - A map with search condition values.
                You are allowed to have maximum two properties with range (more than 1 element in the int array).
                If you have more than two elements with range, it will give you an error.
limit         - A hard limit to the number of results.
callback      - A callback with the search results or an error.

Diagram below shows how it works:

     ┏━━━┓
     ┃ 8 ┃ 8 with ±3 would fall into 5 ~ 11 and that means it matches with items in the bucket of 0 to 10 and 11 to 20
     ┗━┳━┛
     ┏━┻━━━━┓
│    ▼    │ ▼       │         │
│  0 ~ 10 │ 11 ~ 20 │ 21 ~ 30 │
└─────────┴─────────┴─────────┘

To make ranged search more precise, consider using SetOnTicketAllowMatchIf callback for MatchMaker Ticket.

func SetCustomJoinCondition

func SetCustomJoinCondition(callback func(string, *user.User) bool)

SetCustomJoinCondition assigns a on join evaluation callback to be called to evaluate if the user should join or not

func SetOnDeleteTicketRoom

func SetOnDeleteTicketRoom(ticketType uint8, userData *user.User, callback func(id string)) bool

SetOnDeleteTicketRoom assigns a callback which is triggered when a matching room is deleted.

[IMPORTANT] This function is available ONLY for the owner of the ticket.

[NOTE] Uses mutex lock internally.
[NOTE] The callback is invoked while the lock is still being held.
       Avoid using locks in the callback to prevent mutex deadlocks.

func SetOnIssueTicket

func SetOnIssueTicket(ticketType uint8, cb func(userData *user.User) *TicketParams) bool

SetOnIssueTicket assigns a callback to be invoked when the built-in command issue ticket is called.

Parameters

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to be invoked and expected to create and return TicketParams for a new ticket.

The callback is invoked for the owner user only.

Calling StartTicket triggers this callback and TicketParams that it returns will be used to create a new ticket.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] This callback must be assigned to the appropriate ticket type
            in order for StartTicket(ticketType uint8, userData *user.User) to work properly.

[IMPORTANT] userData maybe nil if the user disconnects from the server.

func SetOnLeaveTicketRoom

func SetOnLeaveTicketRoom(ticketType uint8, userData *user.User, callback func(id string, userData *user.User)) bool

SetOnLeaveTicketRoom assigns a callback which is triggered when a member leaves a matching room.

ticket.OnMatchedMemberLeave is valid only while ticket exists whereas this will be triggered also after the ticket completion.

[IMPORTANT] This function is available ONLY for the owner of the ticket.

[NOTE] Uses mutex lock internally.
[NOTE] The callback is invoked while the lock is still being held.
       Avoid using locks in the callback to prevent mutex deadlocks.

func SetOnMatchedTicketCanceled

func SetOnMatchedTicketCanceled(ticketType uint8, cb func(ownerID string, userData *user.User)) bool

SetOnMatchedTicketCanceled assigns a callback to be invoked when a ticket that the user has joined is canceled.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The callback is invoked for the non-owner users of the ticket only.
            For the ticket owner, OnTicketCancled will be invoked.
[IMPORTANT] When the owner of the ticket either disconnects or re-connects to another server, the ticket will be canceled automatically.
            Cancel event is raised event after the completion of the ticket when the owner of the ticket disconnects or re-connects..

Parameters

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to be invoked when a ticket is canceled.

func SetOnTicketAllowMatchIf

func SetOnTicketAllowMatchIf(ticketType uint8, cb func(ticketProps *TicketProperties, owner, candidate *user.User) bool) bool

SetOnTicketAllowMatchIf assigns a callback to be invoked before a match is made to add a custom logic to control if the match found should proceed forward to become an actual match or not..

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
callback   - Callback to be called func(ticketProps *TicketProperties, owner *user.User, candidate *user.User) bool

If the callback returns true, a match will be made.

The callback is invoked for the owner user only.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The callback is invoked while the ticket is holding the mutex lock,
            In order to avoid mutex deadlock, you must not invoke functions that uses mutex lock or retrieve a mutex lock in the callback.
[IMPORTANT] Candidate is the user that has been matched and attempting to join the matched member room.
[IMPORTANT] owner and/or candidate maybe nil if owner and/or candidate disconnects from the server.

func SetOnTicketCanceled

func SetOnTicketCanceled(ticketType uint8, cb func(owner *user.User)) bool

SetOnTicketCanceled assigns a callback to be invoked when an issued ticket has been canceled.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The callback is invoked only for the owner user of the ticket.
            For the non-owner users, OnMatchedTicketCancled will be invoked.

[IMPORTANT] Owner maybe nil if owner is disconnected from the server.

Parameters

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to be invoked when a ticket is canceled.

func SetOnTicketComplete

func SetOnTicketComplete(ticketType uint8, cb func(ticketProps *TicketProperties, owner *user.User) []byte) bool

SetOnTicketComplete assigns a callback to be invoked when an issued ticket successfully completes.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The callback is invoked only for the owner user of the ticket.

[IMPORTANT] Owner maybe nil if owner disconnects from the server.

Parameters

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to be invoked when a matchmaking is complete.

The callback is invoked for the owner user of the ticket only.

The callback function must return a message byte array to be sent to all matched user clients.

func SetOnTicketMatch

func SetOnTicketMatch(ticketType uint8, cb func(ticket *Ticket, matchedUser, ownerUser *user.User, roomID string, memberIDs []string) bool) bool

SetOnTicketMatch assigns a callback to be invoked when a new match is made.

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to control if the match completes the matchmaking or not.
             Room ID passed to the callback is NOT Diarkis Room's ID, but it is the ID of the internal matchmaking room.
             func(ticket *Ticket, matchedUserData *user.User, owner *user.User, roomID string, memberIDs []string) bool

This callback is meant to execute a custom logic for matchmaking completion on every match found.

Having the callback return true will automatically completes the matchmaking.

The callback is invoked for the owner user only.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] callback is invoked for the owner of the ticket only.
[IMPORTANT] matchedUser is a copy of the actual user data of *user.User because the matched user maybe on a different server.
[IMPORTANT] ownerUser and/or matchedUser maybe nil if ownerUser and/or matchedUser disconnects from the server.

func SetOnTicketMemberJoined

func SetOnTicketMemberJoined(ticketType uint8, cb func(ticket *Ticket, joinedUser, ownerUser *user.User, memberIDs []string)) bool

SetOnTicketMemberJoined assigns a callback to be invoked when a matched member joins.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The callback is invoked for the owner user only.
[IMPORTANT] joinedUser is a copy of the actual user data of *user.User because the matched user maybe on a different server.
[IMPORTANT] ownerUser and/or joinedUser maybe nil if ownerUser and/or joinedUser disconnects from the server.

Parameters

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to be invoked when a matched member joins the match.

func SetOnTicketMemberJoinedAnnounce

func SetOnTicketMemberJoinedAnnounce(ticketType uint8, cb func(ticket *Ticket, joinedUser, ownerUser *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte)) bool

SetOnTicketMemberJoinedAnnounce assigns a callback to be invoked when a matched member joins and returns ver, cmd, and message to be used as an announcement.

An announcement is sent to all matched users.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The event is invoked for the owner of the ticket only.

Parameters

ticketType - Apply the callback to the given ticket type.
cb         - Callback to be invoked when a new user successfully match and join.
             ticket     - The matchmaking ticket.
             joinedUser - The user that joined the matchmaking.
                          [IMPORTANT] This is a copy data of the joined user and it is not the actual joined user data.
             ownerUser  - The owner user of the matchmaking ticket.
             memberIDs  - an array of matched member user IDs.

Example:

added := matching.SetOnTicketMemberJoinedAnnounce(ticketType, func(ticket *matching.Ticket, joinedUser, ownerUser *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte) {
	// we will be sending a notification message to all matched users with the following:
	ver = uint8(2)                                  // the notification message will be sent with command ver 2
	cmd = uint16(1010)                              // the notification message will be sent with command ID 1010
	message = []byte(strings.Join(messageIDs, ",")) // the notification message will send a message with comma separated list of matched member user IDs.
	return ver, cmd, message
})

if !added {
  // failed to assign the callback...
}

func SetOnTicketMemberLeave

func SetOnTicketMemberLeave(ticketType uint8, cb func(ticket *Ticket, leftUserData, ownerUser *user.User, roomID string, memberIDs []string)) bool

SetOnTicketMemberLeave assigns a callback to be invoked when a matched member leaves.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The callback is invoked for the owner user only.
[IMPORTANT] leftUser is a copy of the actual user data of *user.User because the matched user maybe on a different server.
[IMPORTANT] ownerUser and/or leftUserData maybe nil if ownerUser and/or leftUserData disconnects from the server.

Parameters

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to be invoked when a matched member leaves the match.

func SetOnTicketMemberLeaveAnnounce

func SetOnTicketMemberLeaveAnnounce(ticketType uint8, cb func(ticket *Ticket, joinedUser, ownerUser *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte)) bool

SetOnTicketMemberLeaveAnnounce assigns a callback to be invoked when a matched member leaves and returns ver, cmd, and message to be used as an announcement.

An announcement is sent to all matched users.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The event is invoked for the owner of the ticket only.

Parameters

ticketType - Apply the callback to the given ticket type.
cb         - Callback to be invoked when a matched user leaves.
             ticket    - The matchmaking ticket.
             leftUser  - The user that left the matchmaking.
                         [IMPORTANT] This is a copy of the left user data and it is not the actual left user data.
             ownerUser - The owner user of the matchmaking ticket.
             memberIDs - an array of matched member user IDs.

Example:

added := matching.SetOnTicketMemberLeaveAnnounce(ticketType, func(ticket *matching.Ticket, leftUser, ownerUser *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte) {
	// we will be sending a notification message to all matched users with the following:
	ver = uint8(2)                                  // the notification message will be sent with command ver 2
	cmd = uint16(1010)                              // the notification message will be sent with command ID 1010
	message = []byte(strings.Join(messageIDs, ",")) // the notification message will send a message with comma separated list of matched member user IDs.
	return ver, cmd, message
})

if !added {
  // failed to assign the callback...
}

func SetOnTicketTimeout

func SetOnTicketTimeout(ticketType uint8, cb func(owner *user.User)) bool

SetOnTicketTimeout assigns a callback to be invoked when an issued ticket has timed out.

[CRITICALLY IMPORTANT] Using pointer variables that are defined outside of the callback closure
                       in the callback closure will cause those pointers to be not garbage collected leading to memory leak.

[IMPORTANT] The callback is invoked for the owner user of the ticket only.
[IMPORTANT] Owner maybe nil if owner disconnects from the server.

Parameters

ticketType - Ticket type is used to group tickets. It will assign the callback to the given ticket type.
cb         - Callback to be invoked when a ticket times out.

func SetTicketProperties

func SetTicketProperties(ticketType uint8, userData *user.User, data map[string]interface{}) error

SetTicketProperties stores a collection of keys and their values to ticket as properties.

Error Cases

┌────────────────┬────────────────────────────────────────────────────────────────┐
│ Error          │ Reason                                                         │
╞════════════════╪════════════════════════════════════════════════════════════════╡
│ Room not found │ MatchMaker ticket is corrupt.                                  │
│ Must be owner  │ Only the ticket owner user is allowed to execute the function. │
└────────────────┴────────────────────────────────────────────────────────────────┘

[IMPORTANT] This function is available ONLY for the owner of the ticket.
[IMPORTANT] If the same key exists, it overwrites the existing value of the same key.

[NOTE] Uses mutex lock internally.

Properties are only primitive values and does not support reference type data such as array and map.

func SetTicketProperty

func SetTicketProperty(ticketType uint8, userData *user.User, key string, value interface{}) error

SetTicketProperty stores a key and a value as a property to the ticket.

Error Cases

┌────────────────┬────────────────────────────────────────────────────────────────┐
│ Error          │ Reason                                                         │
╞════════════════╪════════════════════════════════════════════════════════════════╡
│ Room not found │ MatchMaker ticket is corrupt.                                  │
│ Must be owner  │ Only the ticket owner user is allowed to execute the function. │
└────────────────┴────────────────────────────────────────────────────────────────┘

[IMPORTANT] This function is available ONLY for the owner of the ticket.

[NOTE] Uses mutex lock internally.

If the same key exists, it overwrites the existing value of the same key.

Properties are only primitive values and does not support reference type data such as array and map.

func SetTicketPropertyIfNotExists

func SetTicketPropertyIfNotExists(ticketType uint8, userData *user.User, key string, value interface{}) bool

SetPropertyIfNotExists stores a key and a value as a property to the ticket if the same key does not exist.

[IMPORTANT] This function is available ONLY for the owner of the ticket.

[NOTE] Uses mutex lock internally.

Properties are only primitive values and does not support reference type data such as array and map.

func Setup

func Setup(confpath string)

Setup sets up MatchMaker on the server. You must call this at the start of the server process.

confpath - Absolute path of the configuration file to be loaded.

func StartTicket

func StartTicket(ticketType uint8, userData *user.User) error

StartTicket creates and starts a new ticket-based matchmaking with the given ticket type.

[IMPORTANT] If the owner user (user that created the ticket) disconnects or re-connects, the ticket will be canceled automatically.
            This also means that if you use Diarkis modules (Diarkis Field and Diarkis Room) that
            may require the user to re-connect such as Room and Field,
            it may cause the ticket to be canceled unexpected when the user re-connects.

[IMPORTANT] Complex search properties with large number of properties with long range (lengthy array of values)
            may have negative impact on server performance.

[NOTE] Uses mutex lock internally.

Error Cases

╒════════════════════════════════════════════════════╤════════════════════════════════════════════════════════════════════════════════╕
│ Error                                              │ Reason                                                                         │
╞════════════════════════════════════════════════════╪════════════════════════════════════════════════════════════════════════════════╡
│ MatchMaker is not setup correctly                  │ SetOnIssueTicket callback for the ticketType must be assigned.                 │
├────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ MatchMaker ticket cannot be issued more than once  │ The user is not allowed to issue more than one ticket of the same ticket type. │
├────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ Failed to create a ticket                          │ Given nil for *TicketParams.                                                   │
├────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ Failed to issue a new ticket                       │ The user is still in a previous matchmaking room of the same ticket type.      │
╘════════════════════════════════════════════════════╧════════════════════════════════════════════════════════════════════════════════╛

Parameters

ticketType - Ticket type is used to group tickets. You may issue multiple tickets of different ticket types.
userData   - The user that issues and starts the ticket and becomes the owner of the ticket.

How Ticket Works Internally

Ticket manages MatchMaker's Add and Search internally, so that you do not have to manage and balance their calls.

Ticket has two phases: "Search" and "Waiting"

Ticket starts out in "Search" phase and moves to "Waiting" phase.

The diagram below shows how a ticket operations internally:

                                             ┌──────────────┐
                                             │ Start Ticket │
                                             └──────┬───────┘
                                                    │
                                                    ▼
                                             ┌──────────────┐
                                             │    Search    │
                                             └──────┬───────┘
                                                    │
                                         ┌──────────┴──────────┐
                                         │                     │
    Ticket actively searches for         ▼                     ▼
    other tickets that are       ┌───────────────┐        ┌────────┐ Ticket creates a waiting room for
    in "Wait" phase to match     │ Found Matches │        │  Wait  │ the ticket and waits for
                                 └──────┬────────┘        └──┬──┬──┘ other tickets to "Search" it and match
╭────────────────────────────────────╮  │                    │  │
│ Ticket found other tickets         │  └────────┐    ┌──────┘  └──────────┐
│ to join and matched ticket         ├──────────▷│    │◁──────────┐        │
│ met the required number of users   │           │    │           │        │
╰────────────────────────────────────╯           ▼    ▼           │        ▼
                                      ╔═══════════════════════╗   │   ┌─────────┐ If required number of users are not met
                                      ║ Matchmaking Completed ║   │   │ Timeout │ and ticket duration expires,
                                      ╚═══════════════════════╝   │   └─────────┘ the ticket times out. In order to continue,
                                                       ┌──────────┘               The user must issue and start a new ticket
                                                       │
    ╭──────────────────────────────────────────────────┴─╮
    │ Other tickets searched and found the ticket        │
    │ and the required number of users have been matched │
    ╰────────────────────────────────────────────────────╯

Diagram below visually explains how these two phases of a ticket work and how some of the parameters affect these two phases.

▶︎ SearchInterval 200ms
▶︎ SearchTries    10
▶︎ TicketDuration 4s

▷ Search Phase: The ticket will search every 200ms 10 times
▷ Wait Phase:   If the ticket does not find match, it will wait for the remainder of time until the ticket duration expires

       200ms x 10 searches
  ┌────── Search Phase ──────┐ ┌───────  Waiting Phase ───────┐
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┰────────────────────────────────┐
│  │  │  │  │  │  │  │  │  │  ┃                                │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┸────────────────────────────────┘
 Total 2 seconds of searching      Total 2 seconds of waiting
│                                                              │
└─────────── Ticket duration is 4 seconds in total ────────────┘

This means that a ticket in search phase will only match with tickets in wait phase and vice versa.

Matchmaking result notifications

The table below explains the notifications that the client receives from the server.

┌───────────────────┬──────────────────┬─────────────────┬───────────────────────────────────────────────────────────────────────────┐
│ Notification Type │ Push Command Ver │ Push Command ID │ Description                                                               │
╞═══════════════════╪══════════════════╪═════════════════╪═══════════════════════════════════════════════════════════════════════════╡
│ Success           │ 1                │ 220             │ Matchmaking ticket has been successfully completed                        │
│                   │                  │                 │ and all matched user clients receive this server push.                    │
├───────────────────┼──────────────────┼─────────────────┼───────────────────────────────────────────────────────────────────────────┤
│ Timeout           │ 1                │ 219             │ Matchmaking ticket has failed and it has been discarded.                  │
│                   │                  │                 │ All user clients that matched receives this server push.                  │
├───────────────────┼──────────────────┼─────────────────┼───────────────────────────────────────────────────────────────────────────┤
│ Cancel            │ 1                │ 222             │ Matchmaking ticket has been canceled                                      │
│                   │                  │                 │ and all user clients that matched receives this server push.              │
├───────────────────┼──────────────────┼─────────────────┼───────────────────────────────────────────────────────────────────────────┤
│ Broadcast         │ 1                │ 224             │ Matchmaking ticket sends a broadcast message to all matched user clients. │
└───────────────────┴──────────────────┴─────────────────┴───────────────────────────────────────────────────────────────────────────┘

Calling StartTicket raises The callback assigned by SetOnIssueTicket and a new ticket will be created using the given ticket parameters.

Example with SetOnIssueTicket callback

// the callback will be invoked by matching.StartTicket
matching.SetOnIssueTicket(sampleTicketType, func(userData *user.User) *matching.TicketParams {
	return &matching.TicketParams{
		ProfileIDs:       []string{"RankMatch"},
		MaxMembers:       2,
		SearchInterval:   300, // 300 milliseconds interval of search
		SearchTries:      4,   // allow 4 consecutive empty search results up to 4 times before moving on to wait
		TicketDuration:   20,  // ticket lasts for 20 seconds
		HowMany:          20,  // up to 20 search results
		Tag:              "",  // if we want to group tickets using tag add the string value here
		AddProperties:    &map[string]int{ "Rank": 3 }, // wait for other users to find me and my rank is 3
		SearchProperties: &map[string][]int{ "Rank": &[]int{ 1, 2, 3, 4, 5 } }, // search for other users within the range of 1 to 5 and property is "Rank"
	}
})

err := matching.StartTicket(sampleTicketType, userData)

if err != nil {
	// error...
}

▶︎ SearchPropeties

ServerProperties dictate conditions for searches that is performed internally.

[IMPORTANT] The number of allowed range properties is limited to two.
            When you have multiple elements in a search property, it is considered as a range property.

[IMPORTANT] SearchProperties operates AND operations. It means that with multiple search properties,
            the search must satisfy all search properties in order to match.

Diagram below shows how each search property operates:

     ┏━━━┓
     ┃ 8 ┃ 8 with ±3 would fall into 5 ~ 11 and that means it matches with items in the bucket of 0 to 10 and 11 to 20
     ┗━┳━┛
     ┏━┻━━━━┓
│    ▼    │ ▼       │         │
│  0 ~ 10 │ 11 ~ 20 │ 21 ~ 30 │
└─────────┴─────────┴─────────┘

Example:

searchProps["level"]     = []int{ 1, 2, 3, 4, 5 } // range property
searchProps["rank"]      = []int{ 1, 2, 3 } // range property
searchProps["matchType"] = []int{ 1 } // regular property
searchProps["league"]    = []int{ 10 } // regular property

Order of range search property's search:

The range search properties will look for matches in the order of the array. It means that if []int{ 1, 2, 3, 4, 5 } is given, it will start from 1 and continue up to 5 until it finds it matches.

▶︎ Callbacks

Every callback is assigned to given ticket type and invoked based on assigned ticket type.

func StartTicketBackfill

func StartTicketBackfill(ticketType uint8, owner *user.User) error

StartTicketBackfill starts "backfill" on a ticket that has already been completed.

Backfill allows other users to match and join "after" the completion of the ticket.

When ticket is completed, the ticket itself will be deleted, but the matched users remain in the ticket room.

The owner user that wishes to start backfill must be a member of this ticket room.

[IMPORTANT] If the ticket is full, no users may match and join even with backfill started.
[IMPORTANT] In order to stop backfill, you must invoke StopTicketBackfill.

[NOTE] Uses mutex lock internally.

Error Cases

┌────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│ Error                                              │ Reason                                                                         │
╞════════════════════════════════════════════════════╪════════════════════════════════════════════════════════════════════════════════╡
│ Completed ticket room must be available            │ The ticket and its internal room have been discarded.                          │
│ Ticket already exists                              │ Either the ticket has not been completed or backfill has already been started. │
│ MatchMaker is not setup correctly                  │ SetOnIssueTicket callback for the ticketType must be assigned.                 │
│ MatchMaker ticket cannot be issued more than once  │ The user is not allowed to issue more than one ticket of the same ticket type. │
│ Failed to create a ticket                          │ Given nil for *TicketParams.                                                   │
│ Failed to issue a new ticket                       │ The user is still in a previous matchmaking room of the same ticket type.      │
└────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘

Parameters

ticketType - Type of the completed ticket to start backfill.
owner      - Ticket's owner user.

func StopTicketBackfill

func StopTicketBackfill(ticketType uint8, owner *user.User) error

StopTicketBackfill stops backfill ticket.

[NOTE] Uses mutex lock internally.

Error Cases

┌────────────────────────────────┬─────────────────────────────────────────────────────────────────────────┐
│ Error                          │ Reason                                                                  │
╞════════════════════════════════╪═════════════════════════════════════════════════════════════════════════╡
│ Backfill ticket not found      │ Backfill ticket to stop does not exist.                                 │
│ Backfill ticket room not found │ Backfill ticket room has been discarded. (All users have disconnected.) │
└────────────────────────────────┴─────────────────────────────────────────────────────────────────────────┘

Parameters

ticketType - Backfill ticket type to stop backfill.
owner      - Backfill ticket owner user.

func TTLTest

func TTLTest(src []int64) []int64

TTLTest this is used ONLY in tests

func Test

func Test(method string, data map[string]interface{}) ([]byte, error)

Test this is used ONLY in tests

func TestDebugDataDump

func TestDebugDataDump() map[string][]*searchItem

TestDebugDataDump is used ONLY in internal tests.

func TicketBroadcast

func TicketBroadcast(ticketType uint8, userData *user.User, ver uint8, cmd uint16, msg []byte) error

TicketBroadcast sends a reliable message to all matched users with the given ver, cmd, and message byte array.

[NOTE] This function can be executed by any matched member user.
[NOTE] Uses mutex lock internally.

Parameters

ticketType - MatchMaker Ticket's type.
userData   - Matched member user of the ticket.
ver        - Broadcast message command version.
cmd        - Broadcast message command ID.
msg        - Broadcast message data in byte array format.

func UpdateTicketProperties

func UpdateTicketProperties(
    ticketType uint8,
    userData *user.User,
    data map[string]interface{},
    cb func(exists bool, storedValue interface{}, newValue interface{}) (updateValue interface{})) error

UpdateTicketProperties changes the existing property values of ticket.

The callback is invoked while the internal lock is still held, locking inside the callback may cause mutex deadlock.

Error Cases

┌────────────────┬────────────────────────────────────────────────────────────────┐
│ Error          │ Reason                                                         │
╞════════════════╪════════════════════════════════════════════════════════════════╡
│ Room not found │ MatchMaker ticket is corrupt.                                  │
│ Must be owner  │ Only the ticket owner user is allowed to execute the function. │
└────────────────┴────────────────────────────────────────────────────────────────┘

Highlights

[IMPORTANT] This function is available ONLY for the owner of the ticket.
[IMPORTANT] This function is NOT asynchronous.
[IMPORTANT] Properties are only primitive values and does not support reference type data such as array and map.

[NOTE] Uses mutex lock internally.
[NOTE] The callback is invoked while the lock is still being held.
       Avoid using locks in the callback to prevent mutex deadlocks.

Parameters

ticketType - Ticket type is used to find the ticket.
userData   - Owner user data of the ticket.
data       - A map of key and value pair to be stored or updated as properties.
cb         - Callback to be invoked on every key and value pair to handle the update.
             func(exists bool, storedValue interface{}, updateValue interface{}) (updatedValue interface{})
               - exists      - Indicates if the same key already exists or not
               - storedValue - Existing value that is stored as a property. If the key does not exist it is a nil.
               - updateValue - The value to be used to update/replace or set.

func UpdateTicketProperty

func UpdateTicketProperty(
    ticketType uint8,
    userData *user.User,
    key string,
    value interface{},
    cb func(exists bool, storedValue interface{}, newValue interface{}) (updateValue interface{})) error

UpdateTicketProperty changes the existing property value of ticket.

Error Cases

┌────────────────┬────────────────────────────────────────────────────────────────┐
│ Error          │ Reason                                                         │
╞════════════════╪════════════════════════════════════════════════════════════════╡
│ Room not found │ MatchMaker ticket is corrupt.                                  │
│ Must be owner  │ Only the ticket owner user is allowed to execute the function. │
└────────────────┴────────────────────────────────────────────────────────────────┘

Highlights

[IMPORTANT] This function is available ONLY for the owner of the ticket.
[IMPORTANT] This function is NOT asynchronous.
[IMPORTANT] Properties are only primitive values and does not support reference type data such as array and map.

[NOTE] Uses mutex lock internally.
[NOTE] The callback is invoked while the lock is still being held.
       Avoid using locks in the callback to prevent mutex deadlocks.

Parameters

ticketType - Ticket type is used to find the ticket.
userData   - Owner user data of the ticket.
key        - A key of the property to be updated.
value      - A value of the property to be updated with.
cb         - Callback to be invoked on every key and value pair to handle the update.
             func(exists bool, storedValue interface{}, updateValue interface{}) (updatedValue interface{})
               - exists      - Indicates if the same key already exists or not
               - storedValue - Existing value that is stored as a property. If the key does not exist it is a nil.
               - updateValue - The value to be used to update/replace or set.

type AddData

AddData represents internally used matchmaking data

type AddData struct {
    InternalID string                 `json:"internalID"`
    MatchingID string                 `json:"matchingID"`
    Tag        string                 `json:"tag"`
    Props      map[string]int         `json:"props"`
    Value      map[string]interface{} `json:"value"`
    TTL        int64                  `json:"ttl"`
}

type Client

Client represents matchmaking ticket matched member user client.

type Client struct {
    ID  string
    SID string
    // Available ONLY for UDP
    PublicAddress string
    // Available ONLY for UDP
    PrivateAddressBytes []byte
    // User property data copied when the user joins a matchmaking ticket
    UserData map[string]interface{}
}

func GetTicketMemberClients

func GetTicketMemberClients(ticketType uint8, userData *user.User) ([]*Client, bool)

GetTicketMemberClients returns the list of matched member user clients.

Cases for the second return value to be false

Highlights

[IMPORTANT] This function is available ONLY for the owner of the ticket.
[IMPORTANT] If non-user uses this function it returns an empty array.

[NOTE] Uses mutex lock internally.

type FindOwnerData

FindOwnerData represents internally used data

type FindOwnerData struct {
    SID string `json:"sid"`
}

type JoinRoomData

JoinRoomData represents internally used data

type JoinRoomData struct {
    TicketType uint8                  `json:"ticketType"`
    ID         string                 `json:"id"`
    SID        string                 `json:"sid"`
    MeshAddr   string                 `json:"meshAddr"`
    UserData   map[string]interface{} `json:"userData"`
}

type LeaveRoomData

LeaveRoomData represents internally used data

type LeaveRoomData struct {
    TicketType uint8  `json:"ticketType"`
    ID         string `json:"id"`
    UID        string `json:"uid"`
    SID        string `json:"sid"`
}

type RemoveData

RemoveData represents internally used matchmaking removal data

type RemoveData struct {
    MatchingID string   `json:"matchingID"`
    UniqueIDs  []string `json:"uniqueIDs"`
}

type Room

Room represents matchmaker ticket room that is used internally

type Room struct {
    sync.RWMutex
    // contains filtered or unexported fields
}

func (*Room) CancelReservation

func (r *Room) CancelReservation(userData *user.User) bool

CancelReservation removes the reservation of the given user from the ticket room.

[NOTE] Uses mutex lock internally.

func (*Room) GetID

func (r *Room) GetID() string

GetID returns the room ID.

func (*Room) GetMemberIDs

func (r *Room) GetMemberIDs() []string

GetMemberIDs returns an array of matched member IDs.

[NOTE] Uses mutex lock internally.

func (*Room) GetMemberMeshAddrByUID

func (r *Room) GetMemberMeshAddrByUID(uid string) string

GetMemberMeshAddrByUID returns a mesh address of a member.

Returns an empty string if the member is not found or invalid.

[NOTE] Uses mutex lock internally.

func (*Room) GetMemberMeshAddrList

func (r *Room) GetMemberMeshAddrList() []string

GetMemberMeshAddrList returns an array of internal server address of each matched user.

[NOTE] Uses mutex lock internally.

func (*Room) GetMemberSIDByUID

func (r *Room) GetMemberSIDByUID(uid string) string

GetMemberSIDByUID returns the member's SID.

It returns and empty string if the member is not found or invalid.

[NOTE] Uses mutex lock internally.

func (*Room) GetMemberSIDs

func (r *Room) GetMemberSIDs() []string

GetMemberSIDs returns an array of matched member SIDs.

[NOTE] Uses mutex lock internally.

func (*Room) GetMemberUsers

func (r *Room) GetMemberUsers() []*user.User

GetMemberUsers returns an array of matched member user copies.

[IMPORTANT] The return array contains copies of member users.

[NOTE] Uses mutex lock internally.

func (*Room) GetOwnerUser

func (r *Room) GetOwnerUser() (*user.User, bool)

GetOwnerUser returns the ticket owner user.

[IMPORTANT] Returned owner user is NOT a copy.

[NOTE] Uses mutex lock internally.

func (*Room) GetProperties

func (r *Room) GetProperties(keys []string) map[string]interface{}

GetProperties returns key and value pairs as a map.

[NOTE] Properties are only primitive values and does not support reference type data such as array and map.
[NOTE] Uses mutex lock internally.

If a value of a given key does not exist, the returned map will have a nil as a value of the key.

The returned property value is an interface{}, in order to type assert safely, please use Diarkis' util package functions.

Example:

values, ok := r.GetProperties([]string{ "someKey" })

if !ok {
  // handle error here
}

for key, v := range values {
  // If the value data type is an uint8, of course ;)
  value, ok := util.ToUint8(v)
}

func (*Room) GetProperty

func (r *Room) GetProperty(key string) (interface{}, bool)

GetProperty returns the value of the given key and if the key does not exist, the second return value will be a false.

[NOTE] Properties are only primitive values and does not support reference type data such as array and map.
[NOTE] Uses mutex lock internally.

The returned property value is an interface{}, in order to type assert safely, please use Diarkis' util package functions.

Example:

v, ok := r.GetProperty("someKey")

if !ok {
  // handle error here
}

// If the value data type is an uint8, of course ;)
v, ok := util.ToUint8(v)

func (*Room) MakeReservation

func (r *Room) MakeReservation(userData *user.User) bool

MakeReservation allows the user to reserve a spot in the ticket room.

[NOTE] Uses mutex lock internally.

func (*Room) SetOnDeleted

func (r *Room) SetOnDeleted(cb func(id string)) bool

SetOnDeleted assigns a callback on ticket room deletion.

[NOTE] You may assign multiple callbacks to a room.
[NOTE] Uses mutex lock internally.

func (*Room) SetOnJoin

func (r *Room) SetOnJoin(cb func(id string, userData *user.User) bool) bool

SetOnJoin assigns a callback on ticket room to be invoked when a new member is matched and attempting to join the match.

The callback returns a bool and if you return false, the matched user will be rejected and will not match and join.

[NOTE] You may assign multiple callbacks to a room.
[NOTE] Uses mutex lock internally.

func (*Room) SetOnJoined

func (r *Room) SetOnJoined(cb func(id string, userData *user.User)) bool

SetOnJoined assigns a callback on ticket room to be invoked when a new member is matched.

[NOTE] You may assign multiple callbacks to a room.
[NOTE] Uses mutex lock internally.

func (*Room) SetOnLeft

func (r *Room) SetOnLeft(cb func(id string, userData *user.User)) bool

SetOnLeft assigns a callback on ticket room to be invoked when a member of matched user leaves the match.

[NOTE] You may assign multiple callbacks to a room.
[NOTE] Uses mutex lock internally.

func (*Room) SetOnTick

func (r *Room) SetOnTick(interval uint16, cb func(id string)) bool

SetOnTick assigns a callback to be invoked at every given interval.

[IMPORTANT] A single callback may be assigned per tick interval.
            You may not assign multiple callback a tick with the same interval.

[NOTE] Uses mutex lock internally.

Parameters

interval - Tick interval in seconds.
cb       - Callback to be invoked at every tick.

func (*Room) SetOnTickStop

func (r *Room) SetOnTickStop(interval uint16, cb func(id string)) bool

SetOnTickStop assigns a callback to be invoked when a tick of the room stops.

[IMPORTANT] Only one callback may be assigned to a room.
[NOTE] Uses mutex lock internally.

func (*Room) SetProperties

func (r *Room) SetProperties(data map[string]interface{})

SetProperties stores a collection of keys and their values to ticket room.

If the same key exists, it overwrites the existing value of the same key.

[NOTE] Properties are only primitive values and does not support reference type data such as array and map.
[NOTE] Uses mutex lock internally.

func (*Room) SetProperty

func (r *Room) SetProperty(key string, value interface{})

SetProperty stores a key and value data to ticket room.

If the same key exists, it overwrites the existing value of the same key.

[NOTE] Properties are only primitive values and does not support reference type data such as array and map.
[NOTE] Uses mutex lock internally.

func (*Room) SetPropertyIfNotExists

func (r *Room) SetPropertyIfNotExists(key string, value interface{}) bool

SetPropertyIfNotExists stores a key and value data to ticket room if the same key does not exist.

[NOTE] Properties are only primitive values and does not support reference type data such as array and map.
[NOTE] Uses mutex lock internally.

func (*Room) StopAllTicks

func (r *Room) StopAllTicks() bool

StopAllTicks stops all tick loops.

[NOTE] Uses mutex lock internally.

func (*Room) UpdateProperties

func (r *Room) UpdateProperties(data map[string]interface{}, cb func(bool, interface{}, interface{}) interface{})

UpdateProperties changes the existing property values of ticket room.

The callback is invoked while the internal lock is still held, locking inside the callback may cause mutex deadlock.

[NOTE] Properties are only primitive values and does not support reference type data such as array and map.
[NOTE] Uses mutex lock internally.

Parameters

data - A map of key and value pair to be stored as properties.
cb   - Callback to be invoked on every key and value pair to handle the update.
       func(exists bool, storedValue interface{}, updateValue interface{}) (updatedValue interface{})
         - exists      - Indicates if the same key already exists or not
         - storedValue - Existing value that is stored as a property. If the key does not exist it is a nil.
         - updateValue - The value to be used to update/replace or set.

func (*Room) UpdateProperty

func (r *Room) UpdateProperty(key string, value interface{}, cb func(bool, interface{}, interface{}) interface{})

UpdateProperty changes the existing property value of ticket room.

The callback is invoked while the internal lock is still held, locking inside the callback may cause mutex deadlock.

[NOTE] Properties are only primitive values and does not support reference type data such as array and map.

[NOTE] Uses mutex lock internally.

Parameters

key   - A key of the property to be updated.
value - A value of the property to be updated with.
cb    - Callback to be invoked on every key and value pair to handle the update.
        func(exists bool, storedValue interface{}, updateValue interface{}) (updatedValue interface{})
         - exists      - Indicates if the same key already exists or not
         - storedValue - Existing value that is stored as a property. If the key does not exist it is a nil.
         - updateValue - The value to be used to update/replace or set.

type RoomBroadcastData

RoomBroadcastData represents internally used broadcast message data

type RoomBroadcastData struct {
    ID         string   `json:"id"`
    Ver        uint8    `json:"ver"`
    Cmd        uint16   `json:"cmd"`
    Msg        []byte   `json:"msg"`
    MemberSIDs []string `json:"memberSIDs"`
}

type RoomJoinReturnData

RoomJoinReturnData represents internally used data

type RoomJoinReturnData struct {
    LockKey string `json:"lockKey"`
}

type SearchData

SearchData represents internally used matchmaking data

type SearchData struct {
    MatchingID string           `json:"matchingID"`
    Tag        string           `json:"tag"`
    Props      map[string][]int `json:"props"`
    Limit      int              `json:"limit"`
}

type SearchReturnData

SearchReturnData represents internally used data

type SearchReturnData struct {
    Results []*SearchReturnItemData `json:"results"`
}

type SearchReturnItemData

SearchReturnItemData represents internally used data

type SearchReturnItemData struct {
    ID    string      `json:"id"`
    TTL   int64       `json:"ttl"`
    Value interface{} `json:"value"`
}

type Ticket

Ticket represents a matchmaking ticket that manages a life cycle of issued ticket

OnMatch                       - Raised when a remote user matches. By returning true, you may complete the ticket
                                and raise OnComplete (OnComplete event is captured by matching.SetOnComplete callback)
OnMatchedMemberJoined         - Raised when a matched member completes join.
OnMatchedMemberJoinedAnnounce - Raised when a matched member completes join
                                and returns ver, cmd, and message to be sent to all matched members.
OnMatchedMemberLeaveAnnounce  - Raised when a matched member leaves and returns ver, cmd, and message to be sent to all matched members.
OnMatchedMemberLeave          - Raised when a matched member user leave the match.
OnTimeout                     - Raised when the ticket times out.
type Ticket struct {
    OnMatch                       func(ticket *Ticket, userData *user.User, owner *user.User, roomID string, memberIDs []string) bool
    OnMatchedMemberJoined         func(ticket *Ticket, userData *user.User, owner *user.User, memberIDs []string)
    OnMatchedMemberLeave          func(ticket *Ticket, userData *user.User, owner *user.User, roomID string, memberIDs []string)
    OnTimeout                     func(userData *user.User)
    OnMatchedMemberJoinedAnnounce func(ticket *Ticket, userData, owner *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte)
    OnMatchedMemberLeaveAnnounce  func(ticket *Ticket, userData, owner *user.User, memberIDs []string) (ver uint8, cmd uint16, message []byte)
    // contains filtered or unexported fields
}

func FindTicket

func FindTicket(ticketType uint8, userData *user.User) *Ticket

FindTicket returns the valid matchmaking ticket that the user has.

[IMPORTANT] This function works with the owner of the ticket only.
[IMPORTANT] IF the user given does not own a ticket, it returns nil.

[NOTE] Uses mutex lock internally.

ticketType - MatchMaker ticket type.
userData   - The owner user of the ticket.

func (*Ticket) GetRoomID

func (t *Ticket) GetRoomID() string

GetRoomID returns the room ID of the ticket.

func (*Ticket) GetTicketType

func (t *Ticket) GetTicketType() uint8

GetTicketType returns the ticket type of the *Ticket instance.

func (*Ticket) IsTicketFinished

func (t *Ticket) IsTicketFinished() bool

IsTicketFinished returns true if the ticket has finished its entire operations.

func (*Ticket) Start

func (t *Ticket) Start() bool

Start starts the life cycle of a ticket.

[NOTE] Uses mutex lock internally.

func (*Ticket) Stop

func (t *Ticket) Stop() bool

Stop interrupts the ticket and stops all matchmaking operations.

[NOTE] Uses mutex lock internally.

type TicketHolder

TicketHolder represents add and search properties of the ticket holder user.

AddProperties    - Add properties of the ticket: Add properties are used to be found by other tickets.
SearchProperties - Search properties of the ticket: Search properties are used to search for other tickets.
ApplicationData  - May hold application data that maybe added from the application.
                   ApplicationData must NOT be a struct or must NOT contain struct.
type TicketHolder struct {
    AddProperties    map[string]int
    SearchProperties map[string][]int
    ApplicationData  interface{}
}

type TicketParams

TicketParams parameter struct for issueTicket

[IMPORTANT] AddProperties and SearchProperties are limited to have up to 2 properties.

Properties

ProfileIDs                  - A list of profiles to add to and search against.
AddProfileIDs               - Optional list of profiles to add. If this is used, ProfileIDs will be overridden for add.
SearchProfileIDs            - Optional list of profiles to search. If this is used, ProfileIDs will be overridden for search.
Tag                         - A string tag to group matchmaking data by the same tag.
                              Data with different tag do NOT match even with the matching properties.
                              Leave it with an empty string if no need.
AddProperties               - Matchmaking properties (conditions) for add (being a host waiting)
SearchProperties            - Matchmaking properties to used for search
                              Each property may contain a range of property values i.e. []int{ 1, 2, 3, 4, 5 } etc.
                              The number of properties allowed is 2,
                              if you exceed the number of properties, two properties will be randomly chosen.
ApplicationData             - May hold application data that maybe added from the application.
                              ApplicationData must NOT be a struct or must NOT contain struct.
MaxMembers                  - Maximum number of matchmaking users per matchmaking.
                              When matched users reach this number, the matchmaking will complete as success
TicketDuration              - Duration of the ticket to be valid in seconds.
                              Minimum value for TicketDuration is 10 seconds.
SearchInterval              - The interval for search in milliseconds
TimeoutExtensionOnMatchJoin - Timeout extension in seconds to be added every time a new match joins. Leave it with 0 if no need.
SearchTries                 - Number of empty search results to tolerate before giving up and moving on to hosting (add)
EmptySearches               - If the number of empty search results reach EmptySearches, the ticket will forcefully change to add phase.
                              If 0 is given, this feature will be disabled. Default is 0.
HowMany                     - Matchmaking profile IDs to use for search and add
                              Leave this empty if you do not need to repeat the operation set.

Breaking down how a ticket works under the hood

A ticket has two phases. When you start a ticket, it starts as a search phase where it actively searches other tickets that are in waiting phase. Once a certain time passes and the search yields no matches, ticket then switches to waiting phase where it waits for other searching tickets to find it.

Diagram below visually explains how these two phases of a ticket work and how some of the parameters affect these two phases.

▶︎ SearchInterval 200ms
▶︎ SearchTries    10
▶︎ TicketDuration 4s

▷ Search Phase: The ticket will search every 200ms 10 times
▷ Wait Phase:   If the ticket does not find match, it will wait for the remainder of time until the ticket duration expires

       200ms x 10 searches
  ┌────── Search Phase ──────┐ ┌───────  Waiting Phase ───────┐
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┰────────────────────────────────┐
│  │  │  │  │  │  │  │  │  │  ┃                                │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┸────────────────────────────────┘
 Total 2 seconds of searching      Total 2 seconds of waiting
│                                                              │
└─────────── Ticket duration is 4 seconds in total ────────────┘

This means that a ticket in search phase will only match with tickets in wait phase and vice versa.

TicketParams Tip

It usually helps to have randomized values for SearchInterval, SearchTries, and TicketDuration.

This is because every ticket strictly follows search → wait flow. Having every ticket with different search and wait durations will help tickets find other tickets.

type TicketParams struct {
    ProfileIDs                  []string
    AddProfileIDs               []string
    SearchProfileIDs            []string
    Tag                         string
    AddProperties               map[string]int
    SearchProperties            map[string][]int
    ApplicationData             interface{}
    MaxMembers                  uint8
    TicketDuration              uint8
    SearchInterval              uint16
    TimeoutExtensionOnMatchJoin uint8
    SearchTries                 uint8
    EmptySearches               uint8
    HowMany                     uint8
}

type TicketProperties

TicketProperties represents both the owner of the matched ticket and the candidate to be matched.

It is primarily meant to be used for SetOnTicketAllowMatchIf callback.

Owner           - Represents ticket owner's add and search properties (user that perform add/waiting).
                  Owner add and search properties are pointers to the original properties
                  and changing the values may influence the matchmaking.
Candidate       - Represents match candidate's add and search properties (user that performs searches).
                  Candidate add and search properties are pointers to the original properties
                  and changing the values may influence the matchmaking.
type TicketProperties struct {
    Owner     *TicketHolder
    Candidate *TicketHolder
}

type UpdateUserData

UpdateUserData represents internally used user data update

type UpdateUserData struct {
    RoomID           string `json:"id"`
    UserID           string `json:"ID"`
    SID              string `json:"SID"`
    PublicAddr       string `json:"PublicAddr"`
    PrivateAddrBytes []byte `json:"PrivateAddrBytes"`
    MeshAddr         string `json:"meshAddr"`
}