# open-match匹配流程
(金慶的專欄 2019.1)
https://github.com/GoogleCloudPlatform/open-match
open-match 是一個通用的游戲匹配框架。
由游戲提供自定義的匹配算法(以docker鏡像的方式提供)。
分為多個進程,各進程之間共享一個 redis.
* 前端, 接收玩家加入 redis,成功后通知玩家房間服地址
* 后端,設置一局游戲的匹配規則,設置房間服地址
* MMFOrc,啟動匹配算法(MMF)
* MMF, 自定義匹配算法,讀取 redis 獲取玩家,匹配成功就將結果寫入 redis. 僅匹配一局就退出。
游戲服中連接 open-match 的前端與后端的進程,分別稱為 frontendclient 和 Director。
輸入分2部份,一是玩家信息,二是對局信息。
Director 向后端輸入對局信息,就會收到一個接一個的對局人員列表.
Director 需要為每個對局開房間,然后通知后端房間地址。
后端將房間地址寫入 redis, 然后前端讀取到房間地址,就通知 frontendclient,讓玩家進入房間。
## test/cmd/frontendclient
模擬大廳服或組隊服,連接前端API, 請求匹配玩家/隊伍。成功后將收到房間服(DGS)的地址(Assignment)。
Player 實際上是一個隊伍,其中ID字段是用空格分隔的多個ID.
雖然參數類型都是 Player, CreatePlayer() 參數為整個隊伍,而 GetUpdates() 參數是單個玩家。
main() 中創建多個玩家,每個玩家調用 GetUpdates() 以獲取結果,go waitForResults() 中處理結果。
waitForResult() 讀取流中的匹配結果,壓入 resultsChan(但好像 resultsChan 僅用于打印)。
所有玩家合并到 g 實例中,然后調用 CreatePlayer() 請求匹配。
cleanup() 調用 DeletePlayer() 來刪除匹配請求,不僅需刪除整個隊伍,也需要刪除單個玩家。
好像最后取結果沒取對地方,應該從 resultChan 中獲取 Assignment, 并用該地址 udpClient().
看了該示例就可以理解 frontend.proto
## examples/backendclient
MatchObject.Properties 是從 testprofile.json 讀取的,應該改名為 Profile 是否更好點?
pbProfile 是 MatchObject,Profile 等同于 MatchObject?
Profile 的定義是 MMF 所需的所有參數。
`pbProfile.Properties = jsonProfile` 重復了2遍。
ListMatches()列出這個Profile的所有匹配。
收到一個匹配后,須用CreateAssignments()將房間服地址, 稱為 Assignment, 發送到所有游戲客戶端。
## cmd/frontendapi
CreatePlayer() 將 Player 對象寫入 redis, 鍵值為 Player.Id, 類型為 HSET。
對 Player 的每個 attribute,添加到 ZSET 中去。
此處 Player 是一組玩家。
GetUpdates() 每隔2s讀取redis, Player數據有變化時就發送。此處 Player 是單個玩家。
如果CreatePlayer()中隊伍只有一個玩家,
則寫入的Player與GetUpdates()中讀取的玩家是同一個redis鍵。
## cmd/backendapi
CreateMatch() 中 profile 類型為 MatchObject, 是一個比賽的限制條件。
profile 先寫入 redis, 鍵為 profile.Id.
`requestKey := xid() + "." + profile.Id`,
并將 requestKey 加入 redis 集合 "profileq"。
然后每2s查詢 redis, 看是否有 requestKey 鍵出現,并返回該值。
ListMatch() 每2s調用一次 CreateMatch().
DeleteMatch() 僅僅刪除 Id 這個鍵。
CreateAssignments() 為多個隊伍設置Assignment, 即房間地址。
遍歷所有Roster中的Player對象,在redis中設置Assignment.
(Assignment 更改后,會觸發前端更新。)
將所有 Player.Id 從 "proposed" 移到 "deindexed",這兩個是 ZSET, 分值為加入時間。
Roster 應該是比賽中的陣營,如紅方,藍方,每個陣營中可有多個隊伍。
DeleteAssignments() 僅僅遍歷所有 Player 對象來刪除 Assignment 字段。
## cmd/mmforc
匹配流程是由 mmforc (matchmaking function orchestrator) 控制的。
mmforc 每秒從 redis 的 profileq 中取出 100 個成員, 其中 profileq 是個set類型,
使用命令為`SPOP profileq 100`.
對每個 profile, 創建一個 k8s 任務:
```
// Kick off the job asynchrnously
go mmfunc(ctx, profile, cfg, defaultMmfImages, clientset, &pool)
```
每隔10s, 還有所有匹配任務都完成后,需要 `checkProposals`, 即創建 evaluator 任務。
profileq 中的元素 profile 為字符串,matchObjectID.profileID。
以 profileID 為鍵,可以從 redis 讀取 profile 的內容, profile 是個 MatchObject 對象。
profile 的內容為 json 串,其中 "jsonkeys.mmfImages" 為 mmf (matchmaking function) 鏡像。
如果profile讀取失敗,或者 mmfImages 為空,則使用默認的鏡像。mmfImages 未來會支持多個鏡像。
通過 MMF_* 環境變量傳入各種參數.
## mmf
示例:examples\functions\golang\manual-simple
從環境變量 "MMF_PROFILE_ID" 解析出 profileID, 并向 redis 查詢(HGETALL) profile,HSET 類型。
從 profile 中取 pools 字段,即匹配條件。
pools 分為多個 pool, 每個 pool 中有多個 filter, 每個 filter 向 redis 取符合的 Player.
profile 用到以下字段:
* "properties.playerPool"
json串,是一些過濾條件,如“mmr: 100-999”
* "properties.roster"
json串, 是多個隊伍大小,如 “red: 4”
示例見:`examples\backendclient\profiles\testprofile.json`
### 簡單匹配過程
simple mmf 的匹配過程如下:
1. 從 redis 查詢 profile,獲取過濾條件和各隊伍大小
1. 每個過濾條件向 redis 查詢,所有結果的交集為可選成員
1. 去除 ignoreList, 即最近 800s 內已匹配成功的成員,即 proposal 和 deindexed ZSET 列表。
1. 如果可選成員個數太小,則 insufficient_players 并退出
1. 分配各個隊伍成員
1. 向 redis 記錄結果
### 結果
profile 中添加 roster,即各陣營成員名單,存入 prososalKey.
保存不分隊伍的成員名單。
然后向 "proposalq" 添加 prososalKey
### 細節
poolRosters 以 (pool名, filter attribute) 為鍵,值為 Player ID 列表.
保存從 redis 查詢的符合條件的 Player ID.
overlaps 以 pool 名為鍵,保存符合該pool中所有filter的 Player ID 列表,去除 ignore list.
rosters 是 profile 中的 "properties.rosters" 字段。不知何用?
遍歷 rosters, 為每個陣營的每個player找到對應pool的PlayerID, 保存到 mo.Rosters.
其中 profileRosters 好像沒用。