Repository navigation
feat: send match.find_or_create, the race-free route into a live match - #48
Merged
Merged
Conversation
The match twin of world.find_or_create (widgrensit/asobi#482). The matchmaker groups co-queued tickets and spawns; it never joins a player into a running match, so the only route into a live one was match.list then match.join, which races. This resolves server-side and is serialized, so simultaneous callers converge on one match. It replies with match.joined, the frame match.join already answers with, so the SDK's existing reply routing covers it and no new event is needed. The payload is mode only. Named MatchFindOrCreateAsync to sit beside MatchListAsync and mirror WorldFindOrCreateAsync.
`not_found` is the refusal a caller hits first, and the doc omitted it: `asobi_match_lobby:eligible/2` returns it when `mode_config/1` hands back an empty map, so a misspelt or unregistered mode lands there. The list now reads "refusals include", since more can be added. The old rationale was false for this SDK. Neither `MatchListAsync` nor `JoinMatchAsync` creates a match, so two clients reading the same empty listing do not race to create one, they both find nothing. What is true here: no client call spawns a match at all, and the matchmaker only spawns once it has grouped enough co-queued tickets, so before this frame an empty listing left the caller with nothing but the queue. The refusals were also unreadable. A WS error frame faults the task with `AsobiException(-1, raw)`, so `StatusCode` carries nothing and the reason is only inside the raw envelope, at `payload.reason`. The doc now says how to read it with the SDK's own public scanner, and a test pins that recipe next to `payload.error.code`, which is `ws.request_failed` for every refusal here bar `join_rate_limited` and so cannot tell them apart. `mode` is not the only field the frame takes either: the handler parses an optional join context out of the same payload. This SDK exposes no join context on `match.join`, so none is exposed here and none is documented, but the param doc no longer claims `mode` is all the frame accepts.
Two corrections from verification, both checked against asobi source. The doc claimed payload.error.code is ws.request_failed for every refusal bar join_rate_limited. False: join_refused, match_full and match_locked all carry their own mapped codes (asobi_error.erl:290-292). The reason string is still the right thing to branch on, but for the opposite reason to the one stated - not because the code is always generic, but because it is the only field that separates every case. And the read recipe threw away the one refusal string a game actually authors. match.find_or_create can refuse in two stages: the lobby before the join, and the join itself after a match is found. When a game's own Lua join script refuses, reason is the fixed literal join_refused and the script's wording is at payload.error.details.refused_reason - a script cannot mint an error code, so its text travels as a detail. That is the string to show a player, and the doc never mentioned the field existed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
MatchFindOrCreateAsync(string mode)toAsobiRealtime, the send side ofasobi's new
match.find_or_createinbound frame (widgrensit/asobi#482).What it does
The matchmaker groups co-queued tickets and spawns; it never joins a player into
a running match.
MatchListAsyncthenJoinMatchAsynccan only join a matchthat already exists, and no client call spawns one, so an empty listing left the
caller with nothing but the queue.
match.find_or_createfinds a live match orspawns one, resolved server-side and serialized, so simultaneous callers
converge on one match.
Earlier revisions of this PR sold it as closing a browse-then-join race, with
two clients reading the same empty listing each creating a match. That is the
race at the protocol level for a client that can create; it is not why a Unity
developer should reach for this call, because neither
MatchListAsyncnorJoinMatchAsynccreates anything.Refusals
not_found,quick_play_disabled,wrong_mode_type,match_capacity_reachedand
join_rate_limited.not_foundis the one a caller hits first, sinceasobi_match_lobby:eligible/2returns it for a mode that is unknown or notconfigured; the doc calls the list open rather than exhaustive.
A refusal faults the task with
AsobiException(-1, raw), soStatusCodeishardcoded and carries nothing, and
payload.error.codeisws.request_failedfor all of these bar
join_rate_limited. The reason is only atpayload.reason, so the doc names it and shows the read with the SDK's ownpublic scanner rather than naming reasons nothing can branch on.
Why nothing else changed
The reply is
match.joined, the framematch.joinalready answers with, not anew
match.createdleaf. The dispatch case andOnMatchJoinedalready exist andare pinned by
DispatcherTests(match.joined->OnMatchJoined), so the onlygap was a way to send the frame.
Naming
MatchFindOrCreateAsync, notFindOrCreateMatchAsync: it sits besideMatchListAsync, the most recent match-frame addition, and mirrorsWorldFindOrCreateAsyncname-for-name. Placed betweenMatchListAsyncandJoinMatchAsyncso the match block reads list / find_or_create / join, as theworld block does.
Docs
The XML doc on the method, which is where
MatchListAsyncandWorldInputAsynccarry theirs. No README section: the README documents
WorldFindOrCreateAsynconly inside "Worlds (client-side prediction)", and a match frame does not belong
there.
The
modeparam doc no longer saysmodeis the only field the frame takes:the handler also parses an optional join context out of the same payload. This
SDK exposes no join context on
match.join, so none is exposed or documentedhere either, which keeps the two frames symmetric.
Note for the reviewer: version floor
The doc comment states v0.86.0, not v0.85.0.
match.find_or_createlanded inasobi commit
8f7d950(#482), which is taggedv0.86.0;v0.85.0is97f4cbb(#481) and does not carry the frame. Worth checking the other six SDK PRs say the
same.
Checks
dotnet testonTests/AsobiCore.NET: 129 passed, 0 failed.AsobiRealtime.cscompiled against aUnityEngineshim (it is not linked intothe license-free test project); builds clean, and every new
see crefresolves.AWsRefusalReasonIsReadFromThePayloadNotFromTheErrorCode, whichpins the refusal-reading recipe the doc hands the caller. The send itself is
still untested: the reply routing is already covered, and the only Unity-free
seam,
WsFrame.Request, is covered byWsFrameTests.