feat(Steamworks): add auto-configuration for Steamworks integration and update README
This commit is contained in:
18
README.md
18
README.md
@@ -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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user