Skip to content

Steam Leaderboard API Support#25

Open
corfe83 wants to merge 4 commits into
hajimehoshi:mainfrom
corfe83:leaderboards
Open

Steam Leaderboard API Support#25
corfe83 wants to merge 4 commits into
hajimehoshi:mainfrom
corfe83:leaderboards

Conversation

@corfe83

@corfe83 corfe83 commented Dec 4, 2025

Copy link
Copy Markdown
Contributor

Implement leaderboard support based on the new purego method.

@hajimehoshi

Copy link
Copy Markdown
Owner

Does this PR do multiple things at the same time, like introducing original callback handlings?

Comment thread leaderboards_packsmall.go Outdated
@corfe83

corfe83 commented Dec 4, 2025

Copy link
Copy Markdown
Contributor Author

Does this PR do multiple things at the same time, like introducing original callback handlings?

The callback handling is all code that is not exported. To check it in separately, it would be unused until the leaderboard support merges. I can split it into a separate PR if you prefer.

Comment thread leaderboards_packsmall.go Outdated

package steamworks

// 32-bit Windows uses VALVE_CALLBACK_PACK_SMALL (4-byte alignment)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this entire file, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update the comment to not mention windows. It is odd, but unix 32bit and 64bit both use this file. And windows 64bit uses the other file. This is to match behavior in the steam headers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This build tags are trying to match this behavior in steamclientpublic.h :

#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
// The 32-bit version of gcc has the alignment requirement for uint64 and double set to
// 4 meaning that even with #pragma pack(8) these types will only be four-byte aligned.
// The 64-bit version of gcc has the alignment requirement for these types set to
// 8 meaning that unless we use #pragma pack(4) our structures will get bigger.
// The 64-bit structure packing has to match the 32-bit structure packing for each platform.
#define VALVE_CALLBACK_PACK_SMALL
#else
#define VALVE_CALLBACK_PACK_LARGE
#endif

In my tests, both of these files are necessary to make the leaderboards work for both 64-bit windows and 64-bit linux and 64-bit MacOS.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not have the ability to test ARM64 MacOS though, my MacOS is an Intel 64-bit CPU.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the other comments and build tags to stop mentioning 32-bit.

@hajimehoshi

hajimehoshi commented Dec 5, 2025

Copy link
Copy Markdown
Owner
type leaderboardScoresDownloaded_t struct {
	// m_hSteamLeaderboard:        8 bytes
	// m_hSteamLeaderboardEntries: 8 bytes
	// m_cEntryCount:              4 bytes (8 bytes padded)
	// total: 24 bytes
	data [24]byte
}

You don't have do such tricky things: you can define Go struct as it is. See also e.g. https://github.com/hajimehoshi/ebiten/tree/main/internal/graphicsdriver/directx

Another points:

  • Do not forget to put license comments
  • Follow Go's comment convention rule

Thanks,

@hajimehoshi

hajimehoshi commented Dec 5, 2025

Copy link
Copy Markdown
Owner

The callback handling is all code that is not exported. To check it in separately, it would be unused until the leaderboard support merges. I can split it into a separate PR if you prefer.

Please explain why we need an original event handling. Thanks,

@corfe83

corfe83 commented Dec 7, 2025

Copy link
Copy Markdown
Contributor Author

The callback handling is all code that is not exported. To check it in separately, it would be unused until the leaderboard support merges. I can split it into a separate PR if you prefer.

Please explain why we need an original event handling. Thanks,

For callbacks in the Steam API, it requires tracking the SteamAPICall_t, and polling every frame until steam tells you a response came back. I feel this is overly complicated for the style of go, so this is a little syntactic sugar to provide a simpler interface for go game developers, they just pass in a function to be called when the response from steam comes back, and that's the purpose of callbacks.go.

If you feel this is too far away from the raw API to belong in go-steamworks, I can eliminate it.

@corfe83

corfe83 commented Dec 7, 2025

Copy link
Copy Markdown
Contributor Author

