feat(Steamworks): add auto-configuration for Steamworks integration and update README

This commit is contained in:
extio-de
2026-07-05 16:32:26 +02:00
parent 47c6a86134
commit 35892b4898
6 changed files with 138 additions and 23 deletions

View File

@@ -1107,13 +1107,25 @@ No additional configuration required.
#### Purpose and Overview #### Purpose and Overview
Provides integration with the Steam platform API through a connector abstraction. Supports Steam features including user identity, Steam overlay, lobbies, achievements, and rich presence. The connector is designed as a singleton and can be optionally initialized when running on Steam. Provides integration with the Steam platform API through a connector abstraction. Supports Steam features including user identity, Steam overlay, lobbies, achievements, and rich presence. The connector is designed as a singleton and can be optionally initialized when running on Steam.
**Note**: This is an optional component that requires the Steamworks4J library and proper Steam initialization. **Note**: This is an optional component that requires the Steamworks4J library and proper Steam initialization. It is **disabled by default** and must be explicitly enabled via configuration property.
#### Setup / Autoconfiguration #### Setup / Autoconfiguration
No auto-configuration provided. Must be manually initialized by setting `SteamworksConnector.setInstance()` with a concrete implementation (e.g., `SteamworksConnectorImpl`). **Configuration Class**: `SteamworksAutoConfiguration`
**Property**: `game-engine.steamworks.enabled` (default: `false`)
The auto-configuration is guarded by two conditions:
- `game-engine.steamworks.enabled=true` must be set
- The `com.codedisaster.steamworks.SteamAPI` class must be present on the classpath (`@ConditionalOnClass`)
When enabled, `SteamworksConnectorImpl` is instantiated, registered as the singleton via `SteamworksConnector.setInstance()`, and a `SteamworksRunner` thread (implementing `SmartLifecycle`) is started to drive the Steam callback loop. The runner has a phase of `Integer.MAX_VALUE` (starts last, stops first) and will gracefully shut down the Steam API on context close.
#### Exposed Spring Beans #### Exposed Spring Beans
None (managed as a singleton via static accessor) - **`SteamworksConnector`** (via `SteamworksConnectorImpl`): The Steam API connector singleton
- **`SteamworksRunner`**: Lifecycle-managed callback runner thread
#### Integration Notes
If a custom `SteamworksConnector` bean is provided, the auto-configuration will not create `SteamworksConnectorImpl` but will still create the `SteamworksRunner` to drive callbacks. Both beans use `@ConditionalOnMissingBean`.
--- ---

View File

@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.extio</groupId> <groupId>de.extio</groupId>
<artifactId>game-engine</artifactId> <artifactId>game-engine</artifactId>
<version>1.0.24</version> <version>1.0.25</version>
<properties> <properties>
<java.version>25</java.version> <java.version>25</java.version>

View File

@@ -0,0 +1,28 @@
package de.extio.game_engine.steamworks;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnProperty(name = "game-engine.steamworks.enabled", havingValue = "true")
@ConditionalOnClass(name = "com.codedisaster.steamworks.SteamAPI")
public class SteamworksAutoConfiguration {
@Bean
@ConditionalOnMissingBean(SteamworksConnector.class)
SteamworksConnectorImpl steamworksConnector() {
final var connector = new SteamworksConnectorImpl();
SteamworksConnector.setInstance(connector);
return connector;
}
@Bean
@ConditionalOnMissingBean
SteamworksRunner steamworksRunner(final SteamworksConnector connector) {
return new SteamworksRunner(connector);
}
}

View File

