Jump to content

Trying to Figure Out Steam Lobby / C++ Templates / Callbacks


gamecreator
 Share

Recommended Posts

I've been trying to wrap my head around how Steam Lobby works by looking at the docs, forums and the Spacewar example that comes with Steam but it's beyond my skills. Was hoping someone could help.

 

To start, the Steam Matchmaking & Lobbies page says the following:

 

Creating a lobby

If you can't find an existing lobby for a user to join, this is when you'd typically create a lobby. Just call:

 

SteamAPICall_t CreateLobby( ELobbyType eLobbyType, int cMaxMembers );

 

and wait for it to complete. The call result whether or not it succeeded, and if it did returns the steamID of the lobby in a LobbyCreated_t struct, which can be used to set metadata on the lobby. The first thing you'll want to do after you create a lobby is set a bunch of data on the lobby, that other game clients can use to search for it (see below).

 

I then looked at the Steam Spacewar example and this is how it uses it:

 

// ask steam to create a lobby
SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby( k_ELobbyTypePublic /* public lobby, anyone can find it */, 4 );

// set the function to call when this completes
m_SteamCallResultLobbyCreated.Set( hSteamAPICall, this, &CSpaceWarClient::OnLobbyCreated );

 

I kind of understand that the function OnLobbyCreated gets called when the CreateLobby function eventually succeeds. But I had to look into what m_SteamCallResultLobbyCreated was. That resulted in these two lines in SpaceWarClient.h

 

// callback for when we're creating a new lobby
void OnLobbyCreated( LobbyCreated_t *pCallback, bool bIOFailure );
CCallResult<CSpaceWarClient, LobbyCreated_t> m_SteamCallResultLobbyCreated;

 

That CCallResult and whatever's going on after it completely lost me. Pretty sure that's a template but that's over my head. And what it has to do with CSpaceWarClient, which itself is a giant class, is past me. But for kicks, here's CCallResult in the steam_api.h header.

 

//-----------------------------------------------------------------------------
// Purpose: maps a steam async call result to a class member function
//   template params: T = local class, P = parameter struct
//-----------------------------------------------------------------------------
template< class T, class P >
class CCallResult : private CCallbackBase
{
public:
typedef void (T::*func_t)( P*, bool );
CCallResult()
{
 m_hAPICall = k_uAPICallInvalid;
 m_pObj = NULL;
 m_Func = NULL;
 m_iCallback = P::k_iCallback;
}
void Set( SteamAPICall_t hAPICall, T *p, func_t func )
{
 if ( m_hAPICall )
  SteamAPI_UnregisterCallResult( this, m_hAPICall );
 m_hAPICall = hAPICall;
 m_pObj = p;
 m_Func = func;
 if ( hAPICall )
  SteamAPI_RegisterCallResult( this, hAPICall );
}
bool IsActive() const
{
 return ( m_hAPICall != k_uAPICallInvalid );
}
void Cancel()
{
 if ( m_hAPICall != k_uAPICallInvalid )
 {
  SteamAPI_UnregisterCallResult( this, m_hAPICall );
  m_hAPICall = k_uAPICallInvalid;
 }

}
~CCallResult()
{
 Cancel();
}
void SetGameserverFlag() { m_nCallbackFlags |= k_ECallbackFlagsGameServer; }
private:
virtual void Run( void *pvParam )
{
 m_hAPICall = k_uAPICallInvalid; // caller unregisters for us
 (m_pObj->*m_Func)( (P *)pvParam, false ); 
}
void Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall )
{
 if ( hSteamAPICall == m_hAPICall )
 {
  m_hAPICall = k_uAPICallInvalid; // caller unregisters for us
  (m_pObj->*m_Func)( (P *)pvParam, bIOFailure );  
 }
}
int GetCallbackSizeBytes()
{
 return sizeof( P );
}
SteamAPICall_t m_hAPICall;
T *m_pObj;
func_t m_Func;
};

 

 

Not sure if that's enough info. I'm still searching the Steam forums for help too. Thanks for looking.

Link to comment
Share on other sites