I've pushed an additional commit to stop exporting SteamAPICall_T, because it isn't part of the exportable interfaces at all because of callbacks.go.

But if you feel this doesn't belong, I can eliminate this, export SteamAPICall_T, and move back to the model where user tracks SteamAPICall_T and polls steam each frame for responses.

@hajimehoshi

Copy link
Copy Markdown
Owner

For callbacks in the Steam API, it requires tracking the SteamAPICall_t, and polling every frame until steam tells you a response came back. I feel this is overly complicated for the style of go, so this is a little syntactic sugar to provide a simpler interface for go game developers, they just pass in a function to be called when the response from steam comes back, and that's the purpose of callbacks.go.
If you feel this is too far away from the raw API to belong in go-steamworks, I can eliminate it.

I don't fully understand, but go-steamworks should reflect the original API as it is whenever possible. So, I prefer eliminating it if possible.

@corfe83

corfe83 commented Dec 7, 2025

Copy link
Copy Markdown
Contributor Author

For callbacks in the Steam API, it requires tracking the SteamAPICall_t, and polling every frame until steam tells you a response came back. I feel this is overly complicated for the style of go, so this is a little syntactic sugar to provide a simpler interface for go game developers, they just pass in a function to be called when the response from steam comes back, and that's the purpose of callbacks.go.
If you feel this is too far away from the raw API to belong in go-steamworks, I can eliminate it.

I don't fully understand, but go-steamworks should reflect the original API as it is whenever possible. So, I prefer eliminating it if possible.

Let me give an example to demonstrate how I think this is simpler. Of course, I can remove if this is too much.

With callbacks.go, downloading leaderboard entries looks like this (not including error checking code):

steamworks.SteamUserStats().FindLeaderboard("Test", func(handle steamworks.SteamLeaderboard_t, found bool, err error) {
	steamworks.SteamUserStats().DownloadLeaderboardEntries(handle, steamworks.ELeaderboardDataRequestGlobal, 0, 10, func(entries []steamworks.LeaderboardEntry, err error) {
		// process downloaded result
	})
})

Without it, it looks more like this:

type Game struct {
    findLeaderBoardResult     steamworks.SteamAPICall_T
    downloadLeaderBoardResult steamworks.SteamAPICall_T
}

func (me *Game) initializeCallOnce() {
	me.findLeaderboardResult = steamworks.SteamUserStats().FindLeaderboard("Test")
}

func (me *Game) everyFrameCheckResult() {
	if me.findLeaderboardResult != steamworks.InvalidResult {
		foundLeaderboard, completed, success := steamworks.SteamUtilsGetAPICallResult(me.findLeaderboardResult, steamworks.LeaderboardFindResult_k_iCallback)
		if success && completed {
			me.downloadLeaderBoardResult = steamworks.SteamUserStats().DownloadLeaderboardEntries(foundLeaderboard.SteamLeaderboard(), 0, 10)
		}
		me.findLeaderboardResult = steamworks.InvalidResult
	}

	if me.downloadLeaderBoardResult != steamworks.InvalidResult {
		downloadedLeaderboard, completed, success := steamworks.SteamUtilsGetAPICallResult(me.findLeaderboardResult, steamworks.LeaderboardScoresDownloaded_k_iCallback)
		if success && completed {
			// process or display downloaded result
		}
		me.downloadLeaderBoardResult = steamworks.InvalidResult
	}
}

Steamworks supports multiple leaderboards, so both of these examples will multiply in complexity if a game has, say, 5 leaderboards.

Let me know if I should remove callbacks.go and do a more raw approach to match steam flat API exactly.

@hajimehoshi

hajimehoshi commented Dec 7, 2025

Copy link
Copy Markdown
Owner

Thank you for elaborating. For the callback wrapper, we should have a separate package if needed. For the root package of this go-steamworks library, the API should be low level for consistency. Let's focus on the low level API in this PR, and revisit a wrapper API later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants