-
Notifications
You must be signed in to change notification settings - Fork 500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SteamMatchmaking - creating, retrieving, listing, joining and leaving lobbies. #704
Conversation
I'm actually using this in conjunction with a almost complete implementation of Steam's P2P (UDP) protocol that I've developed. Combining this PR, with the rest of SteamKit and my Steam P2P implementation is enough to get dedicated servers working for games that use Steam P2P exclusively for networking. Which is precisely why I implemented this and is confirmed as working for Tabletop Simulator. I will of course open source the P2P stuff at some point, however my gut feel is that it may need to be a stand-alone project that depends on SteamKit2, rather than part of the core. The reason for this is that unlike the rest of SteamKit2, it's specifically designed to be integrated into existing games' run-loops, which means there's a strong focus on performance and avoiding garbage collection. Its API is a bit hairy as a result. EDIT: Oh, and related to above, in case anyone is interested. I've also back-ported SteamKit2 to .NET 3.5 (https://github.com/Benjamin-Dobell/SteamKit/tree/net35). It's a few months behind upstream master, but surprisingly usable. |
The scope of this seems OK, as additional lobby functionality can be further built on top. I don't see anything unusual after a quick review, but I do want to double check the usage and rationale of lobby.Clone() in those callback handlers (for the thread safety of lobby.Members mutations). |
Would it make sense to have Members be an immutable list of some sort? It shouldn't be necessary to clone Lobby, as reference assignment should be atomic. It would also prevent accidental mutation of the Members list by any other code. |
Excellent catch on such a quick review! I actually just changed it to be "thread-safe by default". In the past the That approach seemed a little to error prone, so the objects returned in handlers are now immutable and won't ever be mutated. Instead of mutating lobbies we first Granted, given
|
It is actually essentially immutable as currently implemented. I think I should just delete the Clone method and explicitly create a new Would that be desirable? |
writer.Write( ( byte )0 ); | ||
} | ||
|
||
; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, I'll fix this when I address any other feedback.
Ah, I see what you mean about the interface being IReadOnlyList, but the implementation itself isn't an immutable collection. I'm suspicious about the clone - mutate pattern. I don't think it's necessary or desirable to clone or create a new Lobby every time. I think the implementation should simply create a truly immutable Members list, and assign a new immutable list to Members every time. That way a reference to a Lobby will always be correct, and iterators over Members are never invalidated (because they would always be a reference to the old or new immutable collection) |
Although in this case, lobbies are never mutated in place, so perhaps it may be an unnecessary optimization, and just creating a new Lobby may be appropriate. |
Ideally I'd rather not mutate any objects returned to users. Is there a precedence for this? The issue would be with users somewhat naively writing: // ..lobby is some Lobby retrieved a while back and now being used on another thread.
int count = lobby.Members.Count;
for ( int i = 0; i < count; i++ )
{
var member = lobby.Members[ i ]; // BOOM!
// We replaced `Members` with a smaller list mid-iteration because a user left the lobby.
} I think returning purely immutable objects is safest. However, I definitely think making I could instead have something like this in private void UpdateLobbyMembers(uint appId, Lobby lobby, IReadOnlyList<Lobby.Member> members)
{
var updatedLobby = new Lobby(
lobby.SteamID,
lobby.lobbyType,
// ..etc
members,
lobby.distance,
/// ...etc
);
var appLobbies = lobbies.GetOrAdd( appId, k => new ConcurrentDictionary<SteamID, Lobby>() );
appLobbies[ lobby.SteamID ] = updatedLobby;
} Then call that from Then I can kill this entire concept of P.S. Off to bed now, will pick this up tomorrow 😄 Thanks for your time thus far! |
I see the case in that example where they didn't hold an iterator. I think we agree that the Lobby should be wholly immutable, so it's just a matter of details at this point. |
/// <summary> | ||
/// This callback is fired in response to <see cref="SetLobbyData"/>. | ||
/// </summary> | ||
public class SetLobbyDataCallback : CallbackMsg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: This and all other callbacks that follow should be sealed
.
To clarify one last point, I think this issue also exists in the implementation as-is. Wouldn't an existing reference to Members have this same issue of mutation by the handler, because I don't think the IReadOnlyList interface provides any guarantees, as the underlying implementation is a mutable List? Edit: My mistake, Clone is actually creating a new list. That's what I get for not looking. |
I don't know C# very well, but it does not seem to work as intended 🤔 internal List<Member> members;
internal Dictionary<string, string> metadata;
internal Lobby( ... Dictionary<string, string> metadata, List<Member> members, ... )
{
...
metadata = metadata ?? new Dictionary<string, string>();
members = members ?? new List<Member>();
...
} |
Haha, yeah no knowledge of C# required to notice that's wrong! That's what I get for last second refactoring. Thanks, will fix it shortly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not particularly comfortable with the approach taken to thread safety here. It seems like it's trying to do the right thing, but it has internal race conditions doesn't quite enforce immutability.
I'd much rather use System.Collections.Immutable
(and perhaps in other places too) to keep things immutable and thread-safe where we can, without a [noticeable] performance impact.
/// <returns>true, if obj is <see cref="Member"/> with a matching SteamID. Otherwise, false.</returns> | ||
public override bool Equals( object obj ) | ||
{ | ||
if ( obj is Member ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use obj is Member member
to avoid a second type-check below (when casting).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, sorry, this was running with my .NET 3.5 fork of SteamKit (https://github.com/Benjamin-Dobell/SteamKit/tree/net35) with an old Mono compiler not supporting the syntax. Will make the change 👍
EDIT: Granted, this is a bit similar to my dislike of out var
, however there's a valid technical reason for this syntax, so I can handle it. Well, also it's not nearly as bad as out var
, constructs followed by a block frequently declare variables that are limited to the scope of the trailing block (for
loop etc.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory you should be able to use this syntax and still target .NET 3.5... in practice setting up the SDKs like that can be a pain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're spot on.
I have managed to bump the language level a couple of times with my tool-chain. I can probably pull this off now and drastically clean up my (very hacky) backport.
My original issue was that I needed MDBs (Mono debug symbols), which requires using xbuild
instead of msbuild
. However, xbuild
can't go above Language Level 6(?).
I ended up getting fed-up with the archaic tooling, swapped to msbuild
and threw together a trivial Cecil-based CLI tool to convert the symbols from PDB to MDB (https://github.com/Benjamin-Dobell/pdbtomdb) - pdb2mdb
doesn't handle the latest "portable" PDB file format. Ah, the joys of the ever changing .NET tooling ecosystem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<DebugType>full</DebugType>
should get you older PDBs if you need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, forgot to mention, I'm (generally) on macOS. Older PDBs are Windows only.
{ | ||
SteamID = steamId; | ||
PersonaName = personaName; | ||
Metadata = metadata ?? new Dictionary<string, string>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a single empty Dictionary
instead of one per Member
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably should use AsReadOnly
, otherwise consumers can cast the object reference back to it's mutable implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a single empty Dictionary instead of one per
Member
?
Can do. Just wasn't sure what the precedent for that was withing this code-base. Very happy to make the change as I do similar in my own code-bases.
We probably should use
AsReadOnly
, otherwise consumers can cast the object reference back to it's mutable implementation.
I'm not typically a fan of doing of stuff like this. If the user is determined to violate our public contract they can always use reflection etc. It's a bit of fruitless battle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just realised I'd missed these two pieces of feedback. Addressing both points now.
/// <returns>A new <see cref="Lobby"/> instance that is equivalent to this one.</returns> | ||
public Lobby Clone() | ||
{ | ||
return new Lobby( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about object.MemberwiseClone()
?
new Dictionary<string, string>( metadata ), | ||
MaxMembers, | ||
NumMembers, | ||
new List<Member>( members ), // We're not doing a deep copy as Member is never mutated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see where we mutate either the dictionary or the list, so why create new ones?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We mutate the list on line 342, granted I've refactored locally. It's purely immutable now.
{ | ||
using ( var writer = new BinaryWriter( ms ) ) | ||
{ | ||
byte[] header = { 0, 0 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider making both of these static+readonly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Good idea. Will do.
void HandleCreateLobbyResponse( IPacketMsg packetMsg ) | ||
{ | ||
var lobbyListResponse = new ClientMsgProtobuf<CMsgClientMMSCreateLobbyResponse>( packetMsg ); | ||
CMsgClientMMSCreateLobbyResponse body = lobbyListResponse.Body; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inconsistent use of var
in these functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I've actually already fixed this locally. Actually, I've fundamentally refactored based on some earlier reviews. Will push shortly.
|
||
lobby = lobby.Clone(); // Cloning to avoid mutating the original Lobby, as it was previously returned in a callback (not thread-safe). | ||
lobby.members.Add( member ); | ||
appLobbies[ lobbyId ] = lobby; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition here between lines 323 and now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, aren't the handlers always called on the one thread (Client thread)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not totally certain there's a potential race here. I think most HandleMsg based implementations expect to run in a single thread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are, but I'd need to look closer at what the thread safety concerns are here and how they're being addressed, since we have comments like // ... (not thread-safe)
.
var userId = new SteamID( body.steam_id_user ); | ||
|
||
var appLobbies = lobbies.GetOrAdd( body.app_id, k => new ConcurrentDictionary<SteamID, Lobby>() ); | ||
var lobby = appLobbies.ContainsKey( lobbyId ) ? appLobbies[ lobbyId ] : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does two dictionary lookups. Better to use TryGetValue
for only one lookup.
var userId = new SteamID( body.steam_id_user ); | ||
|
||
var appLobbies = lobbies.GetOrAdd( body.app_id, k => new ConcurrentDictionary<SteamID, Lobby>() ); | ||
var lobby = appLobbies.ContainsKey( lobbyId ) ? appLobbies[ lobbyId ] : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TryGetValue
as above
|
||
lobby = lobby.Clone(); // Cloning to avoid mutating the original Lobby, as it was previously returned in a callback (not thread-safe). | ||
lobby.members.RemoveAt( memberIndex ); | ||
appLobbies[ lobbyId ] = lobby; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we maybe return clones in the callback rather than having this racy clone-and-replace dance internally every time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've refactored this based on earlier feedback. There is no longer a concept of clone. This logic has moved into a LobbyCache
in a vaguely similar fashion to the FriendCache
. Will push soon.
Heads up to anyone following this, given the effort I'm going to refactor and test I figured I may as well just add the remaining requests/callbacks. As such, I'll soon be pushing a complete implementation with the exception of:
|
Oh I didn't realize you were also working on this, good job so far. Not sure if I'm actually helping here or not, just wanted to pitch in as it seems you've been making more progress that I did. |
@Jvs34 Thanks, those filter snippets are indeed very helpful. I only had captures of the 3 filters I've implemented. Will happily add those ones in. At the moment I'm just trying to sort out chat, which has proved painful to capture because it has a bazillion weird variants (https://partner.steamgames.com/doc/api/steam_api#EChatEntryType). Tempted to forego it for now. Will see. |
36eba69
to
8eb6b97
Compare
Codecov Report
@@ Coverage Diff @@
## master #704 +/- ##
==========================================
- Coverage 24.37% 22.65% -1.72%
==========================================
Files 89 93 +4
Lines 8800 9560 +760
Branches 742 794 +52
==========================================
+ Hits 2145 2166 +21
- Misses 6522 7262 +740
+ Partials 133 132 -1
Continue to review full report at Codecov.
|
Not ready to merge Still a WIP, mostly regarding chat messages, which I'm admittedly a bit confused about at present. However, still a couple of house keeping things to do as well e.g. I've force pushed here so I can get some feedback on the direction. Admittedly, that's going to be a pain to diff, so here's the actual change log: https://github.com/Benjamin-Dobell/SteamKit/commits/matchmaking2 |
P.S. This is totally untested at present. I'm still working on the aforementioned integration tests. I've pushed purely so I can confirm I'm at least taking this in the right direction design-wise. Granted, the changes are technically less efficient (more |
8eb6b97
to
68eb5a9
Compare
This is now ready for review and subsequent merging. Integration testsIntegration test are available at: https://github.com/Benjamin-Dobell/SteamKitIntegrationTests README has instructions. They can be run easily as a Docker container or built and run in an IDE. However, to specifically run the tests against this PR you'd do:
Note that you must provide credentials for two Steam accounts. These can be free (limited) accounts (lobbies are created against DOTA2 which works because it is also free). What's still not included in this PR
I basically just don't have a decent way to capture additional filters or chat (nor am I particularly interested in the functionality), however they should be reasonably easy to add on. Merging/ReviewChanges have been squashed to facilitate merging and a clean history. However, change-sets since previous reviews are available at: https://github.com/Benjamin-Dobell/SteamKit/commits/matchmaking2 |
@yaakov-h Yeah, I felt a bit weird about namespaces/scoping as well e.g. using partial classes for non-generated code seemed a bit unnecessary. I was trying to follow established patterns, however this is certainly one of the more involved handlers. So I think I've stretched the current code layout pattern to its limits. I'd have absolutely zero issue with someone coming along and reorganising. |
Thanks a bunch! If you still want to implement the remaining few items mentioned in the OP, please open a new PR. If you don't, that's perfectly fine as well. 😄 |
Hello, I am working on a project that requires checking if a lobby with a given Steam ID exists, and if so retrieve its data. I believe this requires the RequestLobbyData() function and LobbyDataUpdate_t callback to check for success. Is it possible for these to be implemented? |
@Tyaap Everything you need is already implemented. It's a simple, fairly self explanatory API, as such please refer to the changes in this PR to find the appropriate methods and callbacks. |
Hello Benjamin, I am aware that lobbies may be checked for existence by calling GetLobbyList and searching the returned list. However there is a problem when the lobby is full: GetLobbyList never returns these lobbies. I have tried adding a filter to request lobbies with >= 0 slots but this does not help. So it seems I can't check if a full lobby exists using this approach. Also I can't use GetLobbyData since this only works for lobbies already retrieved via GetLobbyList. The approach mentioned previously should work, since the Steamworks documentation says that RequestLobbyData does not require a prior call to GetLobbyList. However this function does not appear to be present in SteamKit. Is there something I'm still not aware of that can resolve this problem? |
Steamworks documentation applies to Steamworks, not SteamKit. SteamKit is a C# library with C# semantics. What you want to do is already possible, as previously suggested, please refer to the changes made in this PR. |
I have read the code in SteamMatchmaking, which seems to suggest using GetLobbyData. However I have tried using the implementation of GetLobbyData in SteamKit (without a prior call of GetLobbyList) and the client is immediately disconnected from Steam. |
@Tyaap How did you retrieve the steam ID for this lobby? Steam will only let you retrieve data for lobbies you should know about. That means those publicly available in the lobby list, or a friend's lobby. Steam's backend won't allow you request the data for an arbitrary lobby. By the way, there is no such API as "RequestLobbyData". Steamworks's Steamworks Now, technically, even if your friend is in a lobby, you can only know this information if you've requested their There is actually some related functionality that is missing from SteamKit. Specifically, PersonaStateCallback does not expose the lobby ID. If you'd like to implement that, it'd be two line (+ 4 lines of documentation) change. Still though, this is only ever going to work for lobbies that your friends are in; or users which Steam's backend has for some reason allowed you to request the persona state of. EDIT: By the way, seems as you've been referring to Steamwork's documentation for
|
Thanks for the information. I am retrieving the lobby list on a regular basis using GetLobbyList, and storing the details. The goal is to keep track of all active lobbies. Sometimes on retrieving a new list a lobby will disappear. It will either disappear because:
For reference, this is the relevant code I'm working on: |
So after reviewing your message, it appears futile to try work around the "disappearance" of full lobbies from the list retrieved from GetLobbyList. The Steamworks documentation states that hiding full lobbies is the "default" behaviour, but gives no indication that this default behaviour can be changed. Are you aware of any part of your Matchmaking implementation which may allow me to retrieve information about full lobbies? |
I have tried this, but no luck: https://github.com/Tyaap/SuperLobbyBot/blob/d207fb2f852b95c40e822bb5d1da10db4a8f9199/Steam.cs#L171 |
Update: I have tried using the native Steamworks API (via Steamworks.NET) and have successfully kept track of full lobbies using the RequestLobbyData() function. However the behaviour is somewhat unexpected: the requested lobby data is not retrieved until I call RetrieveLobbyList(). This may be caused by Steamworks.NET but I can't tell. I have created a proof of concept project: https://github.com/Tyaap/LobbyTrackingPOC/blob/master/Program.cs |
@Tyaap Just to clarify, you've confirmed the data returned is up-to-date i.e. it's not just serving you cached data (before the lobby was full)? |
@Tyaap Assuming you're not receiving stale data, then what I suspect is happening is that Steam is just sending the client a regular lobby update for any lobbies that are no longer in the latest lobby list. Your call to Have you subscribed for regular lobby data updates ( |
I can confirm it is up to date, because the player count is reported as e.g. 4/4. It must have been less than this previously, otherwise it wouldn't have shown up in a prior retrieval of the lobby list. This data is not retrieved unless I call RequestLobbyData(). In SteamKit, I have tried subscribing to LobbyDataCallback. This is only getting triggered if I run GetLobbyData() on a lobby that was retrieved from GetLobbyList(). The test I have done is: run GetLobbyList() and periodically execute GetLobbyData() on each of the returned lobbies. Eventually this resulted in a Steam disconnection, which I believe is because the lobby disappears. I am unable to confirm this though, or determine whether its due to a lobby that truly no longer exists or one that is just full. I've made a test project for that and will upload it shortly. |
I was just thinking that the idea of Steam disconnecting you simply for requesting data for a lobby you'd previously been told about (but is now full) leading to a disconnection seemed a bit dubious. Because that'd be susceptible to all sorts of race conditions and would likely break standard Steam clients... This does not appear to be the case. I don't know why Steam is disconnecting you, however here's a very simple test that demonstrates you can query data for full lobbies you've previously seen in a lobby list, but are not being returned in the latest lobby list. To run the tests, checkout https://github.com/Benjamin-Dobell/SteamKitIntegrationTests/tree/full-lobby-data (Note:
or Then either run/debug the singular test from your IDE of choice, or from command line with:
|
Thank you very much for the test code. :) Unfortunately I can't test it (without further work) as I'm getting AccountLogonDenied due to my Steam Guard. I've had a look and so far haven't found anything within the code snippet that is majorly different to what I have been trying to do, which is very odd. I will have to investigate some more. |
I suspect the disconnects are caused by attempting to get data for a lobby that truly no longer exists. If this is the case, then there is still an issue of being able to differentiate between these two cases in order to avoid being disconnected. |
@Tyaap You're being disconnected because you're requesting a deleted lobby, not a full one. Just verified this happens. However, Steam does send you updates automatically (without you explicitly requesting) for lobbies that you've previously received data for but have since been deleted. Now, that still seems a bit off to me, because there's potential for a race condition in that you could go to request the data for a lobby, but it's just deleted. I'm thinking the only way the real Steam client could get around this is that Steam's backend will allow you to make this request, until your client has confirmed receiving the packet that indicates the lobby has been deleted. Then when processing its queue Steam would see a queued outbound message pertains to a deleted resource (lobby), delete the message, and return the data from the lobby cache. That's actually insanely convoluted, so I'd love to hear I'm wrong and there's a simpler way. If Steam are doing the above, then we have an huge problem on our hands, as our send and receive logic operate in different threads. Everything is event driven, we really can't process everything serially like that without enormous changes to the architecture of SteamKit 😕 I'd suggest you play around and ensure you're seeing what I am, if you are, you're going to need to open an issue... but ah, if it really is the above, I wouldn't expect it to be fixed anytime soon. EDIT: Posts crossed. |
Okay I am getting the same results as you for the case of a full lobby and the case of a deleted lobby. :( |
I will open an issue about this. For now I have thought of a workaround although its not pullet proof. I will just assume that any lobbies that disappear with less than e.g. MaxMembers/2 was a deleted lobby, and a lobby with more than that is a full lobby. I will be updating the lobby list every 10 seconds and the lobbies always have a max of 10 players in the game I'm working on, so hopefully the likelihood of these assumptions being incorrect is negligible. |
Do you have code that I can refer to in the new issue, which can quickly reproduce it? |
Sure: Benjamin-Dobell/SteamKitIntegrationTests@bd8fd31 The failing code is commented out, the code that's there is actually guaranteed to work in this circumstance. Although far from ideal, it's probably worth keeping in mind that the likelihood requesting lobby data just as a lobby is deleted and before Rather than doing the weirdness you suggested above with guessing about lobby states, it'd probably be easier just to throw your Bot up behind foreman and restart it if it crashes. Could split the Steam logic out into a separate process/container, so if the Steam querying component goes down, your Bot doesn't go down itself. Well, unless you're worried about being banned by Valve. I imagine making bad requests does raise some eyebrows - even if they are infrequent. |
Okay thanks for the help. I have just read through your code. I was not aware that after retrieving a lobby list we get LobbyDataCallbacks from Steam to update the lobby data, including one that changes the steam ID of the lobby owner to 0 to indicate the destruction of a lobby. I will have to change the issue description again... |
Unfortunately, it seems that it's not simply retrieving the lobby list that sets you up for lobby updates. If it was, you'd actually have a 100% crash-free solution on offer, with the ever so slight possibility of mislabelling a deleted lobby as full for a few milliseconds i.e. Never call However, it would seem you only receive deletion updates if you've called |
An implementation of SteamMatchmaking.
Currently supported
CreateLobby
(Sending ofCMsgClientMMSCreateLobby
)CreateLobbyCallback
(Receiving ofCMsgClientMMSCreateLobbyResponse
)SetLobbyData
(Sending ofCMsgClientMMSSetLobbyData
)SetLobbyDataCallback
(Receiving ofCMsgClientMMSSetLobbyDataResponse
)SetLobbyOwner
(Sending ofCMsgClientMMSSetLobbyOwner
)SetLobbyOwnerCallback
(Receiving ofCMsgClientMMSSetLobbyOwnerResponse
)GetLobbyList
(Sending ofCMsgClientMMSGetLobbyList
) i.e. Searching for lobbies.LobbyListCallback
(Receiving ofCMsgClientMMSGetLobbyListResponse
)DistanceFilter
NearValueFilter
NumericalFilter
SlotsAvailableFilter
StringFilter
GetLobby
(Equivalent to functionality offered by https://partner.steamgames.com/doc/api/ISteamMatchmaking i.e. non-blocking synchronous access to lobbies we already "know about" .LobbyDataCallback
(Receiving ofCMsgClientMMSLobbyData
) i.e. Lobby change tracking.UserJoinedLobbyCallback
(Receiving ofCMsgClientMMSUserJoinedLobby
)UserLeftLobbyCallback
(Receiving ofCMsgClientMMSUserLeftLobby
).Since originally opening the PR, the following has been added:
What's not implemented in this PR
Related Issues
Closes #656
Closes #487 ... as the requested functionality is necessary to implement Matchmaking. Specifically, when we create a lobby we need to submit our public IP, so we require persistent access to it. Same is true of
IPCountryCode
.Ping #131 (only partially implemented, so not closing).