@@ -1,12 +1,8 @@
package de.extio.game_engine.steamworks; package de.extio.game_engine.steamworks;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -17,7 +13,6 @@ import com.codedisaster.steamworks.SteamAuth.AuthSessionResponse;
import com.codedisaster.steamworks.SteamAuthTicket; import com.codedisaster.steamworks.SteamAuthTicket;
import com.codedisaster.steamworks.SteamFriends; import com.codedisaster.steamworks.SteamFriends;
import com.codedisaster.steamworks.SteamFriends.OverlayDialog; import com.codedisaster.steamworks.SteamFriends.OverlayDialog;
import com.codedisaster.steamworks.SteamFriends.OverlayToWebPageMode;
import com.codedisaster.steamworks.SteamFriends.PersonaChange; import com.codedisaster.steamworks.SteamFriends.PersonaChange;
import com.codedisaster.steamworks.SteamFriendsCallback; import com.codedisaster.steamworks.SteamFriendsCallback;
import com.codedisaster.steamworks.SteamID; import com.codedisaster.steamworks.SteamID;
@@ -29,18 +24,9 @@ import com.codedisaster.steamworks.SteamMatchmaking.ChatMemberStateChange;
import com.codedisaster.steamworks.SteamMatchmaking.ChatRoomEnterResponse; import com.codedisaster.steamworks.SteamMatchmaking.ChatRoomEnterResponse;
import com.codedisaster.steamworks.SteamMatchmaking.LobbyType; import com.codedisaster.steamworks.SteamMatchmaking.LobbyType;
import com.codedisaster.steamworks.SteamMatchmakingCallback; import com.codedisaster.steamworks.SteamMatchmakingCallback;
import com.codedisaster.steamworks.SteamNativeHandle;
import com.codedisaster.steamworks.SteamPublishedFileID; import com.codedisaster.steamworks.SteamPublishedFileID;
import com.codedisaster.steamworks.SteamRemoteStorage;
import com.codedisaster.steamworks.SteamRemoteStorage.PublishedFileVisibility;
import com.codedisaster.steamworks.SteamResult; import com.codedisaster.steamworks.SteamResult;
import com.codedisaster.steamworks.SteamUGC; import com.codedisaster.steamworks.SteamUGC;
import com.codedisaster.steamworks.SteamUGC.ItemInstallInfo;
import com.codedisaster.steamworks.SteamUGC.ItemUpdateInfo;
import com.codedisaster.steamworks.SteamUGC.ItemUpdateStatus;
import com.codedisaster.steamworks.SteamUGCCallback;
import com.codedisaster.steamworks.SteamUGCDetails;
import com.codedisaster.steamworks.SteamUGCQuery;
import com.codedisaster.steamworks.SteamUGCUpdateHandle; import com.codedisaster.steamworks.SteamUGCUpdateHandle;
import com.codedisaster.steamworks.SteamUser; import com.codedisaster.steamworks.SteamUser;
import com.codedisaster.steamworks.SteamUserCallback; import com.codedisaster.steamworks.SteamUserCallback;
@@ -115,11 +101,6 @@ public final class SteamworksConnectorImpl extends SteamworksConnector {
@Override @Override
public void runCallbacks() { public void runCallbacks() {
// TODO: Executor thread
if (!this.active) { if (!this.active) {
return; return;
} }

View File

@@ -0,0 +1,93 @@
package de.extio.game_engine.steamworks;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.SmartLifecycle;
public final class SteamworksRunner implements SmartLifecycle {
private static final Logger LOGGER = LogManager.getLogger();
private static final int SLEEP_MS = 33;
private final SteamworksConnector connector;
private final AtomicBoolean running = new AtomicBoolean();
private Thread thread;
public SteamworksRunner(final SteamworksConnector connector) {
this.connector = connector;
}
@Override
public void start() {
if (this.running.compareAndSet(false, true)) {
LOGGER.debug(() -> "SteamworksRunner starting");
this.thread = new Thread(this::run, "SteamworksRunner");
this.thread.setDaemon(true);
this.thread.start();
}
}
@Override
public void stop() {
if (this.running.compareAndSet(true, false)) {
LOGGER.debug(() -> "SteamworksRunner stopping");
final Thread t = this.thread;
if (t != null) {
t.interrupt();
try {
t.join(3000);
}
catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(final Runnable callback) {
this.stop();
callback.run();
}
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
private void run() {
try {
while (this.running.get() && this.connector.isActive()) {
this.connector.runCallbacks();
Thread.sleep(SLEEP_MS);
}
}
catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
catch (final Throwable t) {
LOGGER.warn("Error in SteamworksRunner", t);
}
finally {
this.connector.shutdown();
LOGGER.debug(() -> "SteamworksRunner stopped");
}
}
}

View File

@@ -8,3 +8,4 @@ de.extio.game_engine.i18n.LocalizationAutoConfiguration
de.extio.game_engine.resource.StaticResourceAutoConfiguration de.extio.game_engine.resource.StaticResourceAutoConfiguration
de.extio.game_engine.audio.AudioAutoConfiguration de.extio.game_engine.audio.AudioAutoConfiguration
de.extio.game_engine.menu.MenuAutoConfiguration de.extio.game_engine.menu.MenuAutoConfiguration
de.extio.game_engine.steamworks.SteamworksAutoConfiguration