And from here:

https://partner.steamgames.com/documentation/getting_started

 

Call Results

When appropriate, some Steamworks methods use call results instead of a callback to asynchronously return results from a function call. The primary difference between a call result and a callback is that callbacks are broadcast to all listeners, where call results target one listener. Like callbacks, your game will need to call SteamAPI_RunCallbacks() to dispatch call results to their listener.

The following example shows how to use a CCallResult to map the asynchronous result of finding a leaderboard to a member function of a class named CLeaderboard:

// In your class definition

class CLeaderboard

{

...

 

void OnFindLeaderboard( LeaderboardFindResult_t *pResult, bool bIOFailure );

CCallResult<CLeaderboards, LeaderboardFindResult_t> m_callResultFindLeaderboard;

}

 

// Make the request to create the leaderboard

void CLeaderboard::FindLeaderboard()

{

SteamAPICall_t hSteamAPICall = SteamUserStats()->FindLeaderboard( "Best Score" );

m_callResultFindLeaderboard.Set( hSteamAPICall, this, &CLeaderboard::OnFindLeaderboard );

}

 

// Called when SteamUserStats()->FindLeaderboard() returns asynchronously

void CLeaderboard::OnFindLeaderboard( LeaderboardFindResult_t *pResult, bool bIOFailure )

{

// see if we encountered an error during the call

if ( !pResult->m_bLeaderboardFound || bIOFailure )

{

// show error

return;

}

 

SteamLeaderboard_t hLeaderboard = pResult->m_hSteamLeaderboard;

// use handle

}

 

Link to comment
Share on other sites

Purpose: maps a steam async call result to a class member function

 

 

So with callbacks you can either have a pointer to a normal C function (which is easy but not object oriented) or you can make a pointer to a C++ class method. That's what this is doing. It allows you to define one of your C++ class methods to be a callback that the Steam API calls when something happens.

 

I use this all the time but I call mine Event<>. When you are coding in an object oriented way you don't want to mess around with straight C functions for callbacks. It screws up the flow of an object oriented program. Leadwerks actually does this with it's collision methods in C++ and to allow to not screw up the flow it allows you to pass a void pointer as an extra parameter so we can pass around class objects (this is generally actually looked at as bad practice though).

 

 

CCallResult<CLeaderboards, LeaderboardFindResult_t> m_callResultFindLeaderboard;

 

Think of this as defining an event. The event will call a function inside the CLeaderboards class and that function will have 1 parameter of type LeaderboardFinalResult_t.

 

 

 

SteamAPICall_t hSteamAPICall = SteamUserStats()->FindLeaderboard( "Best Score" );

m_callResultFindLeaderboard.Set( hSteamAPICall, this, &CLeaderboard::OnFindLeaderboard );

 

This is where you are linking your event to your class and what function will be called on Steam. So when the FindLeaderboard() function from Steam gets called it'll in turn call your event function in your class. You are giving 'this' which is a pointer to your class, and defining what function in your class it'll call.

  • Upvote 1
Link to comment
Share on other sites

[edit]

Had this typed out and sitting before you posted so just going to finish it up :)

 

 

IMHO something like this would be great if LE would do it. Valve took it a step farther with also sending what function you want to call from their API which wouldn't be needed and confuses things I think. I don't know why they require the class name to be passed in the template. It's not needed and makes it more confusing if you ask me, but LE falling back to C callbacks in my eyes is just a lack of understanding how to do C++ method callbacks.

 

Instead of having a C style Collision callback which breaks the flow of OOP, it would be better if the Entity class had an event named OnCollide that we can subscribe to with our C++ classes. All callbacks should work like this if you ask me.

 

class Player
{
private:
  Entity* player;

  void player_OnCollide(CollisionArgs e)
  {
  }
public:
  void Player()
  {
     player->OnCollide.Bind(this, &player_Collide);
  }
};

 

 

That seems pretty easy to follow doesn't it? This is the same thing steam is doing but they put defining the event itself on your shoulders too and for some reason require you to pass in the classname.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...