mirror of
https://github.com/2OOP/pism.git
synced 2026-02-05 03:14:50 +00:00
Merge remote-tracking branch 'origin/Development' into Reversi
# Conflicts: # app/src/main/java/org/toop/app/canvas/GameCanvas.java # app/src/main/java/org/toop/app/layer/layers/MainLayer.java # game/src/main/java/org/toop/game/Game.java # game/src/main/java/org/toop/game/othello/Othello.java # game/src/main/java/org/toop/game/othello/OthelloAI.java
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -48,6 +48,8 @@ shelf/
|
|||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
misc.xml
|
misc.xml
|
||||||
|
uiDesigner.xml
|
||||||
|
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
## Eclipse
|
## Eclipse
|
||||||
@@ -76,6 +78,8 @@ dist/
|
|||||||
nbdist/
|
nbdist/
|
||||||
nbactions.xml
|
nbactions.xml
|
||||||
nb-configuration.xml
|
nb-configuration.xml
|
||||||
|
misc.xml
|
||||||
|
compiler.xml
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
## Visual Studio Code
|
## Visual Studio Code
|
||||||
|
|||||||
22
.idea/compiler.xml
generated
22
.idea/compiler.xml
generated
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<annotationProcessing>
|
|
||||||
<profile name="Maven default annotation processors profile" enabled="true">
|
|
||||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
|
||||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
|
||||||
<outputRelativeToContentRoot value="true" />
|
|
||||||
<module name="pism_framework" />
|
|
||||||
<module name="pism_game" />
|
|
||||||
<module name="pism_app" />
|
|
||||||
</profile>
|
|
||||||
</annotationProcessing>
|
|
||||||
</component>
|
|
||||||
<component name="JavacSettings">
|
|
||||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
|
||||||
<module name="pism_app" options="" />
|
|
||||||
<module name="pism_framework" options="" />
|
|
||||||
<module name="pism_game" options="" />
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
2
.idea/dictionaries/project.xml
generated
2
.idea/dictionaries/project.xml
generated
@@ -8,11 +8,13 @@
|
|||||||
<w>flushnl</w>
|
<w>flushnl</w>
|
||||||
<w>gaaf</w>
|
<w>gaaf</w>
|
||||||
<w>gamelist</w>
|
<w>gamelist</w>
|
||||||
|
<w>pism</w>
|
||||||
<w>playerlist</w>
|
<w>playerlist</w>
|
||||||
<w>tictactoe</w>
|
<w>tictactoe</w>
|
||||||
<w>toop</w>
|
<w>toop</w>
|
||||||
<w>vmoptions</w>
|
<w>vmoptions</w>
|
||||||
<w>xplugin</w>
|
<w>xplugin</w>
|
||||||
|
<w>yourturn</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
</component>
|
</component>
|
||||||
4
.idea/encodings.xml
generated
4
.idea/encodings.xml
generated
@@ -1,12 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Encoding">
|
<component name="Encoding">
|
||||||
|
<file url="file://$APPLICATION_HOME_DIR$/bin/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$APPLICATION_HOME_DIR$/bin/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/app/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/app/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/app/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/app/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/framework/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/framework/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/framework/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/framework/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/game/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/game/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/game/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/game/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/processors/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/processors/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
3
.idea/inspectionProfiles/Project_Default.xml
generated
3
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -2,7 +2,8 @@
|
|||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,org.toop.framework.audio.AudioPlayer,play" />
|
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,org.toop.framework.audio.AudioPlayer,play,java.util.Map,remove" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="WriteOnlyObject" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
19
.idea/misc.xml
generated
19
.idea/misc.xml
generated
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="EntryPointsManager">
|
|
||||||
<list size="1">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="com.google.common.eventbus.Subscribe" />
|
|
||||||
</list>
|
|
||||||
</component>
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="MavenProjectsManager">
|
|
||||||
<option name="originalFiles">
|
|
||||||
<list>
|
|
||||||
<option value="$PROJECT_DIR$/pom.xml" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
85
app/pom.xml
85
app/pom.xml
@@ -1,8 +1,13 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
<groupId>org.toop</groupId>
|
<groupId>org.toop</groupId>
|
||||||
<artifactId>pism_app</artifactId>
|
<artifactId>pism</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>app</artifactId>
|
||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -24,24 +29,36 @@
|
|||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
<version>2.10.1</version>
|
<version>2.10.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.toop</groupId>
|
|
||||||
<artifactId>pism_framework</artifactId>
|
|
||||||
<version>0.1</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.toop</groupId>
|
|
||||||
<artifactId>pism_game</artifactId>
|
|
||||||
<version>0.1</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-controls</artifactId>
|
<artifactId>javafx-controls</artifactId>
|
||||||
<version>25</version>
|
<version>25</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_annotations</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.toop</groupId>
|
||||||
|
<artifactId>framework</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.toop</groupId>
|
||||||
|
<artifactId>game</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -115,10 +132,52 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.14.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<showWarnings>true</showWarnings>
|
||||||
|
<fork>true</fork>
|
||||||
|
<executable>${java.home}/bin/javac</executable>
|
||||||
<source>25</source>
|
<source>25</source>
|
||||||
<target>25</target>
|
<target>25</target>
|
||||||
|
<release>25</release>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
|
||||||
|
<arg>
|
||||||
|
-Xplugin:ErrorProne \
|
||||||
|
</arg>
|
||||||
|
<!-- TODO-->
|
||||||
|
<!-- -Xep:RestrictedApi:ERROR \-->
|
||||||
|
<!-- -XepOpt:RestrictedApi:annotation=org.toop.annotations.TestsOnly \-->
|
||||||
|
<!-- -XepOpt:RestrictedApi:allowlistRegex=(?s).*/src/test/java/.*|.*test\.java \-->
|
||||||
|
<!-- -XepOpt:RestrictedApi:message=This API is marked @TestsOnly and shouldn't be normally used.-->
|
||||||
|
<arg>-XDcompilePolicy=simple</arg>
|
||||||
|
<arg>--should-stop=ifError=FLOW</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package org.toop;
|
package org.toop;
|
||||||
|
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.framework.asset.ResourceLoader;
|
import org.toop.framework.audio.*;
|
||||||
import org.toop.framework.asset.ResourceManager;
|
|
||||||
import org.toop.framework.audio.SoundManager;
|
|
||||||
import org.toop.framework.networking.NetworkingClientManager;
|
import org.toop.framework.networking.NetworkingClientManager;
|
||||||
import org.toop.framework.networking.NetworkingInitializationException;
|
import org.toop.framework.networking.NetworkingInitializationException;
|
||||||
|
import org.toop.framework.resource.ResourceLoader;
|
||||||
|
import org.toop.framework.resource.ResourceManager;
|
||||||
|
import org.toop.framework.resource.ResourceMeta;
|
||||||
|
import org.toop.framework.resource.resources.MusicAsset;
|
||||||
|
import org.toop.framework.resource.resources.SoundEffectAsset;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class Main {
|
public final class Main {
|
||||||
public static void main(String[] args) {
|
static void main(String[] args) {
|
||||||
initSystems();
|
initSystems();
|
||||||
App.run(args);
|
App.run(args);
|
||||||
}
|
}
|
||||||
@@ -16,6 +22,21 @@ public final class Main {
|
|||||||
private static void initSystems() throws NetworkingInitializationException {
|
private static void initSystems() throws NetworkingInitializationException {
|
||||||
ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"));
|
ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"));
|
||||||
new Thread(NetworkingClientManager::new).start();
|
new Thread(NetworkingClientManager::new).start();
|
||||||
new Thread(SoundManager::new).start();
|
new Thread(() -> {
|
||||||
|
MusicManager<MusicAsset> musicManager = new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class));
|
||||||
|
SoundEffectManager<SoundEffectAsset> soundEffectManager = new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class));
|
||||||
|
AudioVolumeManager audioVolumeManager = new AudioVolumeManager()
|
||||||
|
.registerManager(VolumeControl.MASTERVOLUME, musicManager)
|
||||||
|
.registerManager(VolumeControl.MASTERVOLUME, soundEffectManager)
|
||||||
|
.registerManager(VolumeControl.FX, soundEffectManager)
|
||||||
|
.registerManager(VolumeControl.MUSIC, musicManager);
|
||||||
|
|
||||||
|
new AudioEventListener<>(
|
||||||
|
musicManager,
|
||||||
|
soundEffectManager,
|
||||||
|
audioVolumeManager
|
||||||
|
).initListeners("medium-button-click.wav");
|
||||||
|
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
package org.toop.app;
|
package org.toop.app;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import java.util.Stack;
|
||||||
import org.toop.app.layer.Layer;
|
|
||||||
import org.toop.app.layer.layers.MainLayer;
|
|
||||||
import org.toop.app.layer.layers.QuitPopup;
|
|
||||||
import org.toop.framework.asset.ResourceManager;
|
|
||||||
import org.toop.framework.asset.resources.CssAsset;
|
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
|
||||||
import org.toop.local.AppContext;
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.toop.app.layer.Layer;
|
||||||
|
import org.toop.app.layer.layers.MainLayer;
|
||||||
|
import org.toop.app.layer.layers.QuitPopup;
|
||||||
|
import org.toop.framework.audio.VolumeControl;
|
||||||
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.resource.ResourceManager;
|
||||||
|
import org.toop.framework.resource.resources.CssAsset;
|
||||||
|
import org.toop.local.AppContext;
|
||||||
import org.toop.local.AppSettings;
|
import org.toop.local.AppSettings;
|
||||||
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
public final class App extends Application {
|
public final class App extends Application {
|
||||||
private static Stage stage;
|
private static Stage stage;
|
||||||
private static Scene scene;
|
private static Scene scene;
|
||||||
@@ -42,7 +41,8 @@ public final class App extends Application {
|
|||||||
stage.setWidth(1080);
|
stage.setWidth(1080);
|
||||||
stage.setHeight(720);
|
stage.setHeight(720);
|
||||||
|
|
||||||
stage.setOnCloseRequest(event -> {
|
stage.setOnCloseRequest(
|
||||||
|
event -> {
|
||||||
event.consume();
|
event.consume();
|
||||||
|
|
||||||
if (!isQuitting) {
|
if (!isQuitting) {
|
||||||
@@ -66,29 +66,33 @@ public final class App extends Application {
|
|||||||
|
|
||||||
App.isQuitting = false;
|
App.isQuitting = false;
|
||||||
|
|
||||||
|
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).postEvent();
|
||||||
|
|
||||||
final AppSettings settings = new AppSettings();
|
final AppSettings settings = new AppSettings();
|
||||||
settings.applySettings();
|
settings.applySettings();
|
||||||
|
|
||||||
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
|
|
||||||
activate(new MainLayer());
|
activate(new MainLayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void activate(Layer layer) {
|
public static void activate(Layer layer) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(
|
||||||
|
() -> {
|
||||||
popAll();
|
popAll();
|
||||||
push(layer);
|
push(layer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void push(Layer layer) {
|
public static void push(Layer layer) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(
|
||||||
|
() -> {
|
||||||
root.getChildren().addLast(layer.getLayer());
|
root.getChildren().addLast(layer.getLayer());
|
||||||
stack.push(layer);
|
stack.push(layer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void pop() {
|
public static void pop() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(
|
||||||
|
() -> {
|
||||||
root.getChildren().removeLast();
|
root.getChildren().removeLast();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
|
||||||
@@ -97,14 +101,15 @@ public final class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void popAll() {
|
public static void popAll() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(
|
||||||
|
() -> {
|
||||||
final int childrenCount = root.getChildren().size();
|
final int childrenCount = root.getChildren().size();
|
||||||
|
|
||||||
for (int i = 0; i < childrenCount; i++) {
|
for (int i = 0; i < childrenCount; i++) {
|
||||||
try {
|
try {
|
||||||
root.getChildren().removeLast();
|
root.getChildren().removeLast();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
IO.println(e);
|
IO.println(e); // TODO: Use logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,13 +118,15 @@ public final class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void quitPopup() {
|
public static void quitPopup() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(
|
||||||
|
() -> {
|
||||||
push(new QuitPopup());
|
push(new QuitPopup());
|
||||||
isQuitting = true;
|
isQuitting = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void quit() {
|
public static void quit() {
|
||||||
|
new EventFlow().addPostEvent(new AudioEvents.StopAudioManager()).postEvent();
|
||||||
stage.close();
|
stage.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package org.toop.app;
|
package org.toop.app;
|
||||||
|
|
||||||
public record GameInformation(String[] playerName, boolean[] isPlayerHuman,
|
public record GameInformation(
|
||||||
int[] computerDifficulty, int[] computerThinkTime,
|
String[] playerName,
|
||||||
boolean isConnectionLocal, String serverIP, String serverPort) {
|
boolean[] isPlayerHuman,
|
||||||
}
|
int[] computerDifficulty,
|
||||||
|
int[] computerThinkTime,
|
||||||
|
boolean isConnectionLocal,
|
||||||
|
String serverIP,
|
||||||
|
String serverPort) {}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
package org.toop.app.canvas;
|
package org.toop.app.canvas;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public abstract class GameCanvas {
|
public abstract class GameCanvas {
|
||||||
protected record Cell(float x, float y, float width, float height) {
|
protected record Cell(float x, float y, float width, float height) {}
|
||||||
}
|
|
||||||
|
|
||||||
protected final Canvas canvas;
|
protected final Canvas canvas;
|
||||||
protected final GraphicsContext graphics;
|
protected final GraphicsContext graphics;
|
||||||
@@ -27,7 +25,15 @@ public abstract class GameCanvas {
|
|||||||
|
|
||||||
protected final Cell[] cells;
|
protected final Cell[] cells;
|
||||||
|
|
||||||
protected GameCanvas(Color color, int width, int height, int rows, int columns, int gapSize, boolean edges, Consumer<Integer> onCellClicked) {
|
protected GameCanvas(
|
||||||
|
Color color,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rows,
|
||||||
|
int columns,
|
||||||
|
int gapSize,
|
||||||
|
boolean edges,
|
||||||
|
Consumer<Integer> onCellClicked) {
|
||||||
canvas = new Canvas(width, height);
|
canvas = new Canvas(width, height);
|
||||||
graphics = canvas.getGraphicsContext2D();
|
graphics = canvas.getGraphicsContext2D();
|
||||||
|
|
||||||
@@ -56,7 +62,8 @@ public abstract class GameCanvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.setOnMouseClicked(event -> {
|
canvas.setOnMouseClicked(
|
||||||
|
event -> {
|
||||||
if (event.getButton() != MouseButton.PRIMARY) {
|
if (event.getButton() != MouseButton.PRIMARY) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -106,6 +113,17 @@ public abstract class GameCanvas {
|
|||||||
graphics.fillOval(x, y, width, height);
|
graphics.fillOval(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void draw(Color color, int cell) {
|
||||||
|
final float x = cells[cell].x() + gapSize;
|
||||||
|
final float y = cells[cell].y() + gapSize;
|
||||||
|
|
||||||
|
final float width = cells[cell].width() - gapSize * 2;
|
||||||
|
final float height = cells[cell].height() - gapSize * 2;
|
||||||
|
|
||||||
|
graphics.setFill(color);
|
||||||
|
graphics.fillRect(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
public void resize(int width, int height) {
|
public void resize(int width, int height) {
|
||||||
canvas.setWidth(width);
|
canvas.setWidth(width);
|
||||||
canvas.setHeight(height);
|
canvas.setHeight(height);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package org.toop.app.canvas;
|
package org.toop.app.canvas;
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
public class TicTacToeCanvas extends GameCanvas {
|
public class TicTacToeCanvas extends GameCanvas {
|
||||||
public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
|
public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ public abstract class Container {
|
|||||||
public abstract Region getContainer();
|
public abstract Region getContainer();
|
||||||
|
|
||||||
public abstract void addNodes(Node... nodes);
|
public abstract void addNodes(Node... nodes);
|
||||||
|
|
||||||
public abstract void addContainer(Container container, boolean fill);
|
public abstract void addContainer(Container container, boolean fill);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package org.toop.app.layer;
|
package org.toop.app.layer;
|
||||||
|
|
||||||
import org.toop.app.App;
|
|
||||||
import org.toop.app.canvas.GameCanvas;
|
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.app.canvas.GameCanvas;
|
||||||
|
|
||||||
public abstract class Layer {
|
public abstract class Layer {
|
||||||
protected StackPane layer;
|
protected StackPane layer;
|
||||||
@@ -21,7 +20,13 @@ public abstract class Layer {
|
|||||||
layer.getChildren().addLast(background);
|
layer.getChildren().addLast(background);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addContainer(Container container, Pos position, int xOffset, int yOffset, int widthPercent, int heightPercent) {
|
protected void addContainer(
|
||||||
|
Container container,
|
||||||
|
Pos position,
|
||||||
|
int xOffset,
|
||||||
|
int yOffset,
|
||||||
|
int widthPercent,
|
||||||
|
int heightPercent) {
|
||||||
StackPane.setAlignment(container.getContainer(), position);
|
StackPane.setAlignment(container.getContainer(), position);
|
||||||
|
|
||||||
final double widthUnit = App.getWidth() / 100.0;
|
final double widthUnit = App.getWidth() / 100.0;
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
package org.toop.app.layer;
|
package org.toop.app.layer;
|
||||||
|
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
import java.util.function.Consumer;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.geometry.Orientation;
|
import javafx.geometry.Orientation;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
import java.util.function.Consumer;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
|
||||||
public final class NodeBuilder {
|
public final class NodeBuilder {
|
||||||
public static void addCss(Node node, String... cssClasses) {
|
public static void addCss(Node node, String... cssClasses) {
|
||||||
@@ -40,7 +38,8 @@ public final class NodeBuilder {
|
|||||||
final Label element = new Label(x);
|
final Label element = new Label(x);
|
||||||
setCss(element, "button", "text-normal");
|
setCss(element, "button", "text-normal");
|
||||||
|
|
||||||
element.setOnMouseClicked(_ -> {
|
element.setOnMouseClicked(
|
||||||
|
_ -> {
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||||
runnable.run();
|
runnable.run();
|
||||||
});
|
});
|
||||||
@@ -54,7 +53,8 @@ public final class NodeBuilder {
|
|||||||
|
|
||||||
final BooleanProperty checked = new SimpleBooleanProperty(toggled);
|
final BooleanProperty checked = new SimpleBooleanProperty(toggled);
|
||||||
|
|
||||||
element.setOnMouseClicked(_ -> {
|
element.setOnMouseClicked(
|
||||||
|
_ -> {
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||||
checked.set(!checked.get());
|
checked.set(!checked.get());
|
||||||
|
|
||||||
@@ -81,11 +81,14 @@ public final class NodeBuilder {
|
|||||||
element.setSnapToTicks(true);
|
element.setSnapToTicks(true);
|
||||||
element.setShowTickLabels(true);
|
element.setShowTickLabels(true);
|
||||||
|
|
||||||
element.setOnMouseClicked(_ -> {
|
element.setOnMouseClicked(
|
||||||
|
_ -> {
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||||
});
|
});
|
||||||
|
|
||||||
element.valueProperty().addListener((_, _, newValue) -> {
|
element.valueProperty()
|
||||||
|
.addListener(
|
||||||
|
(_, _, newValue) -> {
|
||||||
consumer.accept(newValue.intValue());
|
consumer.accept(newValue.intValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,11 +99,14 @@ public final class NodeBuilder {
|
|||||||
final TextField element = new TextField(x);
|
final TextField element = new TextField(x);
|
||||||
setCss(element, "input", "text-normal");
|
setCss(element, "input", "text-normal");
|
||||||
|
|
||||||
element.setOnMouseClicked(_ -> {
|
element.setOnMouseClicked(
|
||||||
|
_ -> {
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||||
});
|
});
|
||||||
|
|
||||||
element.textProperty().addListener((_, _, newValue) -> {
|
element.textProperty()
|
||||||
|
.addListener(
|
||||||
|
(_, _, newValue) -> {
|
||||||
consumer.accept(newValue);
|
consumer.accept(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,11 +117,14 @@ public final class NodeBuilder {
|
|||||||
final ChoiceBox<T> element = new ChoiceBox<>();
|
final ChoiceBox<T> element = new ChoiceBox<>();
|
||||||
setCss(element, "choice-box", "text-normal");
|
setCss(element, "choice-box", "text-normal");
|
||||||
|
|
||||||
element.setOnMouseClicked(_ -> {
|
element.setOnMouseClicked(
|
||||||
|
_ -> {
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||||
});
|
});
|
||||||
|
|
||||||
element.valueProperty().addListener((_, _, newValue) -> {
|
element.valueProperty()
|
||||||
|
.addListener(
|
||||||
|
(_, _, newValue) -> {
|
||||||
consumer.accept(newValue);
|
consumer.accept(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ public abstract class Popup extends Layer {
|
|||||||
super(backgroundStyles);
|
super(backgroundStyles);
|
||||||
|
|
||||||
if (popOnBackground) {
|
if (popOnBackground) {
|
||||||
background.setOnMouseClicked(_ -> {
|
background.setOnMouseClicked(
|
||||||
|
_ -> {
|
||||||
App.pop();
|
App.pop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.toop.app.layer.containers;
|
package org.toop.app.layer.containers;
|
||||||
|
|
||||||
import org.toop.app.layer.Container;
|
|
||||||
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import org.toop.app.layer.Container;
|
||||||
|
|
||||||
public final class HorizontalContainer extends Container {
|
public final class HorizontalContainer extends Container {
|
||||||
private final HBox container;
|
private final HBox container;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.toop.app.layer.containers;
|
package org.toop.app.layer.containers;
|
||||||
|
|
||||||
import org.toop.app.layer.Container;
|
|
||||||
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.toop.app.layer.Container;
|
||||||
|
|
||||||
public final class VerticalContainer extends Container {
|
public final class VerticalContainer extends Container {
|
||||||
private final VBox container;
|
private final VBox container;
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package org.toop.app.layer.layers;
|
package org.toop.app.layer.layers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.GameInformation;
|
import org.toop.app.GameInformation;
|
||||||
import org.toop.app.layer.Container;
|
import org.toop.app.layer.Container;
|
||||||
@@ -12,18 +20,8 @@ import org.toop.app.layer.containers.VerticalContainer;
|
|||||||
import org.toop.app.layer.layers.game.TicTacToeLayer;
|
import org.toop.app.layer.layers.game.TicTacToeLayer;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
import org.toop.framework.networking.events.NetworkEvents;
|
import org.toop.framework.networking.events.NetworkEvents;
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ListView;
|
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
public final class ConnectedLayer extends Layer {
|
public final class ConnectedLayer extends Layer {
|
||||||
private static Timer pollTimer = new Timer();
|
private static Timer pollTimer = new Timer();
|
||||||
|
|
||||||
@@ -36,7 +34,12 @@ public final class ConnectedLayer extends Layer {
|
|||||||
private final long clientID;
|
private final long clientID;
|
||||||
private final int challengeID;
|
private final int challengeID;
|
||||||
|
|
||||||
public ChallengePopup(GameInformation information, String challenger, String game, long clientID, String challengeID) {
|
public ChallengePopup(
|
||||||
|
GameInformation information,
|
||||||
|
String challenger,
|
||||||
|
String game,
|
||||||
|
long clientID,
|
||||||
|
String challengeID) {
|
||||||
super(false, "bg-popup");
|
super(false, "bg-popup");
|
||||||
|
|
||||||
this.information = information;
|
this.information = information;
|
||||||
@@ -45,7 +48,8 @@ public final class ConnectedLayer extends Layer {
|
|||||||
this.game = game;
|
this.game = game;
|
||||||
|
|
||||||
this.clientID = clientID;
|
this.clientID = clientID;
|
||||||
this.challengeID = Integer.parseInt(challengeID.substring(18, challengeID.length() - 2));
|
this.challengeID =
|
||||||
|
Integer.parseInt(challengeID.substring(18, challengeID.length() - 2));
|
||||||
|
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
@@ -60,14 +64,24 @@ public final class ConnectedLayer extends Layer {
|
|||||||
final var gameText = NodeBuilder.text(AppContext.getString("gameIsText"));
|
final var gameText = NodeBuilder.text(AppContext.getString("gameIsText"));
|
||||||
final var gameNameText = NodeBuilder.text(game);
|
final var gameNameText = NodeBuilder.text(game);
|
||||||
|
|
||||||
final var acceptButton = NodeBuilder.button(AppContext.getString("accept"), () -> {
|
final var acceptButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("accept"),
|
||||||
|
() -> {
|
||||||
pollTimer.cancel();
|
pollTimer.cancel();
|
||||||
|
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientID, challengeID)).postEvent();
|
new EventFlow()
|
||||||
|
.addPostEvent(
|
||||||
|
new NetworkEvents.SendAcceptChallenge(
|
||||||
|
clientID, challengeID))
|
||||||
|
.postEvent();
|
||||||
App.activate(new TicTacToeLayer(information, clientID));
|
App.activate(new TicTacToeLayer(information, clientID));
|
||||||
});
|
});
|
||||||
|
|
||||||
final var denyButton = NodeBuilder.button(AppContext.getString("deny"), () -> {
|
final var denyButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("deny"),
|
||||||
|
() -> {
|
||||||
App.pop();
|
App.pop();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,17 +109,26 @@ public final class ConnectedLayer extends Layer {
|
|||||||
this.information = information;
|
this.information = information;
|
||||||
|
|
||||||
new EventFlow()
|
new EventFlow()
|
||||||
.addPostEvent(NetworkEvents.StartClient.class, information.serverIP(), Integer.parseInt(information.serverPort()))
|
.addPostEvent(
|
||||||
.onResponse(NetworkEvents.StartClientResponse.class, e -> {
|
NetworkEvents.StartClient.class,
|
||||||
|
information.serverIP(),
|
||||||
|
Integer.parseInt(information.serverPort()))
|
||||||
|
.onResponse(
|
||||||
|
NetworkEvents.StartClientResponse.class,
|
||||||
|
e -> {
|
||||||
clientId = e.clientId();
|
clientId = e.clientId();
|
||||||
user = information.playerName()[0].replaceAll("\\s+", "");
|
user = information.playerName()[0].replaceAll("\\s+", "");
|
||||||
|
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendLogin(this.clientId, this.user)).postEvent();
|
new EventFlow()
|
||||||
|
.addPostEvent(
|
||||||
|
new NetworkEvents.SendLogin(this.clientId, this.user))
|
||||||
|
.postEvent();
|
||||||
|
|
||||||
Thread popThread = new Thread(this::populatePlayerList);
|
Thread popThread = new Thread(this::populatePlayerList);
|
||||||
popThread.setDaemon(false);
|
popThread.setDaemon(false);
|
||||||
popThread.start();
|
popThread.start();
|
||||||
}).postEvent();
|
})
|
||||||
|
.postEvent();
|
||||||
|
|
||||||
new EventFlow().listen(this::handleReceivedChallenge);
|
new EventFlow().listen(this::handleReceivedChallenge);
|
||||||
|
|
||||||
@@ -113,10 +136,17 @@ public final class ConnectedLayer extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void populatePlayerList() {
|
private void populatePlayerList() {
|
||||||
EventFlow sendGetPlayerList = new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId));
|
EventFlow sendGetPlayerList =
|
||||||
new EventFlow().listen(NetworkEvents.PlayerlistResponse.class, e -> {
|
new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId));
|
||||||
|
new EventFlow()
|
||||||
|
.listen(
|
||||||
|
NetworkEvents.PlayerlistResponse.class,
|
||||||
|
e -> {
|
||||||
if (e.clientId() == this.clientId) {
|
if (e.clientId() == this.clientId) {
|
||||||
List<String> playerList = new java.util.ArrayList<>(List.of(e.playerlist())); // TODO: Garbage, but works
|
List<String> playerList =
|
||||||
|
new java.util.ArrayList<>(
|
||||||
|
List.of(e.playerlist())); // TODO: Garbage,
|
||||||
|
// but works
|
||||||
playerList.removeIf(name -> name.equalsIgnoreCase(user));
|
playerList.removeIf(name -> name.equalsIgnoreCase(user));
|
||||||
if (this.onlinePlayers != playerList) {
|
if (this.onlinePlayers != playerList) {
|
||||||
this.onlinePlayers.clear();
|
this.onlinePlayers.clear();
|
||||||
@@ -125,7 +155,8 @@ public final class ConnectedLayer extends Layer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
TimerTask task = new TimerTask() {
|
TimerTask task =
|
||||||
|
new TimerTask() {
|
||||||
public void run() {
|
public void run() {
|
||||||
sendGetPlayerList.postEvent();
|
sendGetPlayerList.postEvent();
|
||||||
Platform.runLater(() -> reload());
|
Platform.runLater(() -> reload());
|
||||||
@@ -139,16 +170,28 @@ public final class ConnectedLayer extends Layer {
|
|||||||
final AtomicInteger challengeId = new AtomicInteger(-1);
|
final AtomicInteger challengeId = new AtomicInteger(-1);
|
||||||
|
|
||||||
if (onlinePlayers.contains(oppUsername)) {
|
if (onlinePlayers.contains(oppUsername)) {
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType))
|
new EventFlow()
|
||||||
.listen(NetworkEvents.ChallengeResponse.class, e -> {
|
.addPostEvent(
|
||||||
challengeId.set(Integer.parseInt(e.challengeId().substring(18, e.challengeId().length() - 2)));
|
new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType))
|
||||||
|
.listen(
|
||||||
|
NetworkEvents.ChallengeResponse.class,
|
||||||
|
e -> {
|
||||||
|
challengeId.set(
|
||||||
|
Integer.parseInt(
|
||||||
|
e.challengeId()
|
||||||
|
.substring(
|
||||||
|
18, e.challengeId().length() - 2)));
|
||||||
})
|
})
|
||||||
.listen(NetworkEvents.GameMatchResponse.class, e -> {
|
.listen(
|
||||||
|
NetworkEvents.GameMatchResponse.class,
|
||||||
|
e -> {
|
||||||
if (e.clientId() == this.clientId) {
|
if (e.clientId() == this.clientId) {
|
||||||
pollTimer.cancel();
|
pollTimer.cancel();
|
||||||
App.activate(new TicTacToeLayer(information, this.clientId));
|
App.activate(new TicTacToeLayer(information, this.clientId));
|
||||||
}
|
}
|
||||||
}, false).postEvent();
|
},
|
||||||
|
false)
|
||||||
|
.postEvent();
|
||||||
// ^
|
// ^
|
||||||
// |
|
// |
|
||||||
// |
|
// |
|
||||||
@@ -157,7 +200,13 @@ public final class ConnectedLayer extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
|
private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
|
||||||
App.push(new ChallengePopup(information, response.challengerName(), response.gameType(), clientId, response.challengeId()));
|
App.push(
|
||||||
|
new ChallengePopup(
|
||||||
|
information,
|
||||||
|
response.challengerName(),
|
||||||
|
response.gameType(),
|
||||||
|
clientId,
|
||||||
|
response.challengeId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -168,7 +217,11 @@ public final class ConnectedLayer extends Layer {
|
|||||||
|
|
||||||
for (int i = 0; i < onlinePlayers.size(); i++) {
|
for (int i = 0; i < onlinePlayers.size(); i++) {
|
||||||
int finalI = i;
|
int finalI = i;
|
||||||
players.getItems().add(NodeBuilder.button(onlinePlayers.get(i), () -> {
|
players.getItems()
|
||||||
|
.add(
|
||||||
|
NodeBuilder.button(
|
||||||
|
onlinePlayers.get(i),
|
||||||
|
() -> {
|
||||||
String clickedPlayer = onlinePlayers.get(finalI);
|
String clickedPlayer = onlinePlayers.get(finalI);
|
||||||
sendChallenge(clickedPlayer, "tic-tac-toe");
|
sendChallenge(clickedPlayer, "tic-tac-toe");
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package org.toop.app.layer.layers;
|
package org.toop.app.layer.layers;
|
||||||
|
|
||||||
|
import javafx.animation.PauseTransition;
|
||||||
|
import javafx.animation.TranslateTransition;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.util.Duration;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.layer.Container;
|
import org.toop.app.layer.Container;
|
||||||
import org.toop.app.layer.NodeBuilder;
|
import org.toop.app.layer.NodeBuilder;
|
||||||
@@ -8,12 +13,6 @@ import org.toop.app.layer.containers.HorizontalContainer;
|
|||||||
import org.toop.app.layer.containers.VerticalContainer;
|
import org.toop.app.layer.containers.VerticalContainer;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import javafx.animation.PauseTransition;
|
|
||||||
import javafx.animation.TranslateTransition;
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.text.Text;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
|
|
||||||
public final class CreditsPopup extends Popup {
|
public final class CreditsPopup extends Popup {
|
||||||
private final int lineHeight = 100;
|
private final int lineHeight = 100;
|
||||||
|
|
||||||
@@ -57,11 +56,13 @@ public final class CreditsPopup extends Popup {
|
|||||||
private void playCredits(Container container, double sceneLength) {
|
private void playCredits(Container container, double sceneLength) {
|
||||||
container.getContainer().setTranslateY(-sceneLength);
|
container.getContainer().setTranslateY(-sceneLength);
|
||||||
|
|
||||||
final TranslateTransition scrollCredits = new TranslateTransition(Duration.seconds(20), container.getContainer());
|
final TranslateTransition scrollCredits =
|
||||||
|
new TranslateTransition(Duration.seconds(20), container.getContainer());
|
||||||
scrollCredits.setFromY(-sceneLength - lineHeight);
|
scrollCredits.setFromY(-sceneLength - lineHeight);
|
||||||
scrollCredits.setToY(sceneLength + lineHeight);
|
scrollCredits.setToY(sceneLength + lineHeight);
|
||||||
|
|
||||||
scrollCredits.setOnFinished(_ -> {
|
scrollCredits.setOnFinished(
|
||||||
|
_ -> {
|
||||||
final PauseTransition pauseCredits = new PauseTransition(Duration.seconds(3));
|
final PauseTransition pauseCredits = new PauseTransition(Duration.seconds(3));
|
||||||
pauseCredits.setOnFinished(_ -> playCredits(container, sceneLength));
|
pauseCredits.setOnFinished(_ -> playCredits(container, sceneLength));
|
||||||
pauseCredits.play();
|
pauseCredits.play();
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package org.toop.app.layer.layers;
|
package org.toop.app.layer.layers;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.layer.Container;
|
import org.toop.app.layer.Container;
|
||||||
import org.toop.app.layer.Layer;
|
import org.toop.app.layer.Layer;
|
||||||
import org.toop.app.layer.NodeBuilder;
|
import org.toop.app.layer.NodeBuilder;
|
||||||
import org.toop.app.layer.containers.VerticalContainer;
|
import org.toop.app.layer.containers.VerticalContainer;
|
||||||
|
import org.toop.framework.audio.VolumeControl;
|
||||||
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
import org.toop.app.layer.layers.game.ReversiLayer;
|
import org.toop.app.layer.layers.game.ReversiLayer;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
|
|
||||||
public final class MainLayer extends Layer {
|
public final class MainLayer extends Layer {
|
||||||
public MainLayer() {
|
public MainLayer() {
|
||||||
super("bg-primary");
|
super("bg-primary");
|
||||||
@@ -20,23 +22,38 @@ public final class MainLayer extends Layer {
|
|||||||
public void reload() {
|
public void reload() {
|
||||||
popAll();
|
popAll();
|
||||||
|
|
||||||
final var tictactoeButton = NodeBuilder.button(AppContext.getString("tictactoe"), () -> {
|
final var tictactoeButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("tictactoe"),
|
||||||
|
() -> {
|
||||||
App.activate(new MultiplayerLayer());
|
App.activate(new MultiplayerLayer());
|
||||||
});
|
});
|
||||||
|
|
||||||
final var othelloButton = NodeBuilder.button(AppContext.getString("othello"), () -> {
|
final var othelloButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("othello"),
|
||||||
|
() -> {
|
||||||
App.activate(new ReversiLayer());
|
App.activate(new ReversiLayer());
|
||||||
});
|
});
|
||||||
|
|
||||||
final var creditsButton = NodeBuilder.button(AppContext.getString("credits"), () -> {
|
final var creditsButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("credits"),
|
||||||
|
() -> {
|
||||||
App.push(new CreditsPopup());
|
App.push(new CreditsPopup());
|
||||||
});
|
});
|
||||||
|
|
||||||
final var optionsButton = NodeBuilder.button(AppContext.getString("options"), () -> {
|
final var optionsButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("options"),
|
||||||
|
() -> {
|
||||||
App.push(new OptionsPopup());
|
App.push(new OptionsPopup());
|
||||||
});
|
});
|
||||||
|
|
||||||
final var quitButton = NodeBuilder.button(AppContext.getString("quit"), () -> {
|
final var quitButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("quit"),
|
||||||
|
() -> {
|
||||||
App.quitPopup();
|
App.quitPopup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.toop.app.layer.layers;
|
package org.toop.app.layer.layers;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.GameInformation;
|
import org.toop.app.GameInformation;
|
||||||
import org.toop.app.layer.Container;
|
import org.toop.app.layer.Container;
|
||||||
@@ -10,10 +12,6 @@ import org.toop.app.layer.containers.VerticalContainer;
|
|||||||
import org.toop.app.layer.layers.game.TicTacToeLayer;
|
import org.toop.app.layer.layers.game.TicTacToeLayer;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
public final class MultiplayerLayer extends Layer {
|
public final class MultiplayerLayer extends Layer {
|
||||||
private boolean isConnectionLocal = true;
|
private boolean isConnectionLocal = true;
|
||||||
|
|
||||||
@@ -42,7 +40,12 @@ public final class MultiplayerLayer extends Layer {
|
|||||||
final Container player1Container = new VerticalContainer(20);
|
final Container player1Container = new VerticalContainer(20);
|
||||||
final Container player2Container = new VerticalContainer(20);
|
final Container player2Container = new VerticalContainer(20);
|
||||||
|
|
||||||
final var isPlayer1HumanToggle = NodeBuilder.toggle(AppContext.getString("human"), AppContext.getString("computer"), !isPlayer1Human, (computer) -> {
|
final var isPlayer1HumanToggle =
|
||||||
|
NodeBuilder.toggle(
|
||||||
|
AppContext.getString("human"),
|
||||||
|
AppContext.getString("computer"),
|
||||||
|
!isPlayer1Human,
|
||||||
|
(computer) -> {
|
||||||
isPlayer1Human = !computer;
|
isPlayer1Human = !computer;
|
||||||
reload();
|
reload();
|
||||||
});
|
});
|
||||||
@@ -51,7 +54,10 @@ public final class MultiplayerLayer extends Layer {
|
|||||||
|
|
||||||
if (isPlayer1Human) {
|
if (isPlayer1Human) {
|
||||||
final var playerNameText = NodeBuilder.text(AppContext.getString("playerName"));
|
final var playerNameText = NodeBuilder.text(AppContext.getString("playerName"));
|
||||||
final var playerNameInput = NodeBuilder.input(player1Name, (name) -> {
|
final var playerNameInput =
|
||||||
|
NodeBuilder.input(
|
||||||
|
player1Name,
|
||||||
|
(name) -> {
|
||||||
player1Name = name;
|
player1Name = name;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,22 +68,38 @@ public final class MultiplayerLayer extends Layer {
|
|||||||
final var computerNameText = NodeBuilder.text(player1Name);
|
final var computerNameText = NodeBuilder.text(player1Name);
|
||||||
final var computerNameSeparator = NodeBuilder.separator();
|
final var computerNameSeparator = NodeBuilder.separator();
|
||||||
|
|
||||||
final var computerDifficultyText = NodeBuilder.text(AppContext.getString("computerDifficulty"));
|
final var computerDifficultyText =
|
||||||
|
NodeBuilder.text(AppContext.getString("computerDifficulty"));
|
||||||
final var computerDifficultySeparator = NodeBuilder.separator();
|
final var computerDifficultySeparator = NodeBuilder.separator();
|
||||||
final var computerDifficultySlider = NodeBuilder.slider(10, computer1Difficulty, (difficulty) ->
|
final var computerDifficultySlider =
|
||||||
computer1Difficulty = difficulty);
|
NodeBuilder.slider(
|
||||||
|
10,
|
||||||
|
computer1Difficulty,
|
||||||
|
(difficulty) -> computer1Difficulty = difficulty);
|
||||||
|
|
||||||
final var computerThinkTimeText = NodeBuilder.text(AppContext.getString("computerThinkTime"));
|
final var computerThinkTimeText =
|
||||||
final var computerThinkTimeSlider = NodeBuilder.slider(5, computer1ThinkTime, (thinkTime) ->
|
NodeBuilder.text(AppContext.getString("computerThinkTime"));
|
||||||
computer1ThinkTime = thinkTime);
|
final var computerThinkTimeSlider =
|
||||||
|
NodeBuilder.slider(
|
||||||
|
5, computer1ThinkTime, (thinkTime) -> computer1ThinkTime = thinkTime);
|
||||||
|
|
||||||
player1Container.addNodes(computerNameText, computerNameSeparator,
|
player1Container.addNodes(
|
||||||
computerDifficultyText, computerDifficultySlider, computerDifficultySeparator,
|
computerNameText,
|
||||||
computerThinkTimeText, computerThinkTimeSlider);
|
computerNameSeparator,
|
||||||
|
computerDifficultyText,
|
||||||
|
computerDifficultySlider,
|
||||||
|
computerDifficultySeparator,
|
||||||
|
computerThinkTimeText,
|
||||||
|
computerThinkTimeSlider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isConnectionLocal) {
|
if (isConnectionLocal) {
|
||||||
final var isPlayer2HumanToggle = NodeBuilder.toggle(AppContext.getString("human"), AppContext.getString("computer"), !isPlayer2Human, (computer) -> {
|
final var isPlayer2HumanToggle =
|
||||||
|
NodeBuilder.toggle(
|
||||||
|
AppContext.getString("human"),
|
||||||
|
AppContext.getString("computer"),
|
||||||
|
!isPlayer2Human,
|
||||||
|
(computer) -> {
|
||||||
isPlayer2Human = !computer;
|
isPlayer2Human = !computer;
|
||||||
reload();
|
reload();
|
||||||
});
|
});
|
||||||
@@ -86,7 +108,10 @@ public final class MultiplayerLayer extends Layer {
|
|||||||
|
|
||||||
if (isPlayer2Human) {
|
if (isPlayer2Human) {
|
||||||
final var playerNameText = NodeBuilder.text(AppContext.getString("playerName"));
|
final var playerNameText = NodeBuilder.text(AppContext.getString("playerName"));
|
||||||
final var playerNameInput = NodeBuilder.input(player2Name, (name) -> {
|
final var playerNameInput =
|
||||||
|
NodeBuilder.input(
|
||||||
|
player2Name,
|
||||||
|
(name) -> {
|
||||||
player2Name = name;
|
player2Name = name;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,50 +122,87 @@ public final class MultiplayerLayer extends Layer {
|
|||||||
final var computerNameText = NodeBuilder.text(player2Name);
|
final var computerNameText = NodeBuilder.text(player2Name);
|
||||||
final var computerNameSeparator = NodeBuilder.separator();
|
final var computerNameSeparator = NodeBuilder.separator();
|
||||||
|
|
||||||
final var computerDifficultyText = NodeBuilder.text(AppContext.getString("computerDifficulty"));
|
final var computerDifficultyText =
|
||||||
|
NodeBuilder.text(AppContext.getString("computerDifficulty"));
|
||||||
final var computerDifficultySeparator = NodeBuilder.separator();
|
final var computerDifficultySeparator = NodeBuilder.separator();
|
||||||
final var computerDifficultySlider = NodeBuilder.slider(10, computer2Difficulty, (difficulty) ->
|
final var computerDifficultySlider =
|
||||||
computer2Difficulty = difficulty);
|
NodeBuilder.slider(
|
||||||
|
10,
|
||||||
|
computer2Difficulty,
|
||||||
|
(difficulty) -> computer2Difficulty = difficulty);
|
||||||
|
|
||||||
final var computerThinkTimeText = NodeBuilder.text(AppContext.getString("computerThinkTime"));
|
final var computerThinkTimeText =
|
||||||
final var computerThinkTimeSlider = NodeBuilder.slider(5, computer2ThinkTime, (thinkTime) ->
|
NodeBuilder.text(AppContext.getString("computerThinkTime"));
|
||||||
computer2ThinkTime = thinkTime);
|
final var computerThinkTimeSlider =
|
||||||
|
NodeBuilder.slider(
|
||||||
|
5,
|
||||||
|
computer2ThinkTime,
|
||||||
|
(thinkTime) -> computer2ThinkTime = thinkTime);
|
||||||
|
|
||||||
player2Container.addNodes(computerNameText, computerNameSeparator,
|
player2Container.addNodes(
|
||||||
computerDifficultyText, computerDifficultySlider, computerDifficultySeparator,
|
computerNameText,
|
||||||
computerThinkTimeText, computerThinkTimeSlider);
|
computerNameSeparator,
|
||||||
|
computerDifficultyText,
|
||||||
|
computerDifficultySlider,
|
||||||
|
computerDifficultySeparator,
|
||||||
|
computerThinkTimeText,
|
||||||
|
computerThinkTimeSlider);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final var serverIPText = NodeBuilder.text(AppContext.getString("serverIP"));
|
final var serverIPText = NodeBuilder.text(AppContext.getString("serverIP"));
|
||||||
final var serverIPSeparator = NodeBuilder.separator();
|
final var serverIPSeparator = NodeBuilder.separator();
|
||||||
final var serverIPInput = NodeBuilder.input(serverIP, (ip) -> {
|
final var serverIPInput =
|
||||||
|
NodeBuilder.input(
|
||||||
|
serverIP,
|
||||||
|
(ip) -> {
|
||||||
serverIP = ip;
|
serverIP = ip;
|
||||||
});
|
});
|
||||||
|
|
||||||
final var serverPortText = NodeBuilder.text(AppContext.getString("serverPort"));
|
final var serverPortText = NodeBuilder.text(AppContext.getString("serverPort"));
|
||||||
final var serverPortInput = NodeBuilder.input(serverPort, (port) -> {
|
final var serverPortInput =
|
||||||
|
NodeBuilder.input(
|
||||||
|
serverPort,
|
||||||
|
(port) -> {
|
||||||
serverPort = port;
|
serverPort = port;
|
||||||
});
|
});
|
||||||
|
|
||||||
player2Container.addNodes(serverIPText, serverIPInput, serverIPSeparator,
|
player2Container.addNodes(
|
||||||
serverPortText, serverPortInput);
|
serverIPText,
|
||||||
|
serverIPInput,
|
||||||
|
serverIPSeparator,
|
||||||
|
serverPortText,
|
||||||
|
serverPortInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var versusText = NodeBuilder.header("VS");
|
final var versusText = NodeBuilder.header("VS");
|
||||||
|
|
||||||
final var connectionTypeText = NodeBuilder.text(AppContext.getString("connectionType") + ":");
|
final var connectionTypeText =
|
||||||
final var connectionTypeToggle = NodeBuilder.toggle(AppContext.getString("local"), AppContext.getString("server"), !isConnectionLocal, (server) -> {
|
NodeBuilder.text(AppContext.getString("connectionType") + ":");
|
||||||
|
final var connectionTypeToggle =
|
||||||
|
NodeBuilder.toggle(
|
||||||
|
AppContext.getString("local"),
|
||||||
|
AppContext.getString("server"),
|
||||||
|
!isConnectionLocal,
|
||||||
|
(server) -> {
|
||||||
isConnectionLocal = !server;
|
isConnectionLocal = !server;
|
||||||
reload();
|
reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
final var playButton = NodeBuilder.button(isConnectionLocal ? AppContext.getString("start") : AppContext.getString("connect"), () -> {
|
final var playButton =
|
||||||
final var information = new GameInformation(
|
NodeBuilder.button(
|
||||||
new String[]{player1Name, player2Name},
|
isConnectionLocal
|
||||||
new boolean[]{isPlayer1Human, isPlayer2Human},
|
? AppContext.getString("start")
|
||||||
new int[]{computer1Difficulty, computer2Difficulty},
|
: AppContext.getString("connect"),
|
||||||
new int[]{computer1ThinkTime, computer2ThinkTime},
|
() -> {
|
||||||
isConnectionLocal, serverIP, serverPort);
|
final var information =
|
||||||
|
new GameInformation(
|
||||||
|
new String[] {player1Name, player2Name},
|
||||||
|
new boolean[] {isPlayer1Human, isPlayer2Human},
|
||||||
|
new int[] {computer1Difficulty, computer2Difficulty},
|
||||||
|
new int[] {computer1ThinkTime, computer2ThinkTime},
|
||||||
|
isConnectionLocal,
|
||||||
|
serverIP,
|
||||||
|
serverPort);
|
||||||
|
|
||||||
if (isConnectionLocal) {
|
if (isConnectionLocal) {
|
||||||
App.activate(new TicTacToeLayer(information));
|
App.activate(new TicTacToeLayer(information));
|
||||||
@@ -163,7 +225,10 @@ public final class MultiplayerLayer extends Layer {
|
|||||||
playersContainer.addNodes(versusText);
|
playersContainer.addNodes(versusText);
|
||||||
playersContainer.addContainer(player2Container, true);
|
playersContainer.addContainer(player2Container, true);
|
||||||
|
|
||||||
final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> {
|
final var backButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("back"),
|
||||||
|
() -> {
|
||||||
App.activate(new MainLayer());
|
App.activate(new MainLayer());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
package org.toop.app.layer.layers;
|
package org.toop.app.layer.layers;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.ChoiceBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.layer.Container;
|
import org.toop.app.layer.Container;
|
||||||
import org.toop.app.layer.NodeBuilder;
|
import org.toop.app.layer.NodeBuilder;
|
||||||
import org.toop.app.layer.Popup;
|
import org.toop.app.layer.Popup;
|
||||||
import org.toop.app.layer.containers.VerticalContainer;
|
import org.toop.app.layer.containers.VerticalContainer;
|
||||||
import org.toop.framework.asset.resources.SettingsAsset;
|
import org.toop.framework.audio.VolumeControl;
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.resource.resources.SettingsAsset;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
import org.toop.local.AppSettings;
|
import org.toop.local.AppSettings;
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.control.ChoiceBox;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.Slider;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public final class OptionsPopup extends Popup {
|
public final class OptionsPopup extends Popup {
|
||||||
AppSettings appSettings = new AppSettings();
|
AppSettings appSettings = new AppSettings();
|
||||||
SettingsAsset settings = appSettings.getPath();
|
SettingsAsset settings = appSettings.getPath();
|
||||||
@@ -62,7 +61,10 @@ public final class OptionsPopup extends Popup {
|
|||||||
final Container mainContainer = new VerticalContainer(50, "");
|
final Container mainContainer = new VerticalContainer(50, "");
|
||||||
mainContainer.addContainer(optionsContainer, true);
|
mainContainer.addContainer(optionsContainer, true);
|
||||||
|
|
||||||
final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> {
|
final var backButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("back"),
|
||||||
|
() -> {
|
||||||
App.pop();
|
App.pop();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,7 +78,9 @@ public final class OptionsPopup extends Popup {
|
|||||||
private ChoiceBox<Locale> languageChoiceBox() {
|
private ChoiceBox<Locale> languageChoiceBox() {
|
||||||
assert AppContext.getLocalization() != null;
|
assert AppContext.getLocalization() != null;
|
||||||
|
|
||||||
final ChoiceBox<Locale> languageChoiceBox = NodeBuilder.choiceBox((locale) -> {
|
final ChoiceBox<Locale> languageChoiceBox =
|
||||||
|
NodeBuilder.choiceBox(
|
||||||
|
(locale) -> {
|
||||||
if (locale == AppContext.getLocale()) {
|
if (locale == AppContext.getLocale()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -87,7 +91,8 @@ public final class OptionsPopup extends Popup {
|
|||||||
App.reloadAll();
|
App.reloadAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
languageChoiceBox.setConverter(new javafx.util.StringConverter<>() {
|
languageChoiceBox.setConverter(
|
||||||
|
new javafx.util.StringConverter<>() {
|
||||||
@Override
|
@Override
|
||||||
public String toString(Locale locale) {
|
public String toString(Locale locale) {
|
||||||
return AppContext.getString(locale.getDisplayName().toLowerCase());
|
return AppContext.getString(locale.getDisplayName().toLowerCase());
|
||||||
@@ -106,29 +111,47 @@ public final class OptionsPopup extends Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Slider volumeSlider() {
|
private Slider volumeSlider() {
|
||||||
return NodeBuilder.slider(100, settings.getVolume(), (volume) -> {
|
return NodeBuilder.slider(
|
||||||
|
100,
|
||||||
|
settings.getVolume(),
|
||||||
|
(volume) -> {
|
||||||
settings.setVolume(volume);
|
settings.setVolume(volume);
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue())).asyncPostEvent();
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue(), VolumeControl.MASTERVOLUME))
|
||||||
|
.asyncPostEvent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Slider fxVolumeSlider() {
|
private Slider fxVolumeSlider() {
|
||||||
return NodeBuilder.slider(100, settings.getFxVolume(), (volume) -> {
|
return NodeBuilder.slider(
|
||||||
|
100,
|
||||||
|
settings.getFxVolume(),
|
||||||
|
(volume) -> {
|
||||||
settings.setFxVolume(volume);
|
settings.setFxVolume(volume);
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ChangeFxVolume(volume.doubleValue())).asyncPostEvent();
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue(), VolumeControl.FX))
|
||||||
|
.asyncPostEvent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Slider musicVolumeSlider() {
|
private Slider musicVolumeSlider() {
|
||||||
return NodeBuilder.slider(100, settings.getMusicVolume(), (volume) -> {
|
return NodeBuilder.slider(
|
||||||
|
100,
|
||||||
|
settings.getMusicVolume(),
|
||||||
|
(volume) -> {
|
||||||
settings.setMusicVolume(volume);
|
settings.setMusicVolume(volume);
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ChangeMusicVolume(volume.doubleValue())).asyncPostEvent();
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue(), VolumeControl.MUSIC))
|
||||||
|
.asyncPostEvent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Label fullscreenToggle() {
|
private Label fullscreenToggle() {
|
||||||
return NodeBuilder.toggle(AppContext.getString("windowed"), AppContext.getString("fullscreen"), !isWindowed, (fullscreen) -> {
|
return NodeBuilder.toggle(
|
||||||
|
AppContext.getString("windowed"),
|
||||||
|
AppContext.getString("fullscreen"),
|
||||||
|
!isWindowed,
|
||||||
|
(fullscreen) -> {
|
||||||
isWindowed = !fullscreen;
|
isWindowed = !fullscreen;
|
||||||
|
|
||||||
settings.setFullscreen(fullscreen);
|
settings.setFullscreen(fullscreen);
|
||||||
@@ -137,7 +160,9 @@ public final class OptionsPopup extends Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ChoiceBox<String> themeChoiceBox() {
|
private ChoiceBox<String> themeChoiceBox() {
|
||||||
final ChoiceBox<String> themeChoiceBox = NodeBuilder.choiceBox((theme) -> {
|
final ChoiceBox<String> themeChoiceBox =
|
||||||
|
NodeBuilder.choiceBox(
|
||||||
|
(theme) -> {
|
||||||
if (theme.equalsIgnoreCase(settings.getTheme())) {
|
if (theme.equalsIgnoreCase(settings.getTheme())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -146,7 +171,8 @@ public final class OptionsPopup extends Popup {
|
|||||||
App.setStyle(theme, settings.getLayoutSize());
|
App.setStyle(theme, settings.getLayoutSize());
|
||||||
});
|
});
|
||||||
|
|
||||||
themeChoiceBox.setConverter(new javafx.util.StringConverter<>() {
|
themeChoiceBox.setConverter(
|
||||||
|
new javafx.util.StringConverter<>() {
|
||||||
@Override
|
@Override
|
||||||
public String toString(String theme) {
|
public String toString(String theme) {
|
||||||
return AppContext.getString(theme);
|
return AppContext.getString(theme);
|
||||||
@@ -165,7 +191,9 @@ public final class OptionsPopup extends Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ChoiceBox<String> layoutSizeChoiceBox() {
|
private ChoiceBox<String> layoutSizeChoiceBox() {
|
||||||
final ChoiceBox<String> layoutSizeChoiceBox = NodeBuilder.choiceBox((layoutSize) -> {
|
final ChoiceBox<String> layoutSizeChoiceBox =
|
||||||
|
NodeBuilder.choiceBox(
|
||||||
|
(layoutSize) -> {
|
||||||
if (layoutSize.equalsIgnoreCase(settings.getLayoutSize())) {
|
if (layoutSize.equalsIgnoreCase(settings.getLayoutSize())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -174,7 +202,8 @@ public final class OptionsPopup extends Popup {
|
|||||||
App.setStyle(settings.getTheme(), layoutSize);
|
App.setStyle(settings.getTheme(), layoutSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
layoutSizeChoiceBox.setConverter(new javafx.util.StringConverter<>() {
|
layoutSizeChoiceBox.setConverter(
|
||||||
|
new javafx.util.StringConverter<>() {
|
||||||
@Override
|
@Override
|
||||||
public String toString(String layoutSize) {
|
public String toString(String layoutSize) {
|
||||||
return AppContext.getString(layoutSize);
|
return AppContext.getString(layoutSize);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.toop.app.layer.layers;
|
package org.toop.app.layer.layers;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.layer.Container;
|
import org.toop.app.layer.Container;
|
||||||
import org.toop.app.layer.NodeBuilder;
|
import org.toop.app.layer.NodeBuilder;
|
||||||
@@ -8,8 +9,6 @@ import org.toop.app.layer.containers.HorizontalContainer;
|
|||||||
import org.toop.app.layer.containers.VerticalContainer;
|
import org.toop.app.layer.containers.VerticalContainer;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
|
|
||||||
public final class QuitPopup extends Popup {
|
public final class QuitPopup extends Popup {
|
||||||
public QuitPopup() {
|
public QuitPopup() {
|
||||||
super(true);
|
super(true);
|
||||||
@@ -22,11 +21,17 @@ public final class QuitPopup extends Popup {
|
|||||||
|
|
||||||
final var sureText = NodeBuilder.header(AppContext.getString("quitSure"));
|
final var sureText = NodeBuilder.header(AppContext.getString("quitSure"));
|
||||||
|
|
||||||
final var yesButton = NodeBuilder.button(AppContext.getString("yes"), () -> {
|
final var yesButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("yes"),
|
||||||
|
() -> {
|
||||||
App.quit();
|
App.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
final var noButton = NodeBuilder.button(AppContext.getString("no"), () -> {
|
final var noButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("no"),
|
||||||
|
() -> {
|
||||||
App.pop();
|
App.pop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.toop.app.layer.layers.game;
|
package org.toop.app.layer.layers.game;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.layer.Container;
|
import org.toop.app.layer.Container;
|
||||||
import org.toop.app.layer.NodeBuilder;
|
import org.toop.app.layer.NodeBuilder;
|
||||||
@@ -8,8 +9,6 @@ import org.toop.app.layer.containers.VerticalContainer;
|
|||||||
import org.toop.app.layer.layers.MainLayer;
|
import org.toop.app.layer.layers.MainLayer;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
|
|
||||||
public class GameFinishedPopup extends Popup {
|
public class GameFinishedPopup extends Popup {
|
||||||
private final boolean isDraw;
|
private final boolean isDraw;
|
||||||
private final String winner;
|
private final String winner;
|
||||||
@@ -35,13 +34,17 @@ public class GameFinishedPopup extends Popup {
|
|||||||
|
|
||||||
mainContainer.addNodes(drawHeader, goodGameText);
|
mainContainer.addNodes(drawHeader, goodGameText);
|
||||||
} else {
|
} else {
|
||||||
final var winHeader = NodeBuilder.header(AppContext.getString("congratulations") + ": " + winner);
|
final var winHeader =
|
||||||
|
NodeBuilder.header(AppContext.getString("congratulations") + ": " + winner);
|
||||||
final var goodGameText = NodeBuilder.text(AppContext.getString("goodGameText"));
|
final var goodGameText = NodeBuilder.text(AppContext.getString("goodGameText"));
|
||||||
|
|
||||||
mainContainer.addNodes(winHeader, goodGameText);
|
mainContainer.addNodes(winHeader, goodGameText);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var backToMainMenuButton = NodeBuilder.button(AppContext.getString("backToMainMenu"), () -> {
|
final var backToMainMenuButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("backToMainMenu"),
|
||||||
|
() -> {
|
||||||
App.activate(new MainLayer());
|
App.activate(new MainLayer());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package org.toop.app.layer.layers.game;
|
package org.toop.app.layer.layers.game;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.GameInformation;
|
import org.toop.app.GameInformation;
|
||||||
@@ -17,14 +24,6 @@ import org.toop.game.tictactoe.TicTacToe;
|
|||||||
import org.toop.game.tictactoe.TicTacToeAI;
|
import org.toop.game.tictactoe.TicTacToeAI;
|
||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
public final class TicTacToeLayer extends Layer {
|
public final class TicTacToeLayer extends Layer {
|
||||||
private TicTacToeCanvas canvas;
|
private TicTacToeCanvas canvas;
|
||||||
|
|
||||||
@@ -47,7 +46,12 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
public TicTacToeLayer(GameInformation information) {
|
public TicTacToeLayer(GameInformation information) {
|
||||||
super("bg-primary");
|
super("bg-primary");
|
||||||
|
|
||||||
canvas = new TicTacToeCanvas(Color.LIME, (App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> {
|
canvas =
|
||||||
|
new TicTacToeCanvas(
|
||||||
|
Color.LIME,
|
||||||
|
(App.getHeight() / 100) * 75,
|
||||||
|
(App.getHeight() / 100) * 75,
|
||||||
|
(cell) -> {
|
||||||
try {
|
try {
|
||||||
if (information.isConnectionLocal()) {
|
if (information.isConnectionLocal()) {
|
||||||
if (ticTacToe.get().getCurrentTurn() == 0) {
|
if (ticTacToe.get().getCurrentTurn() == 0) {
|
||||||
@@ -56,11 +60,15 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
playerMoveQueue.put(new Game.Move(cell, 'O'));
|
playerMoveQueue.put(new Game.Move(cell, 'O'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (information.isPlayerHuman()[0] && currentPlayerMove != Game.EMPTY) {
|
if (information.isPlayerHuman()[0]
|
||||||
playerMoveQueue.put(new Game.Move(cell, firstPlayerIsMe.get()? 'X' : 'O'));
|
&& currentPlayerMove != Game.EMPTY) {
|
||||||
|
playerMoveQueue.put(
|
||||||
|
new Game.Move(
|
||||||
|
cell, firstPlayerIsMe.get() ? 'X' : 'O'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException _) {}
|
} catch (InterruptedException _) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ticTacToe = new AtomicReference<>(new TicTacToe());
|
ticTacToe = new AtomicReference<>(new TicTacToe());
|
||||||
@@ -104,7 +112,10 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> {
|
final var backButton =
|
||||||
|
NodeBuilder.button(
|
||||||
|
AppContext.getString("back"),
|
||||||
|
() -> {
|
||||||
App.activate(new MainLayer());
|
App.activate(new MainLayer());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,7 +141,7 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
final int currentPlayer = ticTacToe.get().getCurrentTurn();
|
final int currentPlayer = ticTacToe.get().getCurrentTurn();
|
||||||
|
|
||||||
currentPlayerNameText.setText(information.playerName()[currentPlayer]);
|
currentPlayerNameText.setText(information.playerName()[currentPlayer]);
|
||||||
currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0? "X" : "O");
|
currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0 ? "X" : "O");
|
||||||
|
|
||||||
Game.Move move = null;
|
Game.Move move = null;
|
||||||
|
|
||||||
@@ -140,24 +151,32 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
final Game.Move[] legalMoves = ticTacToe.get().getLegalMoves();
|
final Game.Move[] legalMoves = ticTacToe.get().getLegalMoves();
|
||||||
|
|
||||||
for (final Game.Move legalMove : legalMoves) {
|
for (final Game.Move legalMove : legalMoves) {
|
||||||
if (legalMove.position() == wants.position() && legalMove.value() == wants.value()) {
|
if (legalMove.position() == wants.position()
|
||||||
|
&& legalMove.value() == wants.value()) {
|
||||||
move = wants;
|
move = wants;
|
||||||
|
// TODO: maybe add break?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException _) {}
|
} catch (InterruptedException _) {
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final long start = System.currentTimeMillis();
|
final long start = System.currentTimeMillis();
|
||||||
|
|
||||||
move = ticTacToeAI.findBestMove(ticTacToe.get(), compurterDifficultyToDepth(10,
|
move =
|
||||||
information.computerDifficulty()[currentPlayer]));
|
ticTacToeAI.findBestMove(
|
||||||
|
ticTacToe.get(),
|
||||||
|
compurterDifficultyToDepth(
|
||||||
|
10, information.computerDifficulty()[currentPlayer]));
|
||||||
|
|
||||||
if (information.computerThinkTime()[currentPlayer] > 0) {
|
if (information.computerThinkTime()[currentPlayer] > 0) {
|
||||||
final long elapsedTime = System.currentTimeMillis() - start;
|
final long elapsedTime = System.currentTimeMillis() - start;
|
||||||
final long sleepTime = information.computerThinkTime()[currentPlayer] * 1000L - elapsedTime;
|
final long sleepTime =
|
||||||
|
information.computerThinkTime()[currentPlayer] * 1000L - elapsedTime;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(sleepTime);
|
Thread.sleep(sleepTime);
|
||||||
} catch (InterruptedException _) {}
|
} catch (InterruptedException _) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +194,10 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
|
|
||||||
if (state != Game.State.NORMAL) {
|
if (state != Game.State.NORMAL) {
|
||||||
if (state == Game.State.WIN) {
|
if (state == Game.State.WIN) {
|
||||||
App.push(new GameFinishedPopup(false, information.playerName()[ticTacToe.get().getCurrentTurn()]));
|
App.push(
|
||||||
|
new GameFinishedPopup(
|
||||||
|
false,
|
||||||
|
information.playerName()[ticTacToe.get().getCurrentTurn()]));
|
||||||
} else if (state == Game.State.DRAW) {
|
} else if (state == Game.State.DRAW) {
|
||||||
App.push(new GameFinishedPopup(true, ""));
|
App.push(new GameFinishedPopup(true, ""));
|
||||||
}
|
}
|
||||||
@@ -195,41 +217,43 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
|
|
||||||
private void handleServerGameStart(NetworkEvents.GameMatchResponse resp) {
|
private void handleServerGameStart(NetworkEvents.GameMatchResponse resp) {
|
||||||
// Meneer Bas de Jong. Dit functie wordt niet aangeroepen als je de challenger bent.
|
// Meneer Bas de Jong. Dit functie wordt niet aangeroepen als je de challenger bent.
|
||||||
// Ik heb veel dingen geprobeert. FUCKING veel dingen. Hij doet het niet.
|
// Ik heb veel dingen geprobeerd. FUCKING veel dingen. Hij doet het niet.
|
||||||
// Ik heb zelfs in jou code gekeken en unsubscribeAfterSuccess op false gezet. (zie ConnectedLayer).
|
// Ik heb zelfs in jouw code gekeken en unsubscribeAfterSuccess op false gezet. (zie
|
||||||
// Alle andere functies worden wel gecalt. Behalve dit.
|
// ConnectedLayer).
|
||||||
|
// Alle andere functies worden wel gecalld. Behalve dit.
|
||||||
|
|
||||||
// Ben jij gehandicapt of ik? Want het moet 1 van de 2 zijn. Ik ben dit al 2 uur aan het debuggen.
|
// Ben jij gehandicapt of ik? Want het moet 1 van de 2 zijn. Ik ben dit al 2 uur aan het
|
||||||
|
// debuggen.
|
||||||
// Ik ga nu slapen (04:46).
|
// Ik ga nu slapen (04:46).
|
||||||
|
|
||||||
// ⠀⠀⠀⠀⠀⠀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⠀⠀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⢀⣴⣿⣿⠿⣟⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⢀⣴⣿⣿⠿⣟⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⢸⣏⡏⠀⠀⠀⢣⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⢸⣏⡏⠀⠀⠀⢣⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⢸⣟⠧⠤⠤⠔⠋⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⢸⣟⠧⠤⠤⠔⠋⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠸⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠸⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⠀⠘⣿⡀⢀⣶⠤⠒⠀⢻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⠘⣿⡀⢀⣶⠤⠒⠀⢻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⠀⠀⢹⣧⠀⠀⠀⠀⠀⠈⢿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⠀⢹⣧⠀⠀⠀⠀⠀⠈⢿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠈⢿⣆⣠⣤⣤⣤⣤⣴⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⠀⠀⣿⡆⠀⠀⠀⠀⠀⠈⢿⣆⣠⣤⣤⣤⣤⣴⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⠀⢀⣾⢿⢿⠀⠀⠀⢀⣀⣀⠘⣿⠋⠁⠀⠙⢇⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⢀⣾⢿⢿⠀⠀⠀⢀⣀⣀⠘⣿⠋⠁⠀⠙⢇⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⢀⣾⢇⡞⠘⣧⠀⢖⡭⠞⢛⡄⠘⣆⠀⠀⠀⠈⢧⠀⠀⠀⠙⢿⣄⠀⠀⠀⠀
|
// ⠀⠀⠀⢀⣾⢇⡞⠘⣧⠀⢖⡭⠞⢛⡄⠘⣆⠀⠀⠀⠈⢧⠀⠀⠀⠙⢿⣄⠀⠀⠀⠀
|
||||||
// ⠀⠀⣠⣿⣛⣥⠤⠤⢿⡄⠀⠀⠈⠉⠀⠀⠹⡄⠀⠀⠀⠈⢧⠀⠀⠀⠈⠻⣦⠀⠀⠀
|
// ⠀⠀⣠⣿⣛⣥⠤⠤⢿⡄⠀⠀⠈⠉⠀⠀⠹⡄⠀⠀⠀⠈⢧⠀⠀⠀⠈⠻⣦⠀⠀⠀
|
||||||
// ⠀⣼⡟⡱⠛⠙⠀⠀⠘⢷⡀⠀⠀⠀⠀⠀⠀⠹⡀⠀⠀⠀⠈⣧⠀⠀⠀⠀⠹⣧⡀⠀
|
// ⠀⣼⡟⡱⠛⠙⠀⠀⠘⢷⡀⠀⠀⠀⠀⠀⠀⠹⡀⠀⠀⠀⠈⣧⠀⠀⠀⠀⠹⣧⡀⠀
|
||||||
// ⢸⡏⢠⠃⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠘⣧⠀⠀⠀⠀⠸⣷⡀
|
// ⢸⡏⢠⠃⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠘⣧⠀⠀⠀⠀⠸⣷⡀
|
||||||
// ⠸⣧⠘⡇⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⢹⡇⠀⠀⠀⠀⣿⠇
|
// ⠸⣧⠘⡇⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⢹⡇⠀⠀⠀⠀⣿⠇
|
||||||
// ⠀⣿⡄⢳⠀⠀⠀⠀⠀⠀⠀⠈⣷⠀⠀⠀⠀⠀⠀⠈⠆⠀⠀⠀⠀⠀⠀⠀⠀⣼⡟⠀
|
// ⠀⣿⡄⢳⠀⠀⠀⠀⠀⠀⠀⠈⣷⠀⠀⠀⠀⠀⠀⠈⠆⠀⠀⠀⠀⠀⠀⠀⠀⣼⡟⠀
|
||||||
// ⠀⢹⡇⠘⣇⠀⠀⠀⠀⠀⠀⠰⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⣼⡟⠀⠀
|
// ⠀⢹⡇⠘⣇⠀⠀⠀⠀⠀⠀⠰⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⣼⡟⠀⠀
|
||||||
// ⠀⢸⡇⠀⢹⡆⠀⠀⠀⠀⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⢳⣼⠟⠀⠀⠀
|
// ⠀⢸⡇⠀⢹⡆⠀⠀⠀⠀⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⢳⣼⠟⠀⠀⠀
|
||||||
// ⠀⠸⣧⣀⠀⢳⡀⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⢃⠀⢀⣴⡿⠁⠀⠀⠀⠀
|
// ⠀⠸⣧⣀⠀⢳⡀⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⢃⠀⢀⣴⡿⠁⠀⠀⠀⠀
|
||||||
// ⠀⠀⠈⠙⢷⣄⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⣠⡿⠟⠛⠉⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠈⠙⢷⣄⢳⡀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⣠⡿⠟⠛⠉⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⠀⠈⠻⢿⣷⣦⣄⣀⣀⣠⣤⠾⠷⣦⣤⣤⡶⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⠈⠻⢿⣷⣦⣄⣀⣀⣠⣤⠾⠷⣦⣤⣤⡶⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
// ⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
// ⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
|
||||||
player2Name = resp.opponent();
|
player2Name = resp.opponent();
|
||||||
System.out.println(player2Name);
|
System.out.println(player2Name);
|
||||||
|
|
||||||
currentPlayerMoveText.setText("X");
|
currentPlayerMoveText.setText("X");
|
||||||
|
|
||||||
if(!resp.playerToMove().equalsIgnoreCase(resp.opponent())) {
|
if (!resp.playerToMove().equalsIgnoreCase(resp.opponent())) {
|
||||||
currentPlayerNameText.setText(information.playerName()[0]);
|
currentPlayerNameText.setText(information.playerName()[0]);
|
||||||
firstPlayerIsMe.set(true);
|
firstPlayerIsMe.set(true);
|
||||||
|
|
||||||
@@ -246,17 +270,21 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
char playerChar;
|
char playerChar;
|
||||||
|
|
||||||
if (!resp.player().equalsIgnoreCase(player2Name)) {
|
if (!resp.player().equalsIgnoreCase(player2Name)) {
|
||||||
playerChar = firstPlayerIsMe.get()? 'X' : 'O';
|
playerChar = firstPlayerIsMe.get() ? 'X' : 'O';
|
||||||
} else {
|
} else {
|
||||||
playerChar = firstPlayerIsMe.get()? 'O' : 'X';
|
playerChar = firstPlayerIsMe.get() ? 'O' : 'X';
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game.Move move = new Game.Move(Integer.parseInt(resp.move()), playerChar);
|
final Game.Move move = new Game.Move(Integer.parseInt(resp.move()), playerChar);
|
||||||
final Game.State state = ticTacToe.get().play(move);
|
final Game.State state = ticTacToe.get().play(move);
|
||||||
|
|
||||||
if (state != Game.State.NORMAL) { //todo differentiate between future draw guaranteed and is currently a draw
|
if (state
|
||||||
|
!= Game.State.NORMAL) { // todo differentiate between future draw guaranteed and is
|
||||||
|
// currently a draw
|
||||||
if (state == Game.State.WIN) {
|
if (state == Game.State.WIN) {
|
||||||
App.push(new GameFinishedPopup(false, information.playerName()[ticTacToe.get().getCurrentTurn()]));
|
App.push(
|
||||||
|
new GameFinishedPopup(
|
||||||
|
false, information.playerName()[ticTacToe.get().getCurrentTurn()]));
|
||||||
} else if (state == Game.State.DRAW) {
|
} else if (state == Game.State.DRAW) {
|
||||||
App.push(new GameFinishedPopup(true, ""));
|
App.push(new GameFinishedPopup(true, ""));
|
||||||
}
|
}
|
||||||
@@ -268,8 +296,11 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
canvas.drawO(Color.BLUE, move.position());
|
canvas.drawO(Color.BLUE, move.position());
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPlayerNameText.setText(ticTacToe.get().getCurrentTurn() == (firstPlayerIsMe.get()? 0 : 1)? information.playerName()[0] : player2Name);
|
currentPlayerNameText.setText(
|
||||||
currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0? "X" : "O");
|
ticTacToe.get().getCurrentTurn() == (firstPlayerIsMe.get() ? 0 : 1)
|
||||||
|
? information.playerName()[0]
|
||||||
|
: player2Name);
|
||||||
|
currentPlayerMoveText.setText(ticTacToe.get().getCurrentTurn() == 0 ? "X" : "O");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void yourTurnResponse(NetworkEvents.YourTurnResponse response) {
|
private void yourTurnResponse(NetworkEvents.YourTurnResponse response) {
|
||||||
@@ -278,19 +309,23 @@ public final class TicTacToeLayer extends Layer {
|
|||||||
if (information.isPlayerHuman()[0]) {
|
if (information.isPlayerHuman()[0]) {
|
||||||
try {
|
try {
|
||||||
position = playerMoveQueue.take().position();
|
position = playerMoveQueue.take().position();
|
||||||
} catch (InterruptedException _) {}
|
} catch (InterruptedException _) {
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final Game.Move move = ticTacToeAI.findBestMove(ticTacToe.get(), compurterDifficultyToDepth(10,
|
final Game.Move move =
|
||||||
information.computerDifficulty()[0]));
|
ticTacToeAI.findBestMove(
|
||||||
|
ticTacToe.get(),
|
||||||
|
compurterDifficultyToDepth(10, information.computerDifficulty()[0]));
|
||||||
|
|
||||||
position = move.position();
|
position = Objects.requireNonNull(move).position();
|
||||||
}
|
}
|
||||||
|
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position))
|
new EventFlow()
|
||||||
|
.addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) position))
|
||||||
.postEvent();
|
.postEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleReceivedMessage(NetworkEvents.ReceivedMessage msg) {
|
private void handleReceivedMessage(NetworkEvents.ReceivedMessage msg) {
|
||||||
System.out.println("Received Message: " + msg.message()); //todo add chat window
|
System.out.println("Received Message: " + msg.message()); // todo add chat window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package org.toop.local;
|
package org.toop.local;
|
||||||
|
|
||||||
import org.toop.framework.asset.ResourceManager;
|
|
||||||
import org.toop.framework.asset.resources.LocalizationAsset;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import org.toop.framework.resource.ResourceManager;
|
||||||
|
import org.toop.framework.resource.resources.LocalizationAsset;
|
||||||
|
|
||||||
public class AppContext {
|
public class AppContext {
|
||||||
private static final LocalizationAsset localization = ResourceManager.get("localization");
|
private static final LocalizationAsset localization = ResourceManager.get("localization");
|
||||||
|
|||||||
@@ -1,31 +1,39 @@
|
|||||||
package org.toop.local;
|
package org.toop.local;
|
||||||
|
|
||||||
import jdk.jfr.Event;
|
|
||||||
import org.toop.app.App;
|
|
||||||
import org.toop.framework.asset.resources.SettingsAsset;
|
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
|
||||||
import org.toop.framework.settings.Settings;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.framework.audio.VolumeControl;
|
||||||
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.resource.ResourceManager;
|
||||||
|
import org.toop.framework.resource.ResourceMeta;
|
||||||
|
import org.toop.framework.resource.resources.SettingsAsset;
|
||||||
|
import org.toop.framework.settings.Settings;
|
||||||
|
|
||||||
public class AppSettings {
|
public class AppSettings {
|
||||||
|
|
||||||
private SettingsAsset settingsAsset;
|
private SettingsAsset settingsAsset;
|
||||||
|
|
||||||
public void applySettings() {
|
public void applySettings() {
|
||||||
SettingsAsset settings = getPath();
|
this.settingsAsset = getPath();
|
||||||
if (!settings.isLoaded()) {
|
if (!this.settingsAsset.isLoaded()) {
|
||||||
settings.load();
|
this.settingsAsset.load();
|
||||||
}
|
}
|
||||||
Settings settingsData = settings.getContent();
|
|
||||||
|
Settings settingsData = this.settingsAsset.getContent();
|
||||||
|
|
||||||
AppContext.setLocale(Locale.of(settingsData.locale));
|
AppContext.setLocale(Locale.of(settingsData.locale));
|
||||||
App.setFullscreen(settingsData.fullScreen);
|
App.setFullscreen(settingsData.fullScreen);
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume)).asyncPostEvent();
|
new EventFlow()
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ChangeFxVolume(settingsData.fxVolume)).asyncPostEvent();
|
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume, VolumeControl.MASTERVOLUME))
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ChangeMusicVolume(settingsData.musicVolume)).asyncPostEvent();
|
.asyncPostEvent();
|
||||||
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.fxVolume, VolumeControl.FX))
|
||||||
|
.asyncPostEvent();
|
||||||
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.musicVolume, VolumeControl.MUSIC))
|
||||||
|
.asyncPostEvent();
|
||||||
App.setStyle(settingsAsset.getTheme(), settingsAsset.getLayoutSize());
|
App.setStyle(settingsAsset.getTheme(), settingsAsset.getLayoutSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +53,15 @@ public class AppSettings {
|
|||||||
basePath = System.getProperty("user.home") + "/.config";
|
basePath = System.getProperty("user.home") + "/.config";
|
||||||
}
|
}
|
||||||
|
|
||||||
File settingsFile = new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
|
File settingsFile =
|
||||||
this.settingsAsset = new SettingsAsset(settingsFile);
|
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
|
||||||
|
|
||||||
|
return new SettingsAsset(settingsFile);
|
||||||
|
// this.settingsAsset = new SettingsAsset(settingsFile); // TODO
|
||||||
|
// ResourceManager.addAsset(new ResourceMeta<>("settings.json", new SettingsAsset(settingsFile))); // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.settingsAsset;
|
return this.settingsAsset;
|
||||||
|
// return ResourceManager.get("settings.json"); // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,13 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
<groupId>org.toop</groupId>
|
<groupId>org.toop</groupId>
|
||||||
<artifactId>pism_framework</artifactId>
|
<artifactId>pism</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>framework</artifactId>
|
||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -13,6 +18,14 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.toop</groupId>
|
||||||
|
<artifactId>processors</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.diffplug.spotless</groupId>
|
<groupId>com.diffplug.spotless</groupId>
|
||||||
<artifactId>spotless-maven-plugin</artifactId>
|
<artifactId>spotless-maven-plugin</artifactId>
|
||||||
@@ -123,6 +136,16 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_annotations</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -132,11 +155,73 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.14.1</version>
|
<version>3.14.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<showWarnings>true</showWarnings>
|
||||||
|
<fork>true</fork>
|
||||||
|
<executable>${java.home}/bin/javac</executable>
|
||||||
<source>25</source>
|
<source>25</source>
|
||||||
<target>25</target>
|
<target>25</target>
|
||||||
<release>25</release>
|
<release>25</release>
|
||||||
<encoding>UTF-8</encoding>
|
<encoding>UTF-8</encoding>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
|
||||||
|
<arg>
|
||||||
|
-Xplugin:ErrorProne
|
||||||
|
</arg>
|
||||||
|
<!-- TODO-->
|
||||||
|
<!-- -Xep:RestrictedApi:ERROR \-->
|
||||||
|
<!-- -XepOpt:RestrictedApi:annotation=org.toop.annotations.TestsOnly \-->
|
||||||
|
<!-- -XepOpt:RestrictedApi:allowlistRegex=(?s).*/src/test/java/.*|.*test\.java \-->
|
||||||
|
<!-- -XepOpt:RestrictedApi:message=This API is marked @TestsOnly and shouldn't be normally used.-->
|
||||||
|
<arg>-XDcompilePolicy=simple</arg>
|
||||||
|
<arg>--should-stop=ifError=FLOW</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<!-- <generatedSourcesDirectory>-->
|
||||||
|
<!-- ${project.build.directory}/generated-sources/-->
|
||||||
|
<!-- </generatedSourcesDirectory>-->
|
||||||
|
<!-- <annotationProcessors>-->
|
||||||
|
<!-- <annotationProcessor>-->
|
||||||
|
<!-- org.toop.processors.AutoResponseResultProcessor-->
|
||||||
|
<!-- </annotationProcessor>-->
|
||||||
|
<!-- </annotationProcessors>-->
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.toop</groupId>
|
||||||
|
<artifactId>processors</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>com.google.auto.service</groupId>
|
||||||
|
<artifactId>auto-service</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>com.squareup</groupId>
|
||||||
|
<artifactId>javapoet</artifactId>
|
||||||
|
<version>1.13.0</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.diffplug.spotless</groupId>
|
<groupId>com.diffplug.spotless</groupId>
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import org.apache.logging.log4j.core.LoggerContext;
|
|||||||
import org.apache.logging.log4j.core.config.Configuration;
|
import org.apache.logging.log4j.core.config.Configuration;
|
||||||
import org.apache.logging.log4j.core.config.LoggerConfig;
|
import org.apache.logging.log4j.core.config.LoggerConfig;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for configuring logging levels dynamically at runtime using Log4j 2.
|
* Utility class for configuring logging levels dynamically at runtime using Log4j 2.
|
||||||
*
|
*
|
||||||
* <p>Provides methods to enable or disable logs globally or per class, with support for specifying
|
* <p>Provides methods to enable or disable logs globally or per class, with support for specifying
|
||||||
* log levels either via {@link Level} enums or string names.
|
* log levels either via {@link Level} enums or string names.
|
||||||
*/
|
*/
|
||||||
// Todo: refactor
|
|
||||||
public final class Logging {
|
public final class Logging {
|
||||||
|
|
||||||
/** Disables all logging globally by setting the root logger level to {@link Level#OFF}. */
|
/** Disables all logging globally by setting the root logger level to {@link Level#OFF}. */
|
||||||
@@ -145,7 +146,7 @@ public final class Logging {
|
|||||||
* @param levelToLog name of the logging level (e.g., "DEBUG", "INFO")
|
* @param levelToLog name of the logging level (e.g., "DEBUG", "INFO")
|
||||||
*/
|
*/
|
||||||
public static void enableLogsForClass(String className, String levelToLog) {
|
public static void enableLogsForClass(String className, String levelToLog) {
|
||||||
Level level = Level.valueOf(levelToLog.trim().toUpperCase());
|
Level level = Level.valueOf(levelToLog.trim().toUpperCase(Locale.ROOT));
|
||||||
if (level != null && verifyStringIsActualClass(className)) {
|
if (level != null && verifyStringIsActualClass(className)) {
|
||||||
enableLogsForClassInternal(className, level);
|
enableLogsForClassInternal(className, level);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,36 +7,20 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A thread-safe, distributed unique ID generator following the Snowflake pattern.
|
* A thread-safe, distributed unique ID generator following the Snowflake pattern.
|
||||||
* <p>
|
*
|
||||||
* Each generated 64-bit ID encodes:
|
* <p>Each generated 64-bit ID encodes:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>41-bit timestamp (milliseconds since custom epoch)</li>
|
* <li>41-bit timestamp (milliseconds since custom epoch)
|
||||||
* <li>10-bit machine identifier</li>
|
* <li>10-bit machine identifier
|
||||||
* <li>12-bit sequence number for IDs generated in the same millisecond</li>
|
* <li>12-bit sequence number for IDs generated in the same millisecond
|
||||||
* </ul>
|
* </ul>
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
* <p>This implementation ensures:
|
* <p>This static implementation ensures global uniqueness per JVM process
|
||||||
* <ul>
|
* and can be accessed via {@link SnowflakeGenerator#nextId()}.
|
||||||
* <li>IDs are unique per machine.</li>
|
|
||||||
* <li>Monotonicity within the same machine.</li>
|
|
||||||
* <li>Safe concurrent generation via synchronized {@link #nextId()}.</li>
|
|
||||||
* </ul>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>Custom epoch is set to {@code 2025-01-01T00:00:00Z}.</p>
|
|
||||||
*
|
|
||||||
* <p>Usage example:</p>
|
|
||||||
* <pre>{@code
|
|
||||||
* SnowflakeGenerator generator = new SnowflakeGenerator();
|
|
||||||
* long id = generator.nextId();
|
|
||||||
* }</pre>
|
|
||||||
*/
|
*/
|
||||||
public class SnowflakeGenerator {
|
public final class SnowflakeGenerator {
|
||||||
|
|
||||||
/**
|
/** Custom epoch in milliseconds (2025-01-01T00:00:00Z). */
|
||||||
* Custom epoch in milliseconds (2025-01-01T00:00:00Z).
|
|
||||||
*/
|
|
||||||
private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli();
|
private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli();
|
||||||
|
|
||||||
// Bit allocations
|
// Bit allocations
|
||||||
@@ -44,27 +28,26 @@ public class SnowflakeGenerator {
|
|||||||
private static final long MACHINE_BITS = 10;
|
private static final long MACHINE_BITS = 10;
|
||||||
private static final long SEQUENCE_BITS = 12;
|
private static final long SEQUENCE_BITS = 12;
|
||||||
|
|
||||||
// Maximum values for each component
|
// Maximum values
|
||||||
private static final long MAX_MACHINE_ID = (1L << MACHINE_BITS) - 1;
|
private static final long MAX_MACHINE_ID = (1L << MACHINE_BITS) - 1;
|
||||||
private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
|
private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
|
||||||
private static final long MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) - 1;
|
private static final long MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) - 1;
|
||||||
|
|
||||||
// Bit shifts for composing the ID
|
// Bit shifts
|
||||||
private static final long MACHINE_SHIFT = SEQUENCE_BITS;
|
private static final long MACHINE_SHIFT = SEQUENCE_BITS;
|
||||||
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS;
|
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS;
|
||||||
|
|
||||||
/**
|
/** Unique machine identifier derived from MAC addresses. */
|
||||||
* Unique machine identifier derived from network interfaces (10 bits).
|
private static final long MACHINE_ID = genMachineId();
|
||||||
*/
|
|
||||||
private static final long machineId = SnowflakeGenerator.genMachineId();
|
|
||||||
|
|
||||||
private final AtomicLong lastTimestamp = new AtomicLong(-1L);
|
/** State variables (shared across all threads). */
|
||||||
private long sequence = 0L;
|
private static final AtomicLong LAST_TIMESTAMP = new AtomicLong(-1L);
|
||||||
|
private static long sequence = 0L;
|
||||||
|
|
||||||
/**
|
// Prevent instantiation
|
||||||
* Generates a 10-bit machine identifier based on MAC addresses of network interfaces.
|
private SnowflakeGenerator() {}
|
||||||
* Falls back to a random value if MAC cannot be determined.
|
|
||||||
*/
|
/** Generates a 10-bit machine identifier from MAC or random fallback. */
|
||||||
private static long genMachineId() {
|
private static long genMachineId() {
|
||||||
try {
|
try {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -80,48 +63,19 @@ public class SnowflakeGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns a globally unique 64-bit Snowflake ID. */
|
||||||
* For testing: manually set the last generated timestamp.
|
public static synchronized long nextId() {
|
||||||
* @param l timestamp in milliseconds
|
|
||||||
*/
|
|
||||||
void setTime(long l) {
|
|
||||||
this.lastTimestamp.set(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a SnowflakeGenerator.
|
|
||||||
* Validates that the machine ID is within allowed range.
|
|
||||||
* @throws IllegalArgumentException if machine ID is invalid
|
|
||||||
*/
|
|
||||||
public SnowflakeGenerator() {
|
|
||||||
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Machine ID must be between 0 and " + MAX_MACHINE_ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the next unique ID.
|
|
||||||
* <p>
|
|
||||||
* If multiple IDs are generated in the same millisecond, a sequence number
|
|
||||||
* is incremented. If the sequence overflows, waits until the next millisecond.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @return a unique 64-bit ID
|
|
||||||
* @throws IllegalStateException if clock moves backwards or timestamp exceeds 41-bit limit
|
|
||||||
*/
|
|
||||||
public synchronized long nextId() {
|
|
||||||
long currentTimestamp = timestamp();
|
long currentTimestamp = timestamp();
|
||||||
|
|
||||||
if (currentTimestamp < lastTimestamp.get()) {
|
if (currentTimestamp < LAST_TIMESTAMP.get()) {
|
||||||
throw new IllegalStateException("Clock moved backwards. Refusing to generate id.");
|
throw new IllegalStateException("Clock moved backwards. Refusing to generate ID.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTimestamp > MAX_TIMESTAMP) {
|
if (currentTimestamp > MAX_TIMESTAMP) {
|
||||||
throw new IllegalStateException("Timestamp bits overflow, Snowflake expired.");
|
throw new IllegalStateException("Timestamp bits overflow — Snowflake expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTimestamp == lastTimestamp.get()) {
|
if (currentTimestamp == LAST_TIMESTAMP.get()) {
|
||||||
sequence = (sequence + 1) & MAX_SEQUENCE;
|
sequence = (sequence + 1) & MAX_SEQUENCE;
|
||||||
if (sequence == 0) {
|
if (sequence == 0) {
|
||||||
currentTimestamp = waitNextMillis(currentTimestamp);
|
currentTimestamp = waitNextMillis(currentTimestamp);
|
||||||
@@ -130,30 +84,22 @@ public class SnowflakeGenerator {
|
|||||||
sequence = 0L;
|
sequence = 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTimestamp.set(currentTimestamp);
|
LAST_TIMESTAMP.set(currentTimestamp);
|
||||||
|
|
||||||
return ((currentTimestamp - EPOCH) << TIMESTAMP_SHIFT)
|
return ((currentTimestamp - EPOCH) << TIMESTAMP_SHIFT)
|
||||||
| (machineId << MACHINE_SHIFT)
|
| (MACHINE_ID << MACHINE_SHIFT)
|
||||||
| sequence;
|
| sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Waits until next millisecond if sequence exhausted. */
|
||||||
* Waits until the next millisecond if sequence overflows.
|
private static long waitNextMillis(long lastTimestamp) {
|
||||||
* @param lastTimestamp previous timestamp
|
|
||||||
* @return new timestamp
|
|
||||||
*/
|
|
||||||
private long waitNextMillis(long lastTimestamp) {
|
|
||||||
long ts = timestamp();
|
long ts = timestamp();
|
||||||
while (ts <= lastTimestamp) {
|
while (ts <= lastTimestamp) ts = timestamp();
|
||||||
ts = timestamp();
|
|
||||||
}
|
|
||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns current timestamp in milliseconds. */
|
||||||
* Returns current system timestamp in milliseconds.
|
private static long timestamp() {
|
||||||
*/
|
|
||||||
private long timestamp() {
|
|
||||||
return System.currentTimeMillis();
|
return System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package org.toop.framework.asset.events;
|
|
||||||
|
|
||||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
|
||||||
|
|
||||||
public class AssetLoaderEvents {
|
|
||||||
public record LoadingProgressUpdate(int hasLoadedAmount, int isLoadingAmount) implements EventWithoutSnowflake {}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package org.toop.framework.asset.resources;
|
|
||||||
|
|
||||||
import javafx.scene.media.Media;
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.LoadableResource;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
@FileExtension({"mp3"})
|
|
||||||
public class MusicAsset extends BaseResource implements LoadableResource {
|
|
||||||
private Media media;
|
|
||||||
|
|
||||||
public MusicAsset(final File audioFile) {
|
|
||||||
super(audioFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Media getMedia() {
|
|
||||||
if (media == null) {
|
|
||||||
media = new Media(file.toURI().toString());
|
|
||||||
}
|
|
||||||
return media;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void load() {
|
|
||||||
if (media == null) media = new Media(file.toURI().toString());
|
|
||||||
this.isLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unload() {
|
|
||||||
media = null;
|
|
||||||
isLoaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLoaded() {
|
|
||||||
return isLoaded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package org.toop.framework.asset.resources;
|
|
||||||
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.LoadableResource;
|
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
|
|
||||||
@FileExtension({"wav"})
|
|
||||||
public class SoundEffectAsset extends BaseResource implements LoadableResource {
|
|
||||||
private byte[] rawData;
|
|
||||||
|
|
||||||
public SoundEffectAsset(final File audioFile) {
|
|
||||||
super(audioFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets a new clip to play
|
|
||||||
public Clip getNewClip() throws LineUnavailableException, UnsupportedAudioFileException, IOException {
|
|
||||||
// Get a new clip from audio system
|
|
||||||
Clip clip = AudioSystem.getClip();
|
|
||||||
|
|
||||||
// Insert a new audio stream into the clip
|
|
||||||
AudioInputStream inputStream = this.getAudioStream();
|
|
||||||
AudioFormat baseFormat = inputStream.getFormat();
|
|
||||||
if (baseFormat.getSampleSizeInBits() > 16) inputStream = downSampleAudio(inputStream, baseFormat);
|
|
||||||
clip.open(inputStream); // ^ Clip can only run 16 bit and lower, thus downsampling necessary.
|
|
||||||
return clip;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates a new audio stream from byte array
|
|
||||||
private AudioInputStream getAudioStream() throws UnsupportedAudioFileException, IOException {
|
|
||||||
// Check if raw data is loaded into memory
|
|
||||||
if(!this.isLoaded()){
|
|
||||||
this.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn rawData into an input stream and turn that into an audio input stream;
|
|
||||||
return AudioSystem.getAudioInputStream(new ByteArrayInputStream(this.rawData));
|
|
||||||
}
|
|
||||||
|
|
||||||
private AudioInputStream downSampleAudio(AudioInputStream audioInputStream, AudioFormat baseFormat) {
|
|
||||||
AudioFormat decodedFormat = new AudioFormat(
|
|
||||||
AudioFormat.Encoding.PCM_SIGNED,
|
|
||||||
baseFormat.getSampleRate(),
|
|
||||||
16, // force 16-bit
|
|
||||||
baseFormat.getChannels(),
|
|
||||||
baseFormat.getChannels() * 2,
|
|
||||||
baseFormat.getSampleRate(),
|
|
||||||
false // little-endian
|
|
||||||
);
|
|
||||||
|
|
||||||
return AudioSystem.getAudioInputStream(decodedFormat, audioInputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void load() {
|
|
||||||
try {
|
|
||||||
this.rawData = Files.readAllBytes(file.toPath());
|
|
||||||
this.isLoaded = true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unload() {
|
|
||||||
this.rawData = null;
|
|
||||||
this.isLoaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLoaded() {
|
|
||||||
return this.isLoaded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package org.toop.framework.asset.types;
|
|
||||||
|
|
||||||
import org.toop.framework.asset.ResourceLoader;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a resource that can be composed of multiple files, or "bundled" together
|
|
||||||
* under a common base name.
|
|
||||||
*
|
|
||||||
* <p>Implementing classes allow an {@link ResourceLoader}
|
|
||||||
* to automatically merge multiple related files into a single resource instance.</p>
|
|
||||||
*
|
|
||||||
* <p>Typical use cases include:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>Localization assets, where multiple `.properties` files (e.g., `messages_en.properties`,
|
|
||||||
* `messages_nl.properties`) are grouped under the same logical resource.</li>
|
|
||||||
* <li>Sprite sheets, tile sets, or other multi-file resources that logically belong together.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Implementing classes must provide:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #loadFile(File)}: Logic to load or merge an individual file into the resource.</li>
|
|
||||||
* <li>{@link #getBaseName()}: A consistent base name used to group multiple files into this resource.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Example usage:</p>
|
|
||||||
* <pre>{@code
|
|
||||||
* public class LocalizationAsset extends BaseResource implements BundledResource {
|
|
||||||
* private final String baseName;
|
|
||||||
*
|
|
||||||
* public LocalizationAsset(File file) {
|
|
||||||
* super(file);
|
|
||||||
* this.baseName = extractBaseName(file.getName());
|
|
||||||
* loadFile(file);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @Override
|
|
||||||
* public void loadFile(File file) {
|
|
||||||
* // merge file into existing bundles
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @Override
|
|
||||||
* public String getBaseName() {
|
|
||||||
* return baseName;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>When used with an asset loader, all files sharing the same base name are
|
|
||||||
* automatically merged into a single resource instance.</p>
|
|
||||||
*/
|
|
||||||
public interface BundledResource {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load or merge an additional file into this resource.
|
|
||||||
*
|
|
||||||
* @param file the file to load or merge
|
|
||||||
*/
|
|
||||||
void loadFile(File file);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a base name for grouping multiple files into this single resource.
|
|
||||||
* Files with the same base name are automatically merged by the loader.
|
|
||||||
*
|
|
||||||
* @return the base name used to identify this bundled resource
|
|
||||||
*/
|
|
||||||
String getBaseName();
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// Returns the name
|
|
||||||
// */
|
|
||||||
// String getDefaultName();
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package org.toop.framework.asset.types;
|
|
||||||
|
|
||||||
import org.toop.framework.asset.ResourceLoader;
|
|
||||||
import org.toop.framework.asset.resources.BaseResource;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to declare which file extensions a {@link BaseResource} subclass
|
|
||||||
* can handle.
|
|
||||||
*
|
|
||||||
* <p>This annotation is processed by the {@link ResourceLoader}
|
|
||||||
* to automatically register resource types for specific file extensions.
|
|
||||||
* Each extension listed will be mapped to the annotated resource class,
|
|
||||||
* allowing the loader to instantiate the correct type when scanning files.</p>
|
|
||||||
*
|
|
||||||
* <p>Usage example:</p>
|
|
||||||
* <pre>{@code
|
|
||||||
* @FileExtension({"png", "jpg"})
|
|
||||||
* public class ImageAsset extends BaseResource implements LoadableResource {
|
|
||||||
* ...
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>Key points:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>The annotation is retained at runtime for reflection-based registration.</li>
|
|
||||||
* <li>Can only be applied to types (classes) that extend {@link BaseResource}.</li>
|
|
||||||
* <li>Multiple extensions can be specified in the {@code value()} array.</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
public @interface FileExtension {
|
|
||||||
/**
|
|
||||||
* The list of file extensions (without leading dot) that the annotated resource class can handle.
|
|
||||||
*
|
|
||||||
* @return array of file extensions
|
|
||||||
*/
|
|
||||||
String[] value();
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package org.toop.framework.asset.types;
|
|
||||||
|
|
||||||
import org.toop.framework.asset.ResourceLoader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a resource that can be explicitly loaded and unloaded.
|
|
||||||
* <p>
|
|
||||||
* Any class implementing {@code LoadableResource} is responsible for managing its own
|
|
||||||
* loading and unloading logic, such as reading files, initializing data structures,
|
|
||||||
* or allocating external resources.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>Implementing classes must define the following behaviors:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #load()}: Load the resource into memory or perform necessary initialization.</li>
|
|
||||||
* <li>{@link #unload()}: Release any held resources or memory when the resource is no longer needed.</li>
|
|
||||||
* <li>{@link #isLoaded()}: Return {@code true} if the resource has been successfully loaded and is ready for use, {@code false} otherwise.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Typical usage:</p>
|
|
||||||
* <pre>{@code
|
|
||||||
* public class MyFontAsset extends BaseResource implements LoadableResource {
|
|
||||||
* private boolean loaded = false;
|
|
||||||
*
|
|
||||||
* @Override
|
|
||||||
* public void load() {
|
|
||||||
* // Load font file into memory
|
|
||||||
* loaded = true;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @Override
|
|
||||||
* public void unload() {
|
|
||||||
* // Release resources if needed
|
|
||||||
* loaded = false;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @Override
|
|
||||||
* public boolean isLoaded() {
|
|
||||||
* return loaded;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>This interface is commonly used with {@link PreloadResource} to allow automatic
|
|
||||||
* loading by an {@link ResourceLoader} if desired.</p>
|
|
||||||
*/
|
|
||||||
public interface LoadableResource {
|
|
||||||
/**
|
|
||||||
* Load the resource into memory or initialize it.
|
|
||||||
* This method may throw runtime exceptions if loading fails.
|
|
||||||
*/
|
|
||||||
void load();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload the resource and free any associated resources.
|
|
||||||
* After this call, {@link #isLoaded()} should return false.
|
|
||||||
*/
|
|
||||||
void unload();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the resource has been successfully loaded.
|
|
||||||
*
|
|
||||||
* @return true if the resource is loaded and ready for use, false otherwise
|
|
||||||
*/
|
|
||||||
boolean isLoaded();
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.toop.framework.audio;
|
||||||
|
|
||||||
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
|
import org.toop.framework.audio.interfaces.MusicManager;
|
||||||
|
import org.toop.framework.audio.interfaces.SoundEffectManager;
|
||||||
|
import org.toop.framework.audio.interfaces.VolumeManager;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
|
||||||
|
public class AudioEventListener<T extends AudioResource, K extends AudioResource> {
|
||||||
|
private final MusicManager<T> musicManager;
|
||||||
|
private final SoundEffectManager<K> soundEffectManager;
|
||||||
|
private final VolumeManager audioVolumeManager;
|
||||||
|
|
||||||
|
public AudioEventListener(
|
||||||
|
MusicManager<T> musicManager,
|
||||||
|
SoundEffectManager<K> soundEffectManager,
|
||||||
|
VolumeManager audioVolumeManager
|
||||||
|
) {
|
||||||
|
this.musicManager = musicManager;
|
||||||
|
this.soundEffectManager = soundEffectManager;
|
||||||
|
this.audioVolumeManager = audioVolumeManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioEventListener<?, ?> initListeners(String buttonSoundToPlay) {
|
||||||
|
new EventFlow()
|
||||||
|
.listen(this::handleStopMusicManager)
|
||||||
|
.listen(this::handlePlaySound)
|
||||||
|
.listen(this::handleStopSound)
|
||||||
|
.listen(this::handleMusicStart)
|
||||||
|
.listen(this::handleVolumeChange)
|
||||||
|
.listen(this::handleGetVolume)
|
||||||
|
.listen(AudioEvents.ClickButton.class, _ ->
|
||||||
|
soundEffectManager.play(buttonSoundToPlay, false));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStopMusicManager(AudioEvents.StopAudioManager event) {
|
||||||
|
this.musicManager.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePlaySound(AudioEvents.PlayEffect event) {
|
||||||
|
this.soundEffectManager.play(event.fileName(), event.loop());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStopSound(AudioEvents.StopEffect event) {
|
||||||
|
this.soundEffectManager.stop(event.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMusicStart(AudioEvents.StartBackgroundMusic event) {
|
||||||
|
this.musicManager.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVolumeChange(AudioEvents.ChangeVolume event) {
|
||||||
|
this.audioVolumeManager.setVolume(event.newVolume() / 100, event.controlType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGetVolume(AudioEvents.GetVolume event) {
|
||||||
|
new EventFlow()
|
||||||
|
.addPostEvent(
|
||||||
|
new AudioEvents.GetVolumeResponse(
|
||||||
|
audioVolumeManager.getVolume(event.controlType()),
|
||||||
|
event.identifier()))
|
||||||
|
.asyncPostEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,91 +1,84 @@
|
|||||||
package org.toop.framework.audio;
|
package org.toop.framework.audio;
|
||||||
|
|
||||||
import com.sun.scenario.Settings;
|
import org.toop.framework.audio.interfaces.AudioManager;
|
||||||
import javafx.scene.media.MediaPlayer;
|
import org.toop.framework.audio.interfaces.VolumeManager;
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
|
||||||
|
|
||||||
import javax.sound.sampled.Clip;
|
/**
|
||||||
import javax.sound.sampled.FloatControl;
|
* Concrete implementation of {@link VolumeManager} that delegates volume control
|
||||||
|
* to the {@link VolumeControl} enum.
|
||||||
public class AudioVolumeManager {
|
* <p>
|
||||||
private final SoundManager sM;
|
* This class acts as a central point for updating volume levels for different
|
||||||
|
* audio categories (MASTER, FX, MUSIC) and for registering audio managers
|
||||||
private double volume = 1.0;
|
* to the appropriate volume types.
|
||||||
private double fxVolume = 1.0;
|
* </p>
|
||||||
private double musicVolume = 1.0;
|
*
|
||||||
|
* <p>Key responsibilities:</p>
|
||||||
public AudioVolumeManager(SoundManager soundManager){
|
* <ul>
|
||||||
this.sM = soundManager;
|
* <li>Set and get volume levels for each {@link VolumeControl} category.</li>
|
||||||
|
* <li>Register {@link AudioManager} instances to specific volume types so
|
||||||
new EventFlow()
|
* that their active audio resources receive volume updates automatically.</li>
|
||||||
.listen(this::handleVolumeChange)
|
* <li>Automatically scales non-master volumes according to the current master volume.</li>
|
||||||
.listen(this::handleFxVolumeChange)
|
* </ul>
|
||||||
.listen(this::handleMusicVolumeChange)
|
*
|
||||||
.listen(this::handleGetCurrentVolume)
|
* <p>Example usage:</p>
|
||||||
.listen(this::handleGetCurrentFxVolume)
|
* <pre>{@code
|
||||||
.listen(this::handleGetCurrentMusicVolume);
|
* AudioVolumeManager volumeManager = new AudioVolumeManager();
|
||||||
|
*
|
||||||
|
* // Register music manager to MUSIC volume type
|
||||||
|
* volumeManager.registerManager(VolumeControl.MUSIC, musicManager);
|
||||||
|
*
|
||||||
|
* // Set master volume to 80%
|
||||||
|
* volumeManager.setVolume(0.8, VolumeControl.MASTERVOLUME);
|
||||||
|
*
|
||||||
|
* // Set FX volume to 50% of master
|
||||||
|
* volumeManager.setVolume(0.5, VolumeControl.FX);
|
||||||
|
*
|
||||||
|
* // Retrieve current MUSIC volume
|
||||||
|
* double musicVol = volumeManager.getVolume(VolumeControl.MUSIC);
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public class AudioVolumeManager implements VolumeManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume for a specific volume type.
|
||||||
|
* <p>
|
||||||
|
* This method automatically takes into account the master volume
|
||||||
|
* for non-master types.
|
||||||
|
*
|
||||||
|
* @param newVolume the desired volume level (0.0 to 1.0)
|
||||||
|
* @param type the {@link VolumeControl} category to update
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setVolume(double newVolume, VolumeControl type) {
|
||||||
|
type.setVolume(newVolume, VolumeControl.MASTERVOLUME.getVolume());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMusicVolume(MediaPlayer mediaPlayer){
|
/**
|
||||||
mediaPlayer.setVolume(this.musicVolume * this.volume);
|
* Returns the current volume for the specified {@link VolumeControl} category.
|
||||||
|
*
|
||||||
|
* @param type the volume category
|
||||||
|
* @return the current volume (0.0 to 1.0)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public double getVolume(VolumeControl type) {
|
||||||
|
return type.getVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateSoundEffectVolume(Clip clip){
|
/**
|
||||||
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)){
|
* Registers an {@link AudioManager} with the specified {@link VolumeControl} category.
|
||||||
FloatControl volumeControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
|
* <p>
|
||||||
float min = volumeControl.getMinimum();
|
* All active audio resources managed by the given {@link AudioManager} will
|
||||||
float max = volumeControl.getMaximum();
|
* automatically receive volume updates when the volume type changes.
|
||||||
float dB = (float) (Math.log10(Math.max(this.fxVolume * this.volume, 0.0001)) * 20.0); // convert linear to dB
|
*
|
||||||
dB = Math.max(min, Math.min(max, dB));
|
* @param type the volume type to register the manager under
|
||||||
volumeControl.setValue(dB);
|
* @param manager the audio manager to register
|
||||||
|
* @return the current {@link AudioVolumeManager} instance (for method chaining)
|
||||||
|
*/
|
||||||
|
public AudioVolumeManager registerManager(VolumeControl type, AudioManager<? extends AudioResource> manager) {
|
||||||
|
if (manager != null) {
|
||||||
|
type.addManager(manager);
|
||||||
}
|
}
|
||||||
}
|
return this;
|
||||||
|
|
||||||
private double limitVolume(double volume) {
|
|
||||||
if (volume > 1.0) return 1.0;
|
|
||||||
else return Math.max(volume, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) {
|
|
||||||
this.fxVolume = limitVolume(event.newVolume() / 100);
|
|
||||||
for (Clip clip : sM.getActiveSoundEffects().values()){
|
|
||||||
updateSoundEffectVolume(clip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleVolumeChange(AudioEvents.ChangeVolume event) {
|
|
||||||
this.volume = limitVolume(event.newVolume() / 100);
|
|
||||||
for (MediaPlayer mediaPlayer : sM.getActiveMusic()) {
|
|
||||||
this.updateMusicVolume(mediaPlayer);
|
|
||||||
}
|
|
||||||
for (Clip clip : sM.getActiveSoundEffects().values()){
|
|
||||||
updateSoundEffectVolume(clip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event){
|
|
||||||
this.musicVolume = limitVolume(event.newVolume() / 100);
|
|
||||||
System.out.println(this.musicVolume);
|
|
||||||
System.out.println(this.volume);
|
|
||||||
for (MediaPlayer mediaPlayer : sM.getActiveMusic()){
|
|
||||||
this.updateMusicVolume(mediaPlayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGetCurrentVolume(AudioEvents.GetCurrentVolume event) {
|
|
||||||
new EventFlow().addPostEvent(new AudioEvents.GetCurrentVolumeResponse(volume * 100, event.snowflakeId()))
|
|
||||||
.asyncPostEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGetCurrentFxVolume(AudioEvents.GetCurrentFxVolume event) {
|
|
||||||
new EventFlow().addPostEvent(new AudioEvents.GetCurrentFxVolumeResponse(fxVolume * 100, event.snowflakeId()))
|
|
||||||
.asyncPostEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGetCurrentMusicVolume(AudioEvents.GetCurrentMusicVolume event){
|
|
||||||
new EventFlow().addPostEvent(new AudioEvents.GetCurrentMusicVolumeResponse(musicVolume * 100, event.snowflakeId()))
|
|
||||||
.asyncPostEvent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package org.toop.framework.audio;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.toop.framework.dispatch.interfaces.Dispatcher;
|
||||||
|
import org.toop.framework.dispatch.JavaFXDispatcher;
|
||||||
|
import org.toop.annotations.TestsOnly;
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class MusicManager<T extends AudioResource> implements org.toop.framework.audio.interfaces.MusicManager<T> {
|
||||||
|
private static final Logger logger = LogManager.getLogger(MusicManager.class);
|
||||||
|
|
||||||
|
private final List<T> backgroundMusic = new ArrayList<>();
|
||||||
|
private final Dispatcher dispatcher;
|
||||||
|
private final List<T> resources;
|
||||||
|
private int playingIndex = 0;
|
||||||
|
private boolean playing = false;
|
||||||
|
|
||||||
|
public MusicManager(List<T> resources) {
|
||||||
|
this.dispatcher = new JavaFXDispatcher();
|
||||||
|
this.resources = resources;
|
||||||
|
createShuffled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code @TestsOnly} DO NOT USE
|
||||||
|
*/
|
||||||
|
@TestsOnly
|
||||||
|
public MusicManager(List<T> resources, Dispatcher dispatcher) {
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
|
this.resources = new ArrayList<>(resources);
|
||||||
|
backgroundMusic.addAll(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<T> getActiveAudio() {
|
||||||
|
return backgroundMusic;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addBackgroundMusic(T musicAsset) {
|
||||||
|
backgroundMusic.add(musicAsset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createShuffled() {
|
||||||
|
backgroundMusic.clear();
|
||||||
|
Collections.shuffle(resources);
|
||||||
|
backgroundMusic.addAll(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
if (playing) {
|
||||||
|
logger.warn("MusicManager is already playing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backgroundMusic.isEmpty()) return;
|
||||||
|
|
||||||
|
playingIndex = 0;
|
||||||
|
playing = true;
|
||||||
|
playCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in testing
|
||||||
|
void play(int index) {
|
||||||
|
if (playing) {
|
||||||
|
logger.warn("MusicManager is already playing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backgroundMusic.isEmpty()) return;
|
||||||
|
|
||||||
|
playingIndex = index;
|
||||||
|
playing = true;
|
||||||
|
playCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playCurrentTrack() {
|
||||||
|
if (playingIndex >= backgroundMusic.size()) {
|
||||||
|
playingIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
T current = backgroundMusic.get(playingIndex);
|
||||||
|
|
||||||
|
if (current == null) {
|
||||||
|
logger.error("Current track is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatcher.run(() -> {
|
||||||
|
current.play();
|
||||||
|
|
||||||
|
setTrackRunnable(current);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTrackRunnable(T track) {
|
||||||
|
track.setOnEnd(() -> {
|
||||||
|
playingIndex++;
|
||||||
|
playCurrentTrack();
|
||||||
|
});
|
||||||
|
|
||||||
|
track.setOnError(() -> {
|
||||||
|
logger.error("Error playing track: {}", track);
|
||||||
|
backgroundMusic.remove(track);
|
||||||
|
|
||||||
|
if (!backgroundMusic.isEmpty()) {
|
||||||
|
playCurrentTrack();
|
||||||
|
} else {
|
||||||
|
playing = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (!playing) return;
|
||||||
|
|
||||||
|
playing = false;
|
||||||
|
dispatcher.run(() -> backgroundMusic.forEach(T::stop));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.toop.framework.audio;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.toop.framework.resource.ResourceManager;
|
||||||
|
import org.toop.framework.resource.ResourceMeta;
|
||||||
|
import org.toop.framework.resource.resources.BaseResource;
|
||||||
|
import org.toop.framework.resource.resources.MusicAsset;
|
||||||
|
import org.toop.framework.resource.resources.SoundEffectAsset;
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
|
||||||
|
import javax.sound.sampled.Clip;
|
||||||
|
import javax.sound.sampled.LineEvent;
|
||||||
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class SoundEffectManager<T extends AudioResource> implements org.toop.framework.audio.interfaces.SoundEffectManager<T> {
|
||||||
|
private static final Logger logger = LogManager.getLogger(SoundEffectManager.class);
|
||||||
|
private final HashMap<String, T> soundEffectResources;
|
||||||
|
|
||||||
|
public <K extends BaseResource & AudioResource> SoundEffectManager(List<ResourceMeta<K>> resources) {
|
||||||
|
// If there are duplicates, takes discards the first
|
||||||
|
this.soundEffectResources = (HashMap<String, T>) resources
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.
|
||||||
|
toMap(ResourceMeta::getName, ResourceMeta::getResource, (a, b) -> b, HashMap::new));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<T> getActiveAudio() {
|
||||||
|
return this.soundEffectResources.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play(String name, boolean loop) {
|
||||||
|
T asset = soundEffectResources.get(name);
|
||||||
|
|
||||||
|
if (asset == null) {
|
||||||
|
logger.warn("Unable to load audio asset: {}", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asset.play();
|
||||||
|
|
||||||
|
logger.debug("Playing sound: {}", asset.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop(String name){
|
||||||
|
T asset = soundEffectResources.get(name);
|
||||||
|
|
||||||
|
if (asset == null) {
|
||||||
|
logger.warn("Unable to load audio asset: {}", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asset.stop();
|
||||||
|
|
||||||
|
logger.debug("Stopped sound: {}", asset.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
package org.toop.framework.audio;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.toop.framework.SnowflakeGenerator;
|
|
||||||
import org.toop.framework.asset.ResourceManager;
|
|
||||||
import org.toop.framework.asset.ResourceMeta;
|
|
||||||
import org.toop.framework.asset.resources.MusicAsset;
|
|
||||||
import org.toop.framework.asset.resources.SoundEffectAsset;
|
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
|
||||||
|
|
||||||
import javafx.scene.media.MediaPlayer;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
import javax.sound.sampled.*;
|
|
||||||
|
|
||||||
public class SoundManager {
|
|
||||||
private static final Logger logger = LogManager.getLogger(SoundManager.class);
|
|
||||||
private final List<MediaPlayer> activeMusic = new ArrayList<>();
|
|
||||||
private final Queue<MusicAsset> backgroundMusicQueue = new LinkedList<>();
|
|
||||||
private final Map<Long, Clip> activeSoundEffects = new HashMap<>();
|
|
||||||
private final HashMap<String, SoundEffectAsset> audioResources = new HashMap<>();
|
|
||||||
private final SnowflakeGenerator idGenerator = new SnowflakeGenerator(); // TODO: Don't create a new generator
|
|
||||||
private final AudioVolumeManager audioVolumeManager = new AudioVolumeManager(this);
|
|
||||||
|
|
||||||
|
|
||||||
public SoundManager() {
|
|
||||||
// Get all Audio Resources and add them to a list.
|
|
||||||
for (ResourceMeta<SoundEffectAsset> asset : ResourceManager.getAllOfType(SoundEffectAsset.class)) {
|
|
||||||
try {
|
|
||||||
this.addAudioResource(asset);
|
|
||||||
} catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new EventFlow()
|
|
||||||
.listen(this::handlePlaySound)
|
|
||||||
.listen(this::handleStopSound)
|
|
||||||
.listen(this::handleMusicStart)
|
|
||||||
.listen(AudioEvents.ClickButton.class, _ -> {
|
|
||||||
try {
|
|
||||||
playSound("medium-button-click.wav", false);
|
|
||||||
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handlePlaySound(AudioEvents.PlayEffect event) {
|
|
||||||
try {
|
|
||||||
this.playSound(event.fileName(), event.loop());
|
|
||||||
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleStopSound(AudioEvents.StopEffect event) {
|
|
||||||
this.stopSound(event.clipId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAudioResource(ResourceMeta<SoundEffectAsset> audioAsset)
|
|
||||||
throws IOException, UnsupportedAudioFileException, LineUnavailableException {
|
|
||||||
|
|
||||||
this.audioResources.put(audioAsset.getName(), audioAsset.getResource());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleMusicStart(AudioEvents.StartBackgroundMusic e) {
|
|
||||||
backgroundMusicQueue.clear();
|
|
||||||
List<MusicAsset> shuffledArray = new ArrayList<>(ResourceManager.getAllOfType(MusicAsset.class)
|
|
||||||
.stream()
|
|
||||||
.map(ResourceMeta::getResource)
|
|
||||||
.toList());
|
|
||||||
Collections.shuffle(shuffledArray);
|
|
||||||
backgroundMusicQueue.addAll(
|
|
||||||
shuffledArray
|
|
||||||
);
|
|
||||||
backgroundMusicPlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addBackgroundMusic(MusicAsset musicAsset) {
|
|
||||||
backgroundMusicQueue.add(musicAsset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void backgroundMusicPlayer() {
|
|
||||||
MusicAsset ma = backgroundMusicQueue.poll();
|
|
||||||
if (ma == null) return;
|
|
||||||
|
|
||||||
MediaPlayer mediaPlayer = new MediaPlayer(ma.getMedia());
|
|
||||||
|
|
||||||
mediaPlayer.setOnEndOfMedia(() -> {
|
|
||||||
addBackgroundMusic(ma);
|
|
||||||
activeMusic.remove(mediaPlayer);
|
|
||||||
mediaPlayer.dispose();
|
|
||||||
ma.unload();
|
|
||||||
backgroundMusicPlayer(); // play next
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.setOnStopped(() -> {
|
|
||||||
addBackgroundMusic(ma);
|
|
||||||
activeMusic.remove(mediaPlayer);
|
|
||||||
ma.unload();
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.setOnError(() -> {
|
|
||||||
addBackgroundMusic(ma);
|
|
||||||
activeMusic.remove(mediaPlayer);
|
|
||||||
ma.unload();
|
|
||||||
});
|
|
||||||
|
|
||||||
audioVolumeManager.updateMusicVolume(mediaPlayer);
|
|
||||||
mediaPlayer.play();
|
|
||||||
activeMusic.add(mediaPlayer);
|
|
||||||
logger.info("Playing background music: {}", ma.getFile().getName());
|
|
||||||
logger.info("Background music next in line: {}", backgroundMusicQueue.peek().getFile().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private long playSound(String audioFileName, boolean loop) throws UnsupportedAudioFileException, LineUnavailableException, IOException {
|
|
||||||
SoundEffectAsset asset = audioResources.get(audioFileName);
|
|
||||||
|
|
||||||
// Return -1 which indicates resource wasn't available
|
|
||||||
if (asset == null) {
|
|
||||||
logger.warn("Unable to load audio asset: {}", audioFileName);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a new clip from resource
|
|
||||||
Clip clip = asset.getNewClip();
|
|
||||||
|
|
||||||
// Set volume of clip
|
|
||||||
audioVolumeManager.updateSoundEffectVolume(clip);
|
|
||||||
|
|
||||||
// If supposed to loop make it loop, else just start it once
|
|
||||||
if (loop) {
|
|
||||||
clip.loop(Clip.LOOP_CONTINUOUSLY);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clip.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Playing sound: {}", asset.getFile().getName());
|
|
||||||
|
|
||||||
// Generate id for clip
|
|
||||||
long clipId = idGenerator.nextId();
|
|
||||||
|
|
||||||
// store it so we can stop it later
|
|
||||||
activeSoundEffects.put(clipId, clip); // TODO: Do on snowflake for specific sound to stop
|
|
||||||
|
|
||||||
// remove when finished (only for non-looping sounds)
|
|
||||||
clip.addLineListener(event -> {
|
|
||||||
if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) {
|
|
||||||
activeSoundEffects.remove(clipId);
|
|
||||||
clip.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return id so it can be stopped
|
|
||||||
return clipId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopSound(long clipId) {
|
|
||||||
Clip clip = activeSoundEffects.get(clipId);
|
|
||||||
|
|
||||||
if (clip == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clip.stop();
|
|
||||||
clip.close();
|
|
||||||
activeSoundEffects.remove(clipId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopAllSounds() {
|
|
||||||
for (Clip clip : activeSoundEffects.values()) {
|
|
||||||
clip.stop();
|
|
||||||
clip.close();
|
|
||||||
}
|
|
||||||
activeSoundEffects.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Long, Clip> getActiveSoundEffects(){ return this.activeSoundEffects; }
|
|
||||||
|
|
||||||
public List<MediaPlayer> getActiveMusic() { return activeMusic; }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package org.toop.framework.audio;
|
||||||
|
|
||||||
|
import org.toop.framework.audio.interfaces.AudioManager;
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing different categories of audio volume in the application.
|
||||||
|
* <p>
|
||||||
|
* Each volume type maintains its own volume level and a list of {@link AudioManager}s
|
||||||
|
* that manage audio resources of that type. The enum provides methods to set, get,
|
||||||
|
* and propagate volume changes, including master volume adjustments that automatically
|
||||||
|
* update dependent volume types (FX and MUSIC).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>Volume types:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #MASTERVOLUME}: The global/master volume that scales all other volume types.</li>
|
||||||
|
* <li>{@link #FX}: Volume for sound effects, scaled by the master volume.</li>
|
||||||
|
* <li>{@link #MUSIC}: Volume for music tracks, scaled by the master volume.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Key features:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Thread-safe management of audio managers using {@link CopyOnWriteArrayList}.</li>
|
||||||
|
* <li>Automatic propagation of master volume changes to dependent volume types.</li>
|
||||||
|
* <li>Clamping volume values between 0.0 and 1.0 to ensure valid audio levels.</li>
|
||||||
|
* <li>Dynamic registration and removal of audio managers for each volume type.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Example usage:</p>
|
||||||
|
* <pre>{@code
|
||||||
|
* // Add a music manager to the MUSIC volume type
|
||||||
|
* VolumeControl.MUSIC.addManager(musicManager);
|
||||||
|
*
|
||||||
|
* // Set master volume to 80%
|
||||||
|
* VolumeControl.MASTERVOLUME.setVolume(0.8, 0);
|
||||||
|
*
|
||||||
|
* // Set FX volume to 50% of master
|
||||||
|
* VolumeControl.FX.setVolume(0.5, VolumeControl.MASTERVOLUME.getVolume());
|
||||||
|
*
|
||||||
|
* // Retrieve current music volume
|
||||||
|
* double musicVol = VolumeControl.MUSIC.getVolume();
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public enum VolumeControl {
|
||||||
|
MASTERVOLUME(),
|
||||||
|
FX(),
|
||||||
|
MUSIC();
|
||||||
|
|
||||||
|
@SuppressWarnings("ImmutableEnumChecker")
|
||||||
|
private final List<AudioManager<? extends AudioResource>> managers = new CopyOnWriteArrayList<>();
|
||||||
|
@SuppressWarnings("ImmutableEnumChecker")
|
||||||
|
private double volume = 1.0;
|
||||||
|
@SuppressWarnings("ImmutableEnumChecker")
|
||||||
|
private double masterVolume = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume for this volume type.
|
||||||
|
* <p>
|
||||||
|
* If this type is {@link #MASTERVOLUME}, all dependent volume types
|
||||||
|
* (FX, MUSIC, etc.) are automatically updated to reflect the new master volume.
|
||||||
|
* Otherwise, the volume is scaled by the provided master volume.
|
||||||
|
*
|
||||||
|
* @param newVolume the new volume level (0.0 to 1.0)
|
||||||
|
* @param currentMasterVolume the current master volume for scaling non-master types
|
||||||
|
*/
|
||||||
|
public void setVolume(double newVolume, double currentMasterVolume) {
|
||||||
|
this.volume = clamp(newVolume);
|
||||||
|
|
||||||
|
if (this == MASTERVOLUME) {
|
||||||
|
for (VolumeControl type : VolumeControl.values()) {
|
||||||
|
if (type != MASTERVOLUME) {
|
||||||
|
type.masterVolume = this.volume;
|
||||||
|
type.broadcastVolume(type.computeEffectiveVolume());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.masterVolume = clamp(currentMasterVolume);
|
||||||
|
broadcastVolume(computeEffectiveVolume());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the effective volume for this type, taking into account
|
||||||
|
* the master volume if this is not {@link #MASTERVOLUME}.
|
||||||
|
*
|
||||||
|
* @return the effective volume (0.0 to 1.0)
|
||||||
|
*/
|
||||||
|
private double computeEffectiveVolume() {
|
||||||
|
return (this == MASTERVOLUME) ? volume : volume * masterVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all registered audio managers with the given effective volume.
|
||||||
|
*
|
||||||
|
* @param effectiveVolume the volume to apply to all active audio resources
|
||||||
|
*/
|
||||||
|
private void broadcastVolume(double effectiveVolume) {
|
||||||
|
managers.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(manager -> manager.getActiveAudio()
|
||||||
|
.forEach(aud -> aud.updateVolume(effectiveVolume)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamps a volume value to the valid range [0.0, 1.0].
|
||||||
|
*
|
||||||
|
* @param vol the volume to clamp
|
||||||
|
* @return the clamped volume
|
||||||
|
*/
|
||||||
|
private double clamp(double vol) {
|
||||||
|
return Math.max(0, Math.min(vol, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current volume for this type.
|
||||||
|
*
|
||||||
|
* @return the current volume (0.0 to 1.0)
|
||||||
|
*/
|
||||||
|
public double getVolume() {
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an {@link AudioManager} to this volume type.
|
||||||
|
* <p>
|
||||||
|
* Duplicate managers are ignored. Managers will receive volume updates
|
||||||
|
* when this type's volume changes.
|
||||||
|
*
|
||||||
|
* @param manager the audio manager to register
|
||||||
|
*/
|
||||||
|
public void addManager(AudioManager<? extends AudioResource> manager) {
|
||||||
|
if (manager != null && !managers.contains(manager)) {
|
||||||
|
managers.add(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a previously registered {@link AudioManager} from this type.
|
||||||
|
*
|
||||||
|
* @param manager the audio manager to remove
|
||||||
|
*/
|
||||||
|
public void removeManager(AudioManager<? extends AudioResource> manager) {
|
||||||
|
if (manager != null) {
|
||||||
|
managers.remove(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unmodifiable view of all registered audio managers for this type.
|
||||||
|
*
|
||||||
|
* @return a list of registered audio managers
|
||||||
|
*/
|
||||||
|
public List<AudioManager<? extends AudioResource>> getManagers() {
|
||||||
|
return Collections.unmodifiableList(managers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,94 +1,32 @@
|
|||||||
package org.toop.framework.audio.events;
|
package org.toop.framework.audio.events;
|
||||||
|
|
||||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
import org.toop.framework.audio.VolumeControl;
|
||||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
import org.toop.framework.eventbus.events.*;
|
||||||
import org.toop.framework.eventbus.events.EventsBase;
|
import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
|
||||||
|
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class AudioEvents extends EventsBase {
|
public class AudioEvents extends EventsBase {
|
||||||
/** Starts playing a sound. */
|
/** Stops the audio manager. */
|
||||||
public record PlayEffect(String fileName, boolean loop)
|
public record StopAudioManager() implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
public record StopEffect(long clipId) implements EventWithoutSnowflake {}
|
/** Start playing a sound effect. */
|
||||||
|
public record PlayEffect(String fileName, boolean loop) implements GenericEvent {}
|
||||||
|
|
||||||
public record StartBackgroundMusic() implements EventWithoutSnowflake {}
|
/** Stop playing a sound effect. */
|
||||||
public record ChangeVolume(double newVolume) implements EventWithoutSnowflake {}
|
public record StopEffect(String fileName) implements GenericEvent {}
|
||||||
public record ChangeFxVolume(double newVolume) implements EventWithoutSnowflake {}
|
|
||||||
public record ChangeMusicVolume(double newVolume) implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
public record GetCurrentVolume(long snowflakeId) implements EventWithSnowflake {
|
/** Start background music. */
|
||||||
@Override
|
public record StartBackgroundMusic() implements GenericEvent {}
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/** Change volume, choose type with {@link VolumeControl}. */
|
||||||
public long eventSnowflake() {
|
public record ChangeVolume(double newVolume, VolumeControl controlType) implements GenericEvent {}
|
||||||
return snowflakeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public record GetCurrentVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake {
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/** Requests the desired volume by selecting it with {@link VolumeControl}. */
|
||||||
public long eventSnowflake() {
|
public record GetVolume(VolumeControl controlType, long identifier) implements UniqueEvent {}
|
||||||
return snowflakeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record GetCurrentFxVolume(long snowflakeId) implements EventWithSnowflake {
|
/** Response to GetVolume. */
|
||||||
@Override
|
public record GetVolumeResponse(double currentVolume, long identifier) implements ResponseToUniqueEvent {}
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/** Plays the predetermined sound for pressing a button. */
|
||||||
public long eventSnowflake() {
|
public record ClickButton() implements GenericEvent {}
|
||||||
return this.snowflakeId;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record GetCurrentMusicVolume(long snowflakeId) implements EventWithSnowflake {
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long eventSnowflake() {
|
|
||||||
return this.snowflakeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record GetCurrentFxVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake {
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long eventSnowflake() {
|
|
||||||
return this.snowflakeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record GetCurrentMusicVolumeResponse(double currentVolume, long snowflakeId) implements EventWithSnowflake {
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long eventSnowflake() {
|
|
||||||
return this.snowflakeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ClickButton() implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.toop.framework.audio.interfaces;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface AudioManager<T> {
|
||||||
|
Collection<T> getActiveAudio();
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.toop.framework.audio.interfaces;
|
||||||
|
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
|
||||||
|
public interface MusicManager<T extends AudioResource> extends AudioManager<T> {
|
||||||
|
void play();
|
||||||
|
void stop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.toop.framework.audio.interfaces;
|
||||||
|
|
||||||
|
import org.toop.framework.resource.resources.SoundEffectAsset;
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
|
||||||
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface SoundEffectManager<T extends AudioResource> extends AudioManager<T> {
|
||||||
|
void play(String name, boolean loop);
|
||||||
|
void stop(String name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.toop.framework.audio.interfaces;
|
||||||
|
|
||||||
|
import org.toop.framework.audio.VolumeControl;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for managing audio volumes in the application.
|
||||||
|
* <p>
|
||||||
|
* Implementations of this interface are responsible for controlling the volume levels
|
||||||
|
* of different categories of audio (e.g., master volume, music, sound effects) and
|
||||||
|
* updating the associated audio managers or resources accordingly.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>Typical responsibilities include:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Setting the volume for a specific category (master, music, FX).</li>
|
||||||
|
* <li>Retrieving the current volume of a category.</li>
|
||||||
|
* <li>Ensuring that changes in master volume propagate to dependent audio categories.</li>
|
||||||
|
* <li>Interfacing with {@link org.toop.framework.audio.interfaces.AudioManager} to update active audio resources.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Example usage:</p>
|
||||||
|
* <pre>{@code
|
||||||
|
* VolumeManager volumeManager = ...;
|
||||||
|
* // Set master volume to 80%
|
||||||
|
* volumeManager.setVolume(0.8, VolumeControl.MASTERVOLUME);
|
||||||
|
*
|
||||||
|
* // Set music volume to 50% of master
|
||||||
|
* volumeManager.setVolume(0.5, VolumeControl.MUSIC);
|
||||||
|
*
|
||||||
|
* // Retrieve current FX volume
|
||||||
|
* double fxVolume = volumeManager.getVolume(VolumeControl.FX);
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public interface VolumeManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Sets the volume to for the specified {@link VolumeControl}.
|
||||||
|
*
|
||||||
|
* @param newVolume The volume to be set to.
|
||||||
|
* @param type The type of volume to change.
|
||||||
|
*/
|
||||||
|
void setVolume(double newVolume, VolumeControl type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current volume for the specified {@link VolumeControl}.
|
||||||
|
*
|
||||||
|
* @param type the type of volume to get.
|
||||||
|
* @return The volume as a {@link Double}
|
||||||
|
*/
|
||||||
|
double getVolume(VolumeControl type);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.toop.framework.dispatch;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import org.toop.framework.dispatch.interfaces.Dispatcher;
|
||||||
|
|
||||||
|
public class JavaFXDispatcher implements Dispatcher {
|
||||||
|
@Override
|
||||||
|
public void run(Runnable task) {
|
||||||
|
Platform.runLater(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package org.toop.framework.dispatch.interfaces;
|
||||||
|
|
||||||
|
public interface Dispatcher {
|
||||||
|
void run(Runnable task);
|
||||||
|
}
|
||||||
@@ -11,13 +11,14 @@ import java.util.function.Consumer;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import org.toop.framework.SnowflakeGenerator;
|
import org.toop.framework.SnowflakeGenerator;
|
||||||
import org.toop.framework.eventbus.events.EventType;
|
import org.toop.framework.eventbus.events.EventType;
|
||||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
|
||||||
|
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EventFlow is a utility class for creating, posting, and optionally subscribing to events in a
|
* EventFlow is a utility class for creating, posting, and optionally subscribing to events in a
|
||||||
* type-safe and chainable manner. It is designed to work with the {@link GlobalEventBus}.
|
* type-safe and chainable manner. It is designed to work with the {@link GlobalEventBus}.
|
||||||
*
|
*
|
||||||
* <p>This class supports automatic UUID assignment for {@link EventWithSnowflake} events, and
|
* <p>This class supports automatic UUID assignment for {@link UniqueEvent} events, and
|
||||||
* allows filtering subscribers so they only respond to events with a specific UUID. All
|
* allows filtering subscribers so they only respond to events with a specific UUID. All
|
||||||
* subscription methods are chainable, and you can configure automatic unsubscription after an event
|
* subscription methods are chainable, and you can configure automatic unsubscription after an event
|
||||||
* has been successfully handled.
|
* has been successfully handled.
|
||||||
@@ -30,7 +31,7 @@ public class EventFlow {
|
|||||||
/** Cache of constructor handles for event classes to avoid repeated reflection lookups. */
|
/** Cache of constructor handles for event classes to avoid repeated reflection lookups. */
|
||||||
private static final Map<Class<?>, MethodHandle> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>();
|
private static final Map<Class<?>, MethodHandle> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** Automatically assigned UUID for {@link EventWithSnowflake} events. */
|
/** Automatically assigned UUID for {@link UniqueEvent} events. */
|
||||||
private long eventSnowflake = -1;
|
private long eventSnowflake = -1;
|
||||||
|
|
||||||
/** The event instance created by this publisher. */
|
/** The event instance created by this publisher. */
|
||||||
@@ -40,7 +41,7 @@ public class EventFlow {
|
|||||||
private final List<ListenerHandler> listeners = new ArrayList<>();
|
private final List<ListenerHandler> listeners = new ArrayList<>();
|
||||||
|
|
||||||
/** Holds the results returned from the subscribed event, if any. */
|
/** Holds the results returned from the subscribed event, if any. */
|
||||||
private Map<String, Object> result = null;
|
private Map<String, ?> result = null;
|
||||||
|
|
||||||
/** Empty constructor (event must be added via {@link #addPostEvent(Class, Object...)}). */
|
/** Empty constructor (event must be added via {@link #addPostEvent(Class, Object...)}). */
|
||||||
public EventFlow() {}
|
public EventFlow() {}
|
||||||
@@ -60,7 +61,7 @@ public class EventFlow {
|
|||||||
// Keep the old class+args version if needed
|
// Keep the old class+args version if needed
|
||||||
public <T extends EventType> EventFlow addPostEvent(Class<T> eventClass, Object... args) {
|
public <T extends EventType> EventFlow addPostEvent(Class<T> eventClass, Object... args) {
|
||||||
try {
|
try {
|
||||||
boolean isUuidEvent = EventWithSnowflake.class.isAssignableFrom(eventClass);
|
boolean isUuidEvent = UniqueEvent.class.isAssignableFrom(eventClass);
|
||||||
|
|
||||||
MethodHandle ctorHandle =
|
MethodHandle ctorHandle =
|
||||||
CONSTRUCTOR_CACHE.computeIfAbsent(
|
CONSTRUCTOR_CACHE.computeIfAbsent(
|
||||||
@@ -81,7 +82,7 @@ public class EventFlow {
|
|||||||
int expectedParamCount = ctorHandle.type().parameterCount();
|
int expectedParamCount = ctorHandle.type().parameterCount();
|
||||||
|
|
||||||
if (isUuidEvent && args.length < expectedParamCount) {
|
if (isUuidEvent && args.length < expectedParamCount) {
|
||||||
this.eventSnowflake = new SnowflakeGenerator().nextId();
|
this.eventSnowflake = SnowflakeGenerator.nextId();
|
||||||
finalArgs = new Object[args.length + 1];
|
finalArgs = new Object[args.length + 1];
|
||||||
System.arraycopy(args, 0, finalArgs, 0, args.length);
|
System.arraycopy(args, 0, finalArgs, 0, args.length);
|
||||||
finalArgs[args.length] = this.eventSnowflake;
|
finalArgs[args.length] = this.eventSnowflake;
|
||||||
@@ -100,13 +101,8 @@ public class EventFlow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public EventFlow addSnowflake() {
|
|
||||||
// this.eventSnowflake = new SnowflakeGenerator(1).nextId();
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */
|
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */
|
||||||
public <TT extends EventWithSnowflake> EventFlow onResponse(
|
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(
|
||||||
Class<TT> eventClass, Consumer<TT> action, boolean unsubscribeAfterSuccess) {
|
Class<TT> eventClass, Consumer<TT> action, boolean unsubscribeAfterSuccess) {
|
||||||
ListenerHandler[] listenerHolder = new ListenerHandler[1];
|
ListenerHandler[] listenerHolder = new ListenerHandler[1];
|
||||||
listenerHolder[0] =
|
listenerHolder[0] =
|
||||||
@@ -114,7 +110,7 @@ public class EventFlow {
|
|||||||
GlobalEventBus.subscribe(
|
GlobalEventBus.subscribe(
|
||||||
eventClass,
|
eventClass,
|
||||||
event -> {
|
event -> {
|
||||||
if (event.eventSnowflake() != this.eventSnowflake) return;
|
if (event.getIdentifier() != this.eventSnowflake) return;
|
||||||
|
|
||||||
action.accept(event);
|
action.accept(event);
|
||||||
|
|
||||||
@@ -130,22 +126,21 @@ public class EventFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */
|
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */
|
||||||
public <TT extends EventWithSnowflake> EventFlow onResponse(
|
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Class<TT> eventClass, Consumer<TT> action) {
|
||||||
Class<TT> eventClass, Consumer<TT> action) {
|
|
||||||
return this.onResponse(eventClass, action, true);
|
return this.onResponse(eventClass, action, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Subscribe by ID without explicit class. */
|
/** Subscribe by ID without explicit class. */
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <TT extends EventWithSnowflake> EventFlow onResponse(
|
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(
|
||||||
Consumer<TT> action, boolean unsubscribeAfterSuccess) {
|
Consumer<TT> action, boolean unsubscribeAfterSuccess) {
|
||||||
ListenerHandler[] listenerHolder = new ListenerHandler[1];
|
ListenerHandler[] listenerHolder = new ListenerHandler[1];
|
||||||
listenerHolder[0] =
|
listenerHolder[0] =
|
||||||
new ListenerHandler(
|
new ListenerHandler(
|
||||||
GlobalEventBus.subscribe(
|
GlobalEventBus.subscribe(
|
||||||
event -> {
|
event -> {
|
||||||
if (!(event instanceof EventWithSnowflake uuidEvent)) return;
|
if (!(event instanceof UniqueEvent uuidEvent)) return;
|
||||||
if (uuidEvent.eventSnowflake() == this.eventSnowflake) {
|
if (uuidEvent.getIdentifier() == this.eventSnowflake) {
|
||||||
try {
|
try {
|
||||||
TT typedEvent = (TT) uuidEvent;
|
TT typedEvent = (TT) uuidEvent;
|
||||||
action.accept(typedEvent);
|
action.accept(typedEvent);
|
||||||
@@ -159,7 +154,7 @@ public class EventFlow {
|
|||||||
throw new ClassCastException(
|
throw new ClassCastException(
|
||||||
"Cannot cast "
|
"Cannot cast "
|
||||||
+ event.getClass().getName()
|
+ event.getClass().getName()
|
||||||
+ " to EventWithSnowflake");
|
+ " to UniqueEvent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -167,7 +162,7 @@ public class EventFlow {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <TT extends EventWithSnowflake> EventFlow onResponse(Consumer<TT> action) {
|
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Consumer<TT> action) {
|
||||||
return this.onResponse(action, true);
|
return this.onResponse(action, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +209,7 @@ public class EventFlow {
|
|||||||
throw new ClassCastException(
|
throw new ClassCastException(
|
||||||
"Cannot cast "
|
"Cannot cast "
|
||||||
+ event.getClass().getName()
|
+ event.getClass().getName()
|
||||||
+ " to EventWithSnowflake");
|
+ " to UniqueEvent");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this.listeners.add(listenerHolder[0]);
|
this.listeners.add(listenerHolder[0]);
|
||||||
@@ -237,7 +232,13 @@ public class EventFlow {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> getResult() {
|
private void clean() {
|
||||||
|
this.listeners.clear();
|
||||||
|
this.event = null;
|
||||||
|
this.result = null;
|
||||||
|
} // TODO
|
||||||
|
|
||||||
|
public Map<String, ?> getResult() {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.toop.framework.eventbus.events.EventType;
|
import org.toop.framework.eventbus.events.EventType;
|
||||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GlobalEventBus backed by the LMAX Disruptor for ultra-low latency, high-throughput event
|
* GlobalEventBus backed by the LMAX Disruptor for ultra-low latency, high-throughput event
|
||||||
@@ -21,7 +21,7 @@ public final class GlobalEventBus {
|
|||||||
|
|
||||||
/** Map of event class to Snowflake-ID-specific listeners. */
|
/** Map of event class to Snowflake-ID-specific listeners. */
|
||||||
private static final Map<
|
private static final Map<
|
||||||
Class<?>, ConcurrentHashMap<Long, Consumer<? extends EventWithSnowflake>>>
|
Class<?>, ConcurrentHashMap<Long, Consumer<? extends UniqueEvent>>>
|
||||||
UUID_LISTENERS = new ConcurrentHashMap<>();
|
UUID_LISTENERS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** Disruptor ring buffer size (must be power of two). */
|
/** Disruptor ring buffer size (must be power of two). */
|
||||||
@@ -90,7 +90,7 @@ public final class GlobalEventBus {
|
|||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends EventWithSnowflake> void subscribeById(
|
public static <T extends UniqueEvent> void subscribeById(
|
||||||
Class<T> eventClass, long eventId, Consumer<T> listener) {
|
Class<T> eventClass, long eventId, Consumer<T> listener) {
|
||||||
UUID_LISTENERS
|
UUID_LISTENERS
|
||||||
.computeIfAbsent(eventClass, _ -> new ConcurrentHashMap<>())
|
.computeIfAbsent(eventClass, _ -> new ConcurrentHashMap<>())
|
||||||
@@ -101,9 +101,9 @@ public final class GlobalEventBus {
|
|||||||
LISTENERS.values().forEach(list -> list.remove(listener));
|
LISTENERS.values().forEach(list -> list.remove(listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends EventWithSnowflake> void unsubscribeById(
|
public static <T extends UniqueEvent> void unsubscribeById(
|
||||||
Class<T> eventClass, long eventId) {
|
Class<T> eventClass, long eventId) {
|
||||||
Map<Long, Consumer<? extends EventWithSnowflake>> map = UUID_LISTENERS.get(eventClass);
|
Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(eventClass);
|
||||||
if (map != null) map.remove(eventId);
|
if (map != null) map.remove(eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,11 +152,11 @@ public final class GlobalEventBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// snowflake listeners
|
// snowflake listeners
|
||||||
if (event instanceof EventWithSnowflake snowflakeEvent) {
|
if (event instanceof UniqueEvent snowflakeEvent) {
|
||||||
Map<Long, Consumer<? extends EventWithSnowflake>> map = UUID_LISTENERS.get(clazz);
|
Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(clazz);
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
Consumer<EventWithSnowflake> listener =
|
Consumer<UniqueEvent> listener =
|
||||||
(Consumer<EventWithSnowflake>) map.remove(snowflakeEvent.eventSnowflake());
|
(Consumer<UniqueEvent>) map.remove(snowflakeEvent.getIdentifier());
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
try {
|
try {
|
||||||
listener.accept(snowflakeEvent);
|
listener.accept(snowflakeEvent);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.toop.framework.eventbus;
|
package org.toop.framework.eventbus;
|
||||||
|
|
||||||
public class ListenerHandler {
|
public class ListenerHandler {
|
||||||
private Object listener = null;
|
private Object listener;
|
||||||
|
|
||||||
// private boolean unsubscribeAfterSuccess = true;
|
// private boolean unsubscribeAfterSuccess = true;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package org.toop.framework.eventbus.events;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public interface EventWithSnowflake extends EventType {
|
|
||||||
Map<String, Object> result();
|
|
||||||
long eventSnowflake();
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package org.toop.framework.eventbus.events;
|
|
||||||
|
|
||||||
public interface EventWithoutSnowflake extends EventType {}
|
|
||||||
@@ -1,69 +1,4 @@
|
|||||||
package org.toop.framework.eventbus.events;
|
package org.toop.framework.eventbus.events;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/** Events that are used in the GlobalEventBus class. */
|
/** Events that are used in the GlobalEventBus class. */
|
||||||
public class EventsBase {
|
public class EventsBase {}
|
||||||
|
|
||||||
/**
|
|
||||||
* WIP, DO NOT USE!
|
|
||||||
*
|
|
||||||
* @param eventName
|
|
||||||
* @param args
|
|
||||||
* @return
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static Object get(String eventName, Object... args) throws Exception {
|
|
||||||
Class<?> clazz = Class.forName("org.toop.framework.eventbus.events.Events$ServerEvents$" + eventName);
|
|
||||||
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
|
|
||||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
|
||||||
return constructor.newInstance(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WIP, DO NOT USE!
|
|
||||||
*
|
|
||||||
* @param eventCategory
|
|
||||||
* @param eventName
|
|
||||||
* @param args
|
|
||||||
* @return
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static Object get(String eventCategory, String eventName, Object... args)
|
|
||||||
throws Exception {
|
|
||||||
Class<?> clazz =
|
|
||||||
Class.forName("org.toop.framework.eventbus.events.Events$" + eventCategory + "$" + eventName);
|
|
||||||
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
|
|
||||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
|
||||||
return constructor.newInstance(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WIP, DO NOT USE!
|
|
||||||
*
|
|
||||||
* @param eventName
|
|
||||||
* @param args
|
|
||||||
* @return
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static Object get2(String eventName, Object... args) throws Exception {
|
|
||||||
// Fully qualified class name
|
|
||||||
String className = "org.toop.server.backend.Events$ServerEvents$" + eventName;
|
|
||||||
|
|
||||||
// Load the class
|
|
||||||
Class<?> clazz = Class.forName(className);
|
|
||||||
|
|
||||||
// Build array of argument types
|
|
||||||
Class<?>[] paramTypes = new Class[args.length];
|
|
||||||
for (int i = 0; i < args.length; i++) {
|
|
||||||
paramTypes[i] = args[i].getClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the constructor
|
|
||||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
|
||||||
|
|
||||||
// Create a new instance
|
|
||||||
return constructor.newInstance(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package org.toop.framework.eventbus.events;
|
||||||
|
|
||||||
|
public interface GenericEvent extends EventType {}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.toop.framework.eventbus.events;
|
||||||
|
|
||||||
|
import java.lang.reflect.RecordComponent;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface ResponseToUniqueEvent extends UniqueEvent {
|
||||||
|
default Map<String, Object> result() {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
try {
|
||||||
|
for (RecordComponent component : this.getClass().getRecordComponents()) {
|
||||||
|
Object value = component.getAccessor().invoke(this);
|
||||||
|
map.put(component.getName(), value);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to build result map via reflection", e);
|
||||||
|
}
|
||||||
|
return Map.copyOf(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.toop.framework.eventbus.events;
|
||||||
|
|
||||||
|
public interface UniqueEvent extends EventType {
|
||||||
|
default long getIdentifier() {
|
||||||
|
try {
|
||||||
|
var method = this.getClass().getMethod("identifier");
|
||||||
|
return (long) method.invoke(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("No identifier accessor found", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,9 +84,9 @@ public class NetworkingClient {
|
|||||||
if (isChannelActive()) {
|
if (isChannelActive()) {
|
||||||
this.channel.writeAndFlush(msg);
|
this.channel.writeAndFlush(msg);
|
||||||
logger.info(
|
logger.info(
|
||||||
"Connection {} sent message: '{}'", this.channel.remoteAddress(), literalMsg);
|
"Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Cannot send message: '{}', connection inactive.", literalMsg);
|
logger.warn("Cannot send message: '{}', connection inactive. ", literalMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ public class NetworkingClientManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long startClientRequest(String ip, int port) {
|
long startClientRequest(String ip, int port) {
|
||||||
long connectionId = new SnowflakeGenerator().nextId(); // TODO: Maybe use the one generated
|
long connectionId = SnowflakeGenerator.nextId();
|
||||||
try { // With EventFlow
|
try {
|
||||||
NetworkingClient client =
|
NetworkingClient client =
|
||||||
new NetworkingClient(
|
new NetworkingClient(
|
||||||
() -> new NetworkingGameClientHandler(connectionId),
|
() -> new NetworkingGameClientHandler(connectionId),
|
||||||
@@ -81,19 +81,13 @@ public class NetworkingClientManager {
|
|||||||
void handleStartClient(NetworkEvents.StartClient event) {
|
void handleStartClient(NetworkEvents.StartClient event) {
|
||||||
long id = this.startClientRequest(event.ip(), event.port());
|
long id = this.startClientRequest(event.ip(), event.port());
|
||||||
new Thread(
|
new Thread(
|
||||||
() -> {
|
() ->
|
||||||
try {
|
|
||||||
Thread.sleep(100); // TODO: Is this a good idea?
|
|
||||||
new EventFlow()
|
new EventFlow()
|
||||||
.addPostEvent(
|
.addPostEvent(
|
||||||
NetworkEvents.StartClientResponse.class,
|
NetworkEvents.StartClientResponse.class,
|
||||||
id,
|
id,
|
||||||
event.eventSnowflake())
|
event.eventSnowflake())
|
||||||
.asyncPostEvent();
|
.asyncPostEvent())
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +179,7 @@ public class NetworkingClientManager {
|
|||||||
|
|
||||||
void handleCloseClient(NetworkEvents.CloseClient event) {
|
void handleCloseClient(NetworkEvents.CloseClient event) {
|
||||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||||
client.closeConnection(); // TODO: Check if not blocking, what if error, mb not remove?
|
client.closeConnection();
|
||||||
this.networkClients.remove(event.clientId());
|
this.networkClients.remove(event.clientId());
|
||||||
logger.info("Client {} closed successfully.", event.clientId());
|
logger.info("Client {} closed successfully.", event.clientId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
|||||||
gameWinConditionHandler(recSrvRemoved);
|
gameWinConditionHandler(recSrvRemoved);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
return;
|
// return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -93,10 +93,10 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
|||||||
helpHandler(recSrvRemoved);
|
helpHandler(recSrvRemoved);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
return;
|
// return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return; // TODO: Should be an error.
|
logger.error("Could not parse: {}", rec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,6 +119,7 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void gameWinConditionHandler(String rec) {
|
private void gameWinConditionHandler(String rec) {
|
||||||
|
@SuppressWarnings("StreamToString")
|
||||||
String condition =
|
String condition =
|
||||||
Pattern.compile("\\b(win|draw|lose)\\b", Pattern.CASE_INSENSITIVE)
|
Pattern.compile("\\b(win|draw|lose)\\b", Pattern.CASE_INSENSITIVE)
|
||||||
.matcher(rec)
|
.matcher(rec)
|
||||||
@@ -136,7 +137,7 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
|||||||
try {
|
try {
|
||||||
String[] msg =
|
String[] msg =
|
||||||
Pattern.compile(
|
Pattern.compile(
|
||||||
"(?:CHALLENGER|GAMETYPE|CHALLENGENUMBER):\\s*\"?(.*?)\"?\\s*(?:,|})")
|
"(?:CHALLENGER|GAMETYPE|CHALLENGENUMBER):\\s*\"?(.*?)\"?\\s*[,}]")
|
||||||
.matcher(rec)
|
.matcher(rec)
|
||||||
.results()
|
.results()
|
||||||
.map(m -> m.group().trim())
|
.map(m -> m.group().trim())
|
||||||
@@ -180,6 +181,7 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void gameYourTurnHandler(String rec) {
|
private void gameYourTurnHandler(String rec) {
|
||||||
|
@SuppressWarnings("StreamToString")
|
||||||
String msg =
|
String msg =
|
||||||
Pattern.compile("TURNMESSAGE:\\s*\"([^\"]*)\"")
|
Pattern.compile("TURNMESSAGE:\\s*\"([^\"]*)\"")
|
||||||
.matcher(rec)
|
.matcher(rec)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package org.toop.framework.networking.events;
|
package org.toop.framework.networking.events;
|
||||||
|
|
||||||
import java.lang.reflect.RecordComponent;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import org.toop.framework.eventbus.events.GenericEvent;
|
||||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
|
||||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||||
import org.toop.framework.eventbus.events.EventsBase;
|
import org.toop.framework.eventbus.events.EventsBase;
|
||||||
|
import org.toop.annotations.AutoResponseResult;
|
||||||
import org.toop.framework.networking.NetworkingClient;
|
import org.toop.framework.networking.NetworkingClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,8 +15,8 @@ import org.toop.framework.networking.NetworkingClient;
|
|||||||
* org.toop.framework.eventbus.GlobalEventBus}.
|
* org.toop.framework.eventbus.GlobalEventBus}.
|
||||||
*
|
*
|
||||||
* <p>This class defines all the events that can be posted or listened to in the networking
|
* <p>This class defines all the events that can be posted or listened to in the networking
|
||||||
* subsystem. Events are separated into those with unique IDs (EventWithSnowflake) and those without
|
* subsystem. Events are separated into those with unique IDs (UniqueEvent) and those without
|
||||||
* (EventWithoutSnowflake).
|
* (GenericEvent).
|
||||||
*/
|
*/
|
||||||
public class NetworkEvents extends EventsBase {
|
public class NetworkEvents extends EventsBase {
|
||||||
|
|
||||||
@@ -30,86 +30,76 @@ public class NetworkEvents extends EventsBase {
|
|||||||
* instances.
|
* instances.
|
||||||
*/
|
*/
|
||||||
public record RequestsAllClients(CompletableFuture<List<NetworkingClient>> future)
|
public record RequestsAllClients(CompletableFuture<List<NetworkingClient>> future)
|
||||||
implements EventWithoutSnowflake {}
|
implements GenericEvent {}
|
||||||
|
|
||||||
/** Forces all active client connections to close immediately. */
|
/** Forces all active client connections to close immediately. */
|
||||||
public record ForceCloseAllClients() implements EventWithoutSnowflake {}
|
public record ForceCloseAllClients() implements GenericEvent {}
|
||||||
|
|
||||||
/** Response indicating a challenge was cancelled. */
|
/** Response indicating a challenge was cancelled. */
|
||||||
public record ChallengeCancelledResponse(long clientId, String challengeId)
|
public record ChallengeCancelledResponse(long clientId, String challengeId) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Response indicating a challenge was received. */
|
/** Response indicating a challenge was received. */
|
||||||
public record ChallengeResponse(
|
public record ChallengeResponse(long clientId, String challengerName, String challengeId, String gameType)
|
||||||
long clientId, String challengerName, String challengeId, String gameType)
|
implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Response containing a list of players for a client. */
|
/** Response containing a list of players for a client. */
|
||||||
public record PlayerlistResponse(long clientId, String[] playerlist)
|
public record PlayerlistResponse(long clientId, String[] playerlist) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Response containing a list of games for a client. */
|
/** Response containing a list of games for a client. */
|
||||||
public record GamelistResponse(long clientId, String[] gamelist)
|
public record GamelistResponse(long clientId, String[] gamelist) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Response indicating a game match information for a client. */
|
/** Response indicating a game match information for a client. */
|
||||||
public record GameMatchResponse(
|
public record GameMatchResponse(long clientId, String playerToMove, String gameType, String opponent)
|
||||||
long clientId, String playerToMove, String gameType, String opponent)
|
implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Response indicating the result of a game. */
|
/** Response indicating the result of a game. */
|
||||||
public record GameResultResponse(long clientId, String condition)
|
public record GameResultResponse(long clientId, String condition) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Response indicating a game move occurred. */
|
/** Response indicating a game move occurred. */
|
||||||
public record GameMoveResponse(long clientId, String player, String move, String details)
|
public record GameMoveResponse(long clientId, String player, String move, String details) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Response indicating it is the player's turn. */
|
/** Response indicating it is the player's turn. */
|
||||||
public record YourTurnResponse(long clientId, String message)
|
public record YourTurnResponse(long clientId, String message)
|
||||||
implements EventWithoutSnowflake {}
|
implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to send login credentials for a client. */
|
/** Request to send login credentials for a client. */
|
||||||
public record SendLogin(long clientId, String username) implements EventWithoutSnowflake {}
|
public record SendLogin(long clientId, String username) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to log out a client. */
|
/** Request to log out a client. */
|
||||||
public record SendLogout(long clientId) implements EventWithoutSnowflake {}
|
public record SendLogout(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to retrieve the player list for a client. */
|
/** Request to retrieve the player list for a client. */
|
||||||
public record SendGetPlayerlist(long clientId) implements EventWithoutSnowflake {}
|
public record SendGetPlayerlist(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to retrieve the game list for a client. */
|
/** Request to retrieve the game list for a client. */
|
||||||
public record SendGetGamelist(long clientId) implements EventWithoutSnowflake {}
|
public record SendGetGamelist(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to subscribe a client to a game type. */
|
/** Request to subscribe a client to a game type. */
|
||||||
public record SendSubscribe(long clientId, String gameType) implements EventWithoutSnowflake {}
|
public record SendSubscribe(long clientId, String gameType) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to make a move in a game. */
|
/** Request to make a move in a game. */
|
||||||
public record SendMove(long clientId, short moveNumber) implements EventWithoutSnowflake {}
|
public record SendMove(long clientId, short moveNumber) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to challenge another player. */
|
/** Request to challenge another player. */
|
||||||
public record SendChallenge(long clientId, String usernameToChallenge, String gameType)
|
public record SendChallenge(long clientId, String usernameToChallenge, String gameType) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Request to accept a challenge. */
|
/** Request to accept a challenge. */
|
||||||
public record SendAcceptChallenge(long clientId, int challengeId)
|
public record SendAcceptChallenge(long clientId, int challengeId) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Request to forfeit a game. */
|
/** Request to forfeit a game. */
|
||||||
public record SendForfeit(long clientId) implements EventWithoutSnowflake {}
|
public record SendForfeit(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to send a message from a client. */
|
/** Request to send a message from a client. */
|
||||||
public record SendMessage(long clientId, String message) implements EventWithoutSnowflake {}
|
public record SendMessage(long clientId, String message) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to display help to a client. */
|
/** Request to display help to a client. */
|
||||||
public record SendHelp(long clientId) implements EventWithoutSnowflake {}
|
public record SendHelp(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/** Request to display help for a specific command. */
|
/** Request to display help for a specific command. */
|
||||||
public record SendHelpForCommand(long clientId, String command)
|
public record SendHelpForCommand(long clientId, String command) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** Request to close a specific client connection. */
|
/** Request to close a specific client connection. */
|
||||||
public record CloseClient(long clientId) implements EventWithoutSnowflake {}
|
public record CloseClient(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event to start a new client connection.
|
* Event to start a new client connection.
|
||||||
@@ -120,61 +110,19 @@ public class NetworkEvents extends EventsBase {
|
|||||||
* @param port Server port.
|
* @param port Server port.
|
||||||
* @param eventSnowflake Unique event identifier for correlation.
|
* @param eventSnowflake Unique event identifier for correlation.
|
||||||
*/
|
*/
|
||||||
public record StartClient(String ip, int port, long eventSnowflake)
|
public record StartClient(String ip, int port, long eventSnowflake) implements UniqueEvent {}
|
||||||
implements EventWithSnowflake {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Stream.of(this.getClass().getRecordComponents())
|
|
||||||
.collect(
|
|
||||||
Collectors.toMap(
|
|
||||||
RecordComponent::getName,
|
|
||||||
rc -> {
|
|
||||||
try {
|
|
||||||
return rc.getAccessor().invoke(this);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long eventSnowflake() {
|
|
||||||
return this.eventSnowflake;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response confirming a client was started.
|
* Response confirming a client was started.
|
||||||
*
|
*
|
||||||
* @param clientId The client ID assigned to the new connection.
|
* @param clientId The client ID assigned to the new connection.
|
||||||
* @param eventSnowflake Event ID used for correlation.
|
* @param identifier Event ID used for correlation.
|
||||||
*/
|
*/
|
||||||
public record StartClientResponse(long clientId, long eventSnowflake)
|
@AutoResponseResult
|
||||||
implements EventWithSnowflake {
|
public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {}
|
||||||
@Override
|
|
||||||
public Map<String, Object> result() {
|
|
||||||
return Stream.of(this.getClass().getRecordComponents())
|
|
||||||
.collect(
|
|
||||||
Collectors.toMap(
|
|
||||||
RecordComponent::getName,
|
|
||||||
rc -> {
|
|
||||||
try {
|
|
||||||
return rc.getAccessor().invoke(this);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long eventSnowflake() {
|
|
||||||
return this.eventSnowflake;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Generic server response. */
|
/** Generic server response. */
|
||||||
public record ServerResponse(long clientId) implements EventWithoutSnowflake {}
|
public record ServerResponse(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to send a command to a server.
|
* Request to send a command to a server.
|
||||||
@@ -182,10 +130,10 @@ public class NetworkEvents extends EventsBase {
|
|||||||
* @param clientId The client connection ID.
|
* @param clientId The client connection ID.
|
||||||
* @param args The command arguments.
|
* @param args The command arguments.
|
||||||
*/
|
*/
|
||||||
public record SendCommand(long clientId, String... args) implements EventWithoutSnowflake {}
|
public record SendCommand(long clientId, String... args) implements GenericEvent {}
|
||||||
|
|
||||||
/** WIP (Not working) Request to reconnect a client to a previous address. */
|
/** WIP (Not working) Request to reconnect a client to a previous address. */
|
||||||
public record Reconnect(long clientId) implements EventWithoutSnowflake {}
|
public record Reconnect(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response triggered when a message is received from a server.
|
* Response triggered when a message is received from a server.
|
||||||
@@ -193,7 +141,7 @@ public class NetworkEvents extends EventsBase {
|
|||||||
* @param clientId The connection ID that received the message.
|
* @param clientId The connection ID that received the message.
|
||||||
* @param message The message content.
|
* @param message The message content.
|
||||||
*/
|
*/
|
||||||
public record ReceivedMessage(long clientId, String message) implements EventWithoutSnowflake {}
|
public record ReceivedMessage(long clientId, String message) implements GenericEvent {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to change a client connection to a new server.
|
* Request to change a client connection to a new server.
|
||||||
@@ -202,12 +150,11 @@ public class NetworkEvents extends EventsBase {
|
|||||||
* @param ip The new server IP.
|
* @param ip The new server IP.
|
||||||
* @param port The new server port.
|
* @param port The new server port.
|
||||||
*/
|
*/
|
||||||
public record ChangeClientHost(long clientId, String ip, int port)
|
public record ChangeClientHost(long clientId, String ip, int port) implements GenericEvent {}
|
||||||
implements EventWithoutSnowflake {}
|
|
||||||
|
|
||||||
/** WIP (Not working) Response indicating that the client could not connect. */
|
/** WIP (Not working) Response indicating that the client could not connect. */
|
||||||
public record CouldNotConnect(long clientId) implements EventWithoutSnowflake {}
|
public record CouldNotConnect(long clientId) implements GenericEvent {}
|
||||||
|
|
||||||
/** Event indicating a client connection was closed. */
|
/** Event indicating a client connection was closed. */
|
||||||
public record ClosedConnection(long clientId) implements EventWithoutSnowflake {}
|
public record ClosedConnection(long clientId) implements GenericEvent {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
package org.toop.framework.asset;
|
package org.toop.framework.resource;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.toop.framework.asset.events.AssetLoaderEvents;
|
|
||||||
import org.toop.framework.asset.resources.*;
|
|
||||||
import org.toop.framework.asset.types.BundledResource;
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.PreloadResource;
|
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
|
||||||
import org.reflections.Reflections;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -16,30 +6,40 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.reflections.Reflections;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.resource.events.AssetLoaderEvents;
|
||||||
|
import org.toop.framework.resource.exceptions.CouldNotCreateResourceFactoryException;
|
||||||
|
import org.toop.framework.resource.resources.*;
|
||||||
|
import org.toop.framework.resource.types.BundledResource;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.PreloadResource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for loading assets from a file system directory into memory.
|
* Responsible for loading assets from a file system directory into memory.
|
||||||
* <p>
|
|
||||||
* The {@code ResourceLoader} scans a root folder recursively, identifies files,
|
|
||||||
* and maps them to registered resource types based on file extensions and
|
|
||||||
* {@link FileExtension} annotations.
|
|
||||||
* It supports multiple resource types including {@link PreloadResource} (automatically loaded)
|
|
||||||
* and {@link BundledResource} (merged across multiple files).
|
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
* <p>Assets are stored in a static, thread-safe list and can be retrieved
|
* <p>The {@code ResourceLoader} scans a root folder recursively, identifies files, and maps them to
|
||||||
* through {@link ResourceManager}.</p>
|
* registered resource types based on file extensions and {@link FileExtension} annotations. It
|
||||||
|
* supports multiple resource types including {@link PreloadResource} (automatically loaded) and
|
||||||
|
* {@link BundledResource} (merged across multiple files).
|
||||||
|
*
|
||||||
|
* <p>Assets are stored in a static, thread-safe list and can be retrieved through {@link
|
||||||
|
* ResourceManager}.
|
||||||
|
*
|
||||||
|
* <p>Features:
|
||||||
*
|
*
|
||||||
* <p>Features:</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Recursive directory scanning for assets.</li>
|
* <li>Recursive directory scanning for assets.
|
||||||
* <li>Automatic registration of resource classes via reflection.</li>
|
* <li>Automatic registration of resource classes via reflection.
|
||||||
* <li>Bundled resource support: multiple files merged into a single resource instance.</li>
|
* <li>Bundled resource support: multiple files merged into a single resource instance.
|
||||||
* <li>Preload resources automatically invoke {@link PreloadResource#load()}.</li>
|
* <li>Preload resources automatically invoke {@link PreloadResource#load()}.
|
||||||
* <li>Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events.</li>
|
* <li>Progress tracking via {@link AssetLoaderEvents.LoadingProgressUpdate} events.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>Usage example:</p>
|
* <p>Usage example:
|
||||||
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* ResourceLoader loader = new ResourceLoader("assets");
|
* ResourceLoader loader = new ResourceLoader("assets");
|
||||||
* double progress = loader.getProgress();
|
* double progress = loader.getProgress();
|
||||||
@@ -48,14 +48,17 @@ import java.util.function.Function;
|
|||||||
*/
|
*/
|
||||||
public class ResourceLoader {
|
public class ResourceLoader {
|
||||||
private static final Logger logger = LogManager.getLogger(ResourceLoader.class);
|
private static final Logger logger = LogManager.getLogger(ResourceLoader.class);
|
||||||
private static final List<ResourceMeta<? extends BaseResource>> assets = new CopyOnWriteArrayList<>();
|
private static final List<ResourceMeta<? extends BaseResource>> assets =
|
||||||
private final Map<String, Function<File, ? extends BaseResource>> registry = new ConcurrentHashMap<>();
|
new CopyOnWriteArrayList<>();
|
||||||
|
private final Map<String, Function<File, ? extends BaseResource>> registry =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final AtomicInteger loadedCount = new AtomicInteger(0);
|
private final AtomicInteger loadedCount = new AtomicInteger(0);
|
||||||
private int totalCount = 0;
|
private int totalCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an ResourceLoader and loads assets from the given root folder.
|
* Constructs an ResourceLoader and loads assets from the given root folder.
|
||||||
|
*
|
||||||
* @param rootFolder the folder containing asset files
|
* @param rootFolder the folder containing asset files
|
||||||
*/
|
*/
|
||||||
public ResourceLoader(File rootFolder) {
|
public ResourceLoader(File rootFolder) {
|
||||||
@@ -84,6 +87,7 @@ public class ResourceLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an ResourceLoader from a folder path.
|
* Constructs an ResourceLoader from a folder path.
|
||||||
|
*
|
||||||
* @param rootFolder the folder path containing assets
|
* @param rootFolder the folder path containing assets
|
||||||
*/
|
*/
|
||||||
public ResourceLoader(String rootFolder) {
|
public ResourceLoader(String rootFolder) {
|
||||||
@@ -92,6 +96,7 @@ public class ResourceLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current progress of loading assets (0.0 to 1.0).
|
* Returns the current progress of loading assets (0.0 to 1.0).
|
||||||
|
*
|
||||||
* @return progress as a double
|
* @return progress as a double
|
||||||
*/
|
*/
|
||||||
public double getProgress() {
|
public double getProgress() {
|
||||||
@@ -100,6 +105,7 @@ public class ResourceLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of assets loaded so far.
|
* Returns the number of assets loaded so far.
|
||||||
|
*
|
||||||
* @return loaded count
|
* @return loaded count
|
||||||
*/
|
*/
|
||||||
public int getLoadedCount() {
|
public int getLoadedCount() {
|
||||||
@@ -108,6 +114,7 @@ public class ResourceLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the total number of files found to load.
|
* Returns the total number of files found to load.
|
||||||
|
*
|
||||||
* @return total asset count
|
* @return total asset count
|
||||||
*/
|
*/
|
||||||
public int getTotalCount() {
|
public int getTotalCount() {
|
||||||
@@ -116,6 +123,7 @@ public class ResourceLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a snapshot list of all assets loaded by this loader.
|
* Returns a snapshot list of all assets loaded by this loader.
|
||||||
|
*
|
||||||
* @return list of loaded assets
|
* @return list of loaded assets
|
||||||
*/
|
*/
|
||||||
public List<ResourceMeta<? extends BaseResource>> getAssets() {
|
public List<ResourceMeta<? extends BaseResource>> getAssets() {
|
||||||
@@ -124,6 +132,7 @@ public class ResourceLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a factory for a specific file extension.
|
* Registers a factory for a specific file extension.
|
||||||
|
*
|
||||||
* @param extension the file extension (without dot)
|
* @param extension the file extension (without dot)
|
||||||
* @param factory a function mapping a File to a resource instance
|
* @param factory a function mapping a File to a resource instance
|
||||||
* @param <T> the type of resource
|
* @param <T> the type of resource
|
||||||
@@ -132,69 +141,79 @@ public class ResourceLoader {
|
|||||||
this.registry.put(extension, factory);
|
this.registry.put(extension, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Maps a file to a resource instance based on its extension and registered factories. */
|
||||||
* Maps a file to a resource instance based on its extension and registered factories.
|
private <T extends BaseResource> T resourceMapper(File file)
|
||||||
*/
|
throws CouldNotCreateResourceFactoryException, IllegalArgumentException {
|
||||||
private <T extends BaseResource> T resourceMapper(File file, Class<T> type) {
|
|
||||||
String ext = getExtension(file.getName());
|
String ext = getExtension(file.getName());
|
||||||
Function<File, ? extends BaseResource> factory = registry.get(ext);
|
Function<File, ? extends BaseResource> factory = registry.get(ext);
|
||||||
if (factory == null) return null;
|
if (factory == null)
|
||||||
|
throw new CouldNotCreateResourceFactoryException(registry, file.getName());
|
||||||
|
|
||||||
BaseResource resource = factory.apply(file);
|
BaseResource resource = factory.apply(file);
|
||||||
|
|
||||||
if (!type.isInstance(resource)) {
|
if (resource == null) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"File " + file.getName() + " is not of type " + type.getSimpleName()
|
"File "
|
||||||
);
|
+ file.getName()
|
||||||
|
+ " is not of type "
|
||||||
|
+ BaseResource.class.getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
return type.cast(resource);
|
return ((Class<T>) BaseResource.class).cast(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Loads the given list of files into assets, handling bundled and preload resources. */
|
||||||
* Loads the given list of files into assets, handling bundled and preload resources.
|
|
||||||
*/
|
|
||||||
private void loader(List<File> files) {
|
private void loader(List<File> files) {
|
||||||
Map<String, BundledResource> bundledResources = new HashMap<>();
|
Map<String, BundledResource> bundledResources = new HashMap<>();
|
||||||
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
boolean skipAdd = false;
|
boolean skipAdd = false;
|
||||||
BaseResource resource = resourceMapper(file, BaseResource.class);
|
BaseResource resource = null;
|
||||||
|
try {
|
||||||
|
resource = resourceMapper(file);
|
||||||
|
} catch (CouldNotCreateResourceFactoryException _) {
|
||||||
|
logger.warn("Could not create resource for: {}", file);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.error(e);
|
||||||
|
}
|
||||||
switch (resource) {
|
switch (resource) {
|
||||||
case null -> {
|
case null -> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case BundledResource br -> {
|
case BundledResource br -> {
|
||||||
String key = resource.getClass().getName() + ":" + br.getBaseName();
|
String key = resource.getClass().getName() + ":" + br.getBaseName();
|
||||||
if (!bundledResources.containsKey(key)) {bundledResources.put(key, br);}
|
if (!bundledResources.containsKey(key)) {
|
||||||
|
bundledResources.put(key, br);
|
||||||
|
}
|
||||||
bundledResources.get(key).loadFile(file);
|
bundledResources.get(key).loadFile(file);
|
||||||
resource = (BaseResource) bundledResources.get(key);
|
resource = (BaseResource) bundledResources.get(key);
|
||||||
assets.add(new ResourceMeta<>(br.getBaseName(), resource));
|
assets.add(new ResourceMeta<>(br.getBaseName(), resource));
|
||||||
skipAdd = true;
|
skipAdd = true;
|
||||||
}
|
}
|
||||||
case PreloadResource pr -> pr.load();
|
case PreloadResource pr -> pr.load();
|
||||||
default -> {
|
default -> {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseResource finalResource = resource;
|
BaseResource finalResource = resource;
|
||||||
boolean alreadyAdded = assets.stream()
|
boolean alreadyAdded = assets.stream().anyMatch(a -> a.getResource() == finalResource);
|
||||||
.anyMatch(a -> a.getResource() == finalResource);
|
|
||||||
if (!alreadyAdded && !skipAdd) {
|
if (!alreadyAdded && !skipAdd) {
|
||||||
assets.add(new ResourceMeta<>(file.getName(), resource));
|
assets.add(new ResourceMeta<>(file.getName(), resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Loaded {} from {}", resource.getClass().getSimpleName(), file.getAbsolutePath());
|
logger.info(
|
||||||
|
"Loaded {} from {}",
|
||||||
|
resource.getClass().getSimpleName(),
|
||||||
|
file.getAbsolutePath());
|
||||||
loadedCount.incrementAndGet();
|
loadedCount.incrementAndGet();
|
||||||
new EventFlow()
|
new EventFlow()
|
||||||
.addPostEvent(new AssetLoaderEvents.LoadingProgressUpdate(loadedCount.get(), totalCount))
|
.addPostEvent(
|
||||||
|
new AssetLoaderEvents.LoadingProgressUpdate(
|
||||||
|
loadedCount.get(), totalCount))
|
||||||
.postEvent();
|
.postEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Recursively searches a folder and adds all files to the foundFiles list. */
|
||||||
* Recursively searches a folder and adds all files to the foundFiles list.
|
|
||||||
*/
|
|
||||||
private void fileSearcher(final File folder, List<File> foundFiles) {
|
private void fileSearcher(final File folder, List<File> foundFiles) {
|
||||||
for (File fileEntry : Objects.requireNonNull(folder.listFiles())) {
|
for (File fileEntry : Objects.requireNonNull(folder.listFiles())) {
|
||||||
if (fileEntry.isDirectory()) {
|
if (fileEntry.isDirectory()) {
|
||||||
@@ -206,18 +225,20 @@ public class ResourceLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses reflection to automatically register all {@link BaseResource} subclasses
|
* Uses reflection to automatically register all {@link BaseResource} subclasses annotated with
|
||||||
* annotated with {@link FileExtension}.
|
* {@link FileExtension}.
|
||||||
*/
|
*/
|
||||||
private void autoRegisterResources() {
|
private void autoRegisterResources() {
|
||||||
Reflections reflections = new Reflections("org.toop.framework.asset.resources");
|
Reflections reflections = new Reflections("org.toop.framework.resource.resources");
|
||||||
Set<Class<? extends BaseResource>> classes = reflections.getSubTypesOf(BaseResource.class);
|
Set<Class<? extends BaseResource>> classes = reflections.getSubTypesOf(BaseResource.class);
|
||||||
|
|
||||||
for (Class<? extends BaseResource> cls : classes) {
|
for (Class<? extends BaseResource> cls : classes) {
|
||||||
if (!cls.isAnnotationPresent(FileExtension.class)) continue;
|
if (!cls.isAnnotationPresent(FileExtension.class)) continue;
|
||||||
FileExtension annotation = cls.getAnnotation(FileExtension.class);
|
FileExtension annotation = cls.getAnnotation(FileExtension.class);
|
||||||
for (String ext : annotation.value()) {
|
for (String ext : annotation.value()) {
|
||||||
registry.put(ext, file -> {
|
registry.put(
|
||||||
|
ext,
|
||||||
|
file -> {
|
||||||
try {
|
try {
|
||||||
return cls.getConstructor(File.class).newInstance(file);
|
return cls.getConstructor(File.class).newInstance(file);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -228,9 +249,7 @@ public class ResourceLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Extracts the base name from a file name, used for bundling multiple files. */
|
||||||
* Extracts the base name from a file name, used for bundling multiple files.
|
|
||||||
*/
|
|
||||||
private static String getBaseName(String fileName) {
|
private static String getBaseName(String fileName) {
|
||||||
int underscoreIndex = fileName.indexOf('_');
|
int underscoreIndex = fileName.indexOf('_');
|
||||||
int dotIndex = fileName.lastIndexOf('.');
|
int dotIndex = fileName.lastIndexOf('.');
|
||||||
@@ -238,9 +257,7 @@ public class ResourceLoader {
|
|||||||
return fileName.substring(0, dotIndex);
|
return fileName.substring(0, dotIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the file extension of a given file name (without dot). */
|
||||||
* Returns the file extension of a given file name (without dot).
|
|
||||||
*/
|
|
||||||
public static String getExtension(String name) {
|
public static String getExtension(String name) {
|
||||||
int i = name.lastIndexOf('.');
|
int i = name.lastIndexOf('.');
|
||||||
return (i > 0) ? name.substring(i + 1) : "";
|
return (i > 0) ? name.substring(i + 1) : "";
|
||||||
@@ -1,30 +1,31 @@
|
|||||||
package org.toop.framework.asset;
|
package org.toop.framework.resource;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.toop.framework.asset.resources.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.toop.framework.resource.exceptions.ResourceNotFoundException;
|
||||||
|
import org.toop.framework.resource.resources.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centralized manager for all loaded assets in the application.
|
* Centralized manager for all loaded assets in the application.
|
||||||
* <p>
|
|
||||||
* {@code ResourceManager} maintains a thread-safe registry of {@link Asset} objects
|
|
||||||
* and provides utility methods to retrieve assets by name, ID, or type.
|
|
||||||
* It works together with {@link ResourceLoader} to register assets automatically
|
|
||||||
* when they are loaded from the file system.
|
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
* <p>Key responsibilities:</p>
|
* <p>{@code ResourceManager} maintains a thread-safe registry of {@link ResourceMeta} objects and
|
||||||
|
* provides utility methods to retrieve assets by name, ID, or type. It works together with {@link
|
||||||
|
* ResourceLoader} to register assets automatically when they are loaded from the file system.
|
||||||
|
*
|
||||||
|
* <p>Key responsibilities:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Storing all loaded assets in a concurrent map.</li>
|
* <li>Storing all loaded assets in a concurrent map.
|
||||||
* <li>Providing typed access to asset resources.</li>
|
* <li>Providing typed access to asset resources.
|
||||||
* <li>Allowing lookup by asset name or ID.</li>
|
* <li>Allowing lookup by asset name or ID.
|
||||||
* <li>Supporting retrieval of all assets of a specific {@link BaseResource} subclass.</li>
|
* <li>Supporting retrieval of all assets of a specific {@link BaseResource} subclass.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>Example usage:</p>
|
* <p>Example usage:
|
||||||
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* // Load assets from a loader
|
* // Load assets from a loader
|
||||||
* ResourceLoader loader = new ResourceLoader(new File("RootFolder"));
|
* ResourceLoader loader = new ResourceLoader(new File("RootFolder"));
|
||||||
@@ -40,27 +41,28 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
* Optional<Asset<? extends BaseResource>> maybeAsset = ResourceManager.findByName("menu.css");
|
* Optional<Asset<? extends BaseResource>> maybeAsset = ResourceManager.findByName("menu.css");
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>Notes:</p>
|
* <p>Notes:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>All retrieval methods are static and thread-safe.</li>
|
* <li>All retrieval methods are static and thread-safe.
|
||||||
* <li>The {@link #get(String)} method may require casting if the asset type is not known at compile time.</li>
|
* <li>The {@link #get(String)} method may require casting if the asset type is not known at
|
||||||
* <li>Assets should be loaded via {@link ResourceLoader} before retrieval.</li>
|
* compile time.
|
||||||
|
* <li>Assets should be loaded via {@link ResourceLoader} before retrieval.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class ResourceManager {
|
public class ResourceManager {
|
||||||
private static final Logger logger = LogManager.getLogger(ResourceManager.class);
|
private static final Logger logger = LogManager.getLogger(ResourceManager.class);
|
||||||
private static final ResourceManager INSTANCE = new ResourceManager();
|
private static final Map<String, ResourceMeta<? extends BaseResource>> assets =
|
||||||
private static final Map<String, ResourceMeta<? extends BaseResource>> assets = new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
private static ResourceManager instance;
|
||||||
|
|
||||||
private ResourceManager() {}
|
private ResourceManager() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the singleton instance of {@code ResourceManager}.
|
|
||||||
*
|
|
||||||
* @return the shared instance
|
|
||||||
*/
|
|
||||||
public static ResourceManager getInstance() {
|
public static ResourceManager getInstance() {
|
||||||
return INSTANCE;
|
if (instance == null) {
|
||||||
|
instance = new ResourceManager();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,8 +70,8 @@ public class ResourceManager {
|
|||||||
*
|
*
|
||||||
* @param loader the loader that has already loaded assets
|
* @param loader the loader that has already loaded assets
|
||||||
*/
|
*/
|
||||||
public synchronized static void loadAssets(ResourceLoader loader) {
|
public static synchronized void loadAssets(ResourceLoader loader) {
|
||||||
for (var asset : loader.getAssets()) {
|
for (ResourceMeta<? extends BaseResource> asset : loader.getAssets()) {
|
||||||
assets.put(asset.getName(), asset);
|
assets.put(asset.getName(), asset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,15 +87,15 @@ public class ResourceManager {
|
|||||||
public static <T extends BaseResource> T get(String name) {
|
public static <T extends BaseResource> T get(String name) {
|
||||||
ResourceMeta<T> asset = (ResourceMeta<T>) assets.get(name);
|
ResourceMeta<T> asset = (ResourceMeta<T>) assets.get(name);
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
throw new TypeNotPresentException(name, new RuntimeException(String.format("Type %s not present", name))); // TODO: Create own exception, BAM
|
throw new ResourceNotFoundException(name);
|
||||||
}
|
}
|
||||||
return asset.getResource();
|
return asset.getResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @SuppressWarnings("unchecked")
|
// @SuppressWarnings("unchecked")
|
||||||
// public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType() {
|
// public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType() {
|
||||||
// return (ArrayList<ResourceMeta<T>>) (ArrayList<?>) new ArrayList<>(assets.values());
|
// return (ArrayList<ResourceMeta<T>>) (ArrayList<?>) new ArrayList<>(assets.values());
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve all assets of a specific resource type.
|
* Retrieve all assets of a specific resource type.
|
||||||
@@ -102,16 +104,32 @@ public class ResourceManager {
|
|||||||
* @param <T> the resource type
|
* @param <T> the resource type
|
||||||
* @return a list of assets matching the type
|
* @return a list of assets matching the type
|
||||||
*/
|
*/
|
||||||
public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType(Class<T> type) {
|
public static <T extends BaseResource> List<ResourceMeta<T>> getAllOfType(Class<T> type) {
|
||||||
ArrayList<ResourceMeta<T>> list = new ArrayList<>();
|
List<ResourceMeta<T>> result = new ArrayList<>();
|
||||||
for (ResourceMeta<? extends BaseResource> asset : assets.values()) {
|
|
||||||
if (type.isInstance(asset.getResource())) {
|
for (ResourceMeta<? extends BaseResource> meta : assets.values()) {
|
||||||
|
BaseResource res = meta.getResource();
|
||||||
|
if (type.isInstance(res)) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
ResourceMeta<T> typed = (ResourceMeta<T>) asset;
|
ResourceMeta<T> typed = (ResourceMeta<T>) meta;
|
||||||
list.add(typed);
|
result.add(typed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends BaseResource> List<T> getAllOfTypeAndRemoveWrapper(Class<T> type) {
|
||||||
|
List<T> result = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ResourceMeta<? extends BaseResource> meta : assets.values()) {
|
||||||
|
BaseResource res = meta.getResource();
|
||||||
|
if (type.isInstance(res)) {
|
||||||
|
result.add((T) res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,5 +154,6 @@ public class ResourceManager {
|
|||||||
*/
|
*/
|
||||||
public static void addAsset(ResourceMeta<? extends BaseResource> asset) {
|
public static void addAsset(ResourceMeta<? extends BaseResource> asset) {
|
||||||
assets.put(asset.getName(), asset);
|
assets.put(asset.getName(), asset);
|
||||||
|
logger.info("Successfully added asset: {}, to the asset list", asset.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.toop.framework.asset;
|
package org.toop.framework.resource;
|
||||||
|
|
||||||
import org.toop.framework.SnowflakeGenerator;
|
import org.toop.framework.SnowflakeGenerator;
|
||||||
import org.toop.framework.asset.resources.BaseResource;
|
import org.toop.framework.resource.resources.BaseResource;
|
||||||
|
|
||||||
public class ResourceMeta<T extends BaseResource> {
|
public class ResourceMeta<T extends BaseResource> {
|
||||||
private final Long id;
|
private final Long id;
|
||||||
@@ -9,7 +9,7 @@ public class ResourceMeta<T extends BaseResource> {
|
|||||||
private final T resource;
|
private final T resource;
|
||||||
|
|
||||||
public ResourceMeta(String name, T resource) {
|
public ResourceMeta(String name, T resource) {
|
||||||
this.id = new SnowflakeGenerator().nextId();
|
this.id = SnowflakeGenerator.nextId();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
}
|
}
|
||||||
@@ -25,5 +25,4 @@ public class ResourceMeta<T extends BaseResource> {
|
|||||||
public T getResource() {
|
public T getResource() {
|
||||||
return this.resource;
|
return this.resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.toop.framework.resource.events;
|
||||||
|
|
||||||
|
import org.toop.framework.eventbus.events.GenericEvent;
|
||||||
|
|
||||||
|
public class AssetLoaderEvents {
|
||||||
|
public record LoadingProgressUpdate(int hasLoadedAmount, int isLoadingAmount)
|
||||||
|
implements GenericEvent {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.toop.framework.resource.exceptions;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CouldNotCreateResourceFactoryException extends RuntimeException {
|
||||||
|
public CouldNotCreateResourceFactoryException(Map<?, ?> registry, String fileName) {
|
||||||
|
super(
|
||||||
|
String.format(
|
||||||
|
"Could not create resource factory for: %s, isRegistryEmpty: %b",
|
||||||
|
fileName, registry.isEmpty()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.toop.framework.resource.exceptions;
|
||||||
|
|
||||||
|
public class IsNotAResourceException extends RuntimeException {
|
||||||
|
public <T> IsNotAResourceException(Class<T> clazz, String message) {
|
||||||
|
super(clazz.getName() + " does not implement BaseResource");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.toop.framework.resource.exceptions;
|
||||||
|
|
||||||
|
public class ResourceNotFoundException extends RuntimeException {
|
||||||
|
public ResourceNotFoundException(String name) {
|
||||||
|
super("Could not find resource: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
@@ -14,5 +14,4 @@ public abstract class BaseResource {
|
|||||||
public File getFile() {
|
public File getFile() {
|
||||||
return this.file;
|
return this.file;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
|
||||||
@FileExtension({"css"})
|
@FileExtension({"css"})
|
||||||
public class CssAsset extends BaseResource {
|
public class CssAsset extends BaseResource {
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.PreloadResource;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.PreloadResource;
|
||||||
|
|
||||||
@FileExtension({"ttf", "otf"})
|
@FileExtension({"ttf", "otf"})
|
||||||
public class FontAsset extends BaseResource implements PreloadResource {
|
public class FontAsset extends BaseResource implements PreloadResource {
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.LoadableResource;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.LoadableResource;
|
||||||
|
|
||||||
@FileExtension({"png", "jpg", "jpeg"})
|
@FileExtension({"png", "jpg", "jpeg"})
|
||||||
public class ImageAsset extends BaseResource implements LoadableResource {
|
public class ImageAsset extends BaseResource implements LoadableResource {
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.LoadableResource;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.LoadableResource;
|
||||||
|
|
||||||
@FileExtension({"json"})
|
@FileExtension({"json"})
|
||||||
public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
||||||
@@ -25,7 +26,8 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
|||||||
File file = getFile();
|
File file = getFile();
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
try {
|
try {
|
||||||
// make a new file with the declared constructor (example: settings) if it doesn't exist
|
// make a new file with the declared constructor (example: settings) if it doesn't
|
||||||
|
// exist
|
||||||
content = type.getDeclaredConstructor().newInstance();
|
content = type.getDeclaredConstructor().newInstance();
|
||||||
save();
|
save();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -36,7 +38,7 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
|||||||
try (FileReader reader = new FileReader(file)) {
|
try (FileReader reader = new FileReader(file)) {
|
||||||
content = gson.fromJson(reader, type);
|
content = gson.fromJson(reader, type);
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
} catch(Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to load JSON asset" + getFile(), e);
|
throw new RuntimeException("Failed to load JSON asset" + getFile(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +61,8 @@ public class JsonAsset<T> extends BaseResource implements LoadableResource {
|
|||||||
File file = getFile();
|
File file = getFile();
|
||||||
File parent = file.getParentFile();
|
File parent = file.getParentFile();
|
||||||
if (parent != null && !parent.exists()) {
|
if (parent != null && !parent.exists()) {
|
||||||
parent.mkdirs();
|
boolean isDirectoryMade = parent.mkdirs();
|
||||||
|
assert isDirectoryMade;
|
||||||
}
|
}
|
||||||
try (FileWriter writer = new FileWriter(file)) {
|
try (FileWriter writer = new FileWriter(file)) {
|
||||||
gson.toJson(content, writer);
|
gson.toJson(content, writer);
|
||||||
@@ -1,34 +1,29 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
import org.toop.framework.asset.types.BundledResource;
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.LoadableResource;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import org.toop.framework.resource.types.BundledResource;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.LoadableResource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a localization resource asset that loads and manages property files
|
* Represents a localization resource asset that loads and manages property files containing
|
||||||
* containing key-value pairs for different locales.
|
* key-value pairs for different locales.
|
||||||
* <p>
|
*
|
||||||
* This class implements {@link LoadableResource} to support loading/unloading
|
* <p>This class implements {@link LoadableResource} to support loading/unloading and {@link
|
||||||
* and {@link BundledResource} to represent resources that can contain multiple
|
* BundledResource} to represent resources that can contain multiple localized bundles.
|
||||||
* localized bundles.
|
*
|
||||||
* </p>
|
* <p>Files handled by this class must have the {@code .properties} extension, optionally with a
|
||||||
* <p>
|
* locale suffix, e.g., {@code messages_en_US.properties}.
|
||||||
* Files handled by this class must have the {@code .properties} extension,
|
*
|
||||||
* optionally with a locale suffix, e.g., {@code messages_en_US.properties}.
|
* <p>Example usage:
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
* <p>
|
|
||||||
* Example usage:
|
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* LocalizationAsset asset = new LocalizationAsset(new File("messages_en_US.properties"));
|
* LocalizationAsset asset = new LocalizationAsset(new File("messages_en_US.properties"));
|
||||||
* asset.load();
|
* asset.load();
|
||||||
* String greeting = asset.getString("hello", Locale.US);
|
* String greeting = asset.getString("hello", Locale.US);
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* </p>
|
|
||||||
*/
|
*/
|
||||||
@FileExtension({"properties"})
|
@FileExtension({"properties"})
|
||||||
public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource {
|
public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource {
|
||||||
@@ -37,13 +32,13 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
|||||||
private final Map<Locale, ResourceBundle> bundles = new HashMap<>();
|
private final Map<Locale, ResourceBundle> bundles = new HashMap<>();
|
||||||
|
|
||||||
/** Flag indicating whether this asset has been loaded. */
|
/** Flag indicating whether this asset has been loaded. */
|
||||||
private boolean isLoaded = false;
|
private boolean loaded = false;
|
||||||
|
|
||||||
/** Basename of the given asset */
|
/** Basename of the given asset */
|
||||||
private final String baseName = "localization";
|
private final String baseName = "localization";
|
||||||
|
|
||||||
/** Fallback locale used when no matching locale is found. */
|
/** Fallback locale used when no matching locale is found. */
|
||||||
private final Locale fallback = Locale.forLanguageTag("en_US");
|
private final Locale fallback = Locale.forLanguageTag("en");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new LocalizationAsset for the specified file.
|
* Constructs a new LocalizationAsset for the specified file.
|
||||||
@@ -54,22 +49,18 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
|||||||
super(file);
|
super(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Loads the resource file into memory and prepares localized bundles. */
|
||||||
* Loads the resource file into memory and prepares localized bundles.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
loadFile(getFile());
|
loadFile(getFile());
|
||||||
isLoaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Unloads all loaded resource bundles, freeing memory. */
|
||||||
* Unloads all loaded resource bundles, freeing memory.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void unload() {
|
public void unload() {
|
||||||
bundles.clear();
|
bundles.clear();
|
||||||
isLoaded = false;
|
loaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,13 +70,12 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isLoaded() {
|
public boolean isLoaded() {
|
||||||
return isLoaded;
|
return loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a localized string for the given key and locale.
|
* Retrieves a localized string for the given key and locale. If an exact match for the locale
|
||||||
* If an exact match for the locale is not found, a fallback
|
* is not found, a fallback matching the language or the default locale will be used.
|
||||||
* matching the language or the default locale will be used.
|
|
||||||
*
|
*
|
||||||
* @param key the key of the string
|
* @param key the key of the string
|
||||||
* @param locale the desired locale
|
* @param locale the desired locale
|
||||||
@@ -95,14 +85,15 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
|||||||
public String getString(String key, Locale locale) {
|
public String getString(String key, Locale locale) {
|
||||||
Locale target = findBestLocale(locale);
|
Locale target = findBestLocale(locale);
|
||||||
ResourceBundle bundle = bundles.get(target);
|
ResourceBundle bundle = bundles.get(target);
|
||||||
if (bundle == null) throw new MissingResourceException(
|
if (bundle == null)
|
||||||
|
throw new MissingResourceException(
|
||||||
"No bundle for locale: " + target, getClass().getName(), key);
|
"No bundle for locale: " + target, getClass().getName(), key);
|
||||||
return bundle.getString(key);
|
return bundle.getString(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the best matching locale among loaded bundles.
|
* Finds the best matching locale among loaded bundles. Prefers an exact match, then
|
||||||
* Prefers an exact match, then language-only match, then fallback.
|
* language-only match, then fallback.
|
||||||
*
|
*
|
||||||
* @param locale the desired locale
|
* @param locale the desired locale
|
||||||
* @return the best matching locale
|
* @return the best matching locale
|
||||||
@@ -125,8 +116,8 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a specific property file as a resource bundle.
|
* Loads a specific property file as a resource bundle. The locale is extracted from the file
|
||||||
* The locale is extracted from the file name if present.
|
* name if present.
|
||||||
*
|
*
|
||||||
* @param file the property file to load
|
* @param file the property file to load
|
||||||
* @throws RuntimeException if the file cannot be read
|
* @throws RuntimeException if the file cannot be read
|
||||||
@@ -140,7 +131,7 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("Failed to load localization file: " + file, e);
|
throw new RuntimeException("Failed to load localization file: " + file, e);
|
||||||
}
|
}
|
||||||
isLoaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,22 +144,23 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
|
|||||||
return this.baseName;
|
return this.baseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * Extracts the base name from a file name.
|
// * Extracts the base name from a file name.
|
||||||
// *
|
// *
|
||||||
// * @param fileName the file name
|
// * @param fileName the file name
|
||||||
// * @return base name without locale or extension
|
// * @return base name without locale or extension
|
||||||
// */
|
// */
|
||||||
// private String getBaseName(String fileName) {
|
// private String getBaseName(String fileName) {
|
||||||
// int dotIndex = fileName.lastIndexOf('.');
|
// int dotIndex = fileName.lastIndexOf('.');
|
||||||
// String nameWithoutExtension = (dotIndex > 0) ? fileName.substring(0, dotIndex) : fileName;
|
// String nameWithoutExtension = (dotIndex > 0) ? fileName.substring(0, dotIndex) :
|
||||||
//
|
// fileName;
|
||||||
// int underscoreIndex = nameWithoutExtension.indexOf('_');
|
//
|
||||||
// if (underscoreIndex > 0) {
|
// int underscoreIndex = nameWithoutExtension.indexOf('_');
|
||||||
// return nameWithoutExtension.substring(0, underscoreIndex);
|
// if (underscoreIndex > 0) {
|
||||||
// }
|
// return nameWithoutExtension.substring(0, underscoreIndex);
|
||||||
// return nameWithoutExtension;
|
// }
|
||||||
// }
|
// return nameWithoutExtension;
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts a locale from a file name based on the pattern "base_LOCALE.properties".
|
* Extracts a locale from a file name based on the pattern "base_LOCALE.properties".
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import javafx.scene.media.Media;
|
||||||
|
import javafx.scene.media.MediaPlayer;
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.LoadableResource;
|
||||||
|
|
||||||
|
@FileExtension({"mp3"})
|
||||||
|
public class MusicAsset extends BaseResource implements LoadableResource, AudioResource {
|
||||||
|
private MediaPlayer mediaPlayer;
|
||||||
|
private double volume;
|
||||||
|
|
||||||
|
public MusicAsset(final File audioFile) {
|
||||||
|
super(audioFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaPlayer getMediaPlayer() {
|
||||||
|
load();
|
||||||
|
return mediaPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPlayer() {
|
||||||
|
mediaPlayer.setOnEndOfMedia(this::stop);
|
||||||
|
mediaPlayer.setOnError(this::stop);
|
||||||
|
mediaPlayer.setOnStopped(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load() {
|
||||||
|
if (mediaPlayer == null) {
|
||||||
|
mediaPlayer = new MediaPlayer(new Media(file.toURI().toString()));
|
||||||
|
initPlayer();
|
||||||
|
mediaPlayer.setVolume(volume);
|
||||||
|
}
|
||||||
|
this.isLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unload() {
|
||||||
|
if (mediaPlayer != null) {
|
||||||
|
mediaPlayer.stop();
|
||||||
|
mediaPlayer.dispose();
|
||||||
|
mediaPlayer = null;
|
||||||
|
}
|
||||||
|
isLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoaded() {
|
||||||
|
return isLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateVolume(double volume) {
|
||||||
|
if (mediaPlayer != null) {
|
||||||
|
mediaPlayer.setVolume(volume);
|
||||||
|
}
|
||||||
|
this.volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return super.getFile().getName(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnEnd(Runnable run) {
|
||||||
|
mediaPlayer.setOnEndOfMedia(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnError(Runnable run) {
|
||||||
|
mediaPlayer.setOnError(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
getMediaPlayer().play();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
getMediaPlayer().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
|
|
||||||
import org.toop.framework.settings.Settings;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import org.toop.framework.settings.Settings;
|
||||||
|
|
||||||
public class SettingsAsset extends JsonAsset<Settings> {
|
public class SettingsAsset extends JsonAsset<Settings> {
|
||||||
|
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.LoadableResource;
|
||||||
|
|
||||||
|
@FileExtension({"wav"})
|
||||||
|
public class SoundEffectAsset extends BaseResource implements LoadableResource, AudioResource {
|
||||||
|
private final Clip clip = AudioSystem.getClip();
|
||||||
|
|
||||||
|
private LineListener onEnd = null;
|
||||||
|
private LineListener onError = null;
|
||||||
|
|
||||||
|
private double volume = 100; // TODO: Find a better way to set volume on clip load
|
||||||
|
|
||||||
|
public SoundEffectAsset(final File audioFile) throws LineUnavailableException {
|
||||||
|
super(audioFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a new clip to play
|
||||||
|
public Clip getClip() {
|
||||||
|
if (!this.isLoaded()) {this.load();} return this.clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AudioInputStream downSampleAudio(AudioInputStream audioInputStream, AudioFormat baseFormat) {
|
||||||
|
AudioFormat decodedFormat =
|
||||||
|
new AudioFormat(
|
||||||
|
AudioFormat.Encoding.PCM_SIGNED,
|
||||||
|
baseFormat.getSampleRate(),
|
||||||
|
16, // force 16-bit
|
||||||
|
baseFormat.getChannels(),
|
||||||
|
baseFormat.getChannels() * 2,
|
||||||
|
baseFormat.getSampleRate(),
|
||||||
|
false // little-endian
|
||||||
|
);
|
||||||
|
|
||||||
|
return AudioSystem.getAudioInputStream(decodedFormat, audioInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load() {
|
||||||
|
try {
|
||||||
|
if (this.isLoaded){
|
||||||
|
return; // Return if it is already loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert a new audio stream into the clip
|
||||||
|
AudioInputStream inputStream = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream(this.getFile())));
|
||||||
|
AudioFormat baseFormat = inputStream.getFormat();
|
||||||
|
if (baseFormat.getSampleSizeInBits() > 16)
|
||||||
|
inputStream = downSampleAudio(inputStream, baseFormat);
|
||||||
|
this.clip.open(inputStream); // ^ Clip can only run 16 bit and lower, thus downsampling necessary.
|
||||||
|
this.updateVolume(this.volume);
|
||||||
|
this.isLoaded = true;
|
||||||
|
} catch (LineUnavailableException | UnsupportedAudioFileException | IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unload() {
|
||||||
|
if (!this.isLoaded) return; // Return if already unloaded
|
||||||
|
|
||||||
|
if (clip.isRunning()) clip.stop(); // Stops playback of the clip
|
||||||
|
|
||||||
|
clip.close(); // Releases native resources (empties buffer)
|
||||||
|
|
||||||
|
this.getClip().removeLineListener(this.onEnd);
|
||||||
|
this.getClip().removeLineListener(this.onError);
|
||||||
|
|
||||||
|
this.onEnd = null;
|
||||||
|
this.onError = null;
|
||||||
|
|
||||||
|
this.isLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoaded() {
|
||||||
|
return this.isLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateVolume(double volume) {
|
||||||
|
{
|
||||||
|
this.volume = volume;
|
||||||
|
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
|
||||||
|
FloatControl volumeControl =
|
||||||
|
(FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
|
||||||
|
float min = volumeControl.getMinimum();
|
||||||
|
float max = volumeControl.getMaximum();
|
||||||
|
float dB =
|
||||||
|
(float)
|
||||||
|
(Math.log10(Math.max(volume, 0.0001))
|
||||||
|
* 20.0); // convert linear to dB
|
||||||
|
dB = Math.max(min, Math.min(max, dB));
|
||||||
|
volumeControl.setValue(dB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return this.getFile().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnEnd(Runnable run) {
|
||||||
|
this.onEnd = event -> {
|
||||||
|
if (event.getType() == LineEvent.Type.STOP) {
|
||||||
|
run.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getClip().addLineListener(this.onEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnError(Runnable run) {
|
||||||
|
// this.onError = event -> {
|
||||||
|
// if (event.getType() == LineEvent.Type.STOP) {
|
||||||
|
// run.run();
|
||||||
|
// }
|
||||||
|
// }; TODO
|
||||||
|
//
|
||||||
|
// this.getClip().addLineListener(this.onEnd);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
if (!isLoaded()) load();
|
||||||
|
|
||||||
|
this.clip.setFramePosition(0); // rewind to the start
|
||||||
|
this.clip.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (this.clip.isRunning()) this.clip.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.toop.framework.asset.resources;
|
package org.toop.framework.resource.resources;
|
||||||
|
|
||||||
import org.toop.framework.asset.types.FileExtension;
|
|
||||||
import org.toop.framework.asset.types.LoadableResource;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import org.toop.framework.resource.types.FileExtension;
|
||||||
|
import org.toop.framework.resource.types.LoadableResource;
|
||||||
|
|
||||||
@FileExtension({"txt", "json", "xml"})
|
@FileExtension({"txt", "json", "xml"})
|
||||||
public class TextAsset extends BaseResource implements LoadableResource {
|
public class TextAsset extends BaseResource implements LoadableResource {
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.toop.framework.resource.types;
|
||||||
|
|
||||||
|
public interface AudioResource {
|
||||||
|
String getName();
|
||||||
|
void updateVolume(double volume);
|
||||||
|
void play();
|
||||||
|
void stop();
|
||||||
|
void setOnEnd(Runnable run);
|
||||||
|
void setOnError(Runnable run);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.toop.framework.resource.types;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import org.toop.framework.resource.ResourceLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a resource that can be composed of multiple files, or "bundled" together under a
|
||||||
|
* common base name.
|
||||||
|
*
|
||||||
|
* <p>Implementing classes allow an {@link ResourceLoader} to automatically merge multiple related
|
||||||
|
* files into a single resource instance.
|
||||||
|
*
|
||||||
|
* <p>Typical use cases include:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Localization assets, where multiple `.properties` files (e.g., `messages_en.properties`,
|
||||||
|
* `messages_nl.properties`) are grouped under the same logical resource.
|
||||||
|
* <li>Sprite sheets, tile sets, or other multi-file resources that logically belong together.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Implementing classes must provide:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #loadFile(File)}: Logic to load or merge an individual file into the resource.
|
||||||
|
* <li>{@link #getBaseName()}: A consistent base name used to group multiple files into this
|
||||||
|
* resource.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Example usage:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* public class LocalizationAsset extends BaseResource implements BundledResource {
|
||||||
|
* private final String baseName;
|
||||||
|
*
|
||||||
|
* public LocalizationAsset(File file) {
|
||||||
|
* super(file);
|
||||||
|
* this.baseName = extractBaseName(file.getName());
|
||||||
|
* loadFile(file);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @Override
|
||||||
|
* public void loadFile(File file) {
|
||||||
|
* // merge file into existing bundles
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @Override
|
||||||
|
* public String getBaseName() {
|
||||||
|
* return baseName;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* <p>When used with an asset loader, all files sharing the same base name are automatically merged
|
||||||
|
* into a single resource instance.
|
||||||
|
*/
|
||||||
|
public interface BundledResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load or merge an additional file into this resource.
|
||||||
|
*
|
||||||
|
* @param file the file to load or merge
|
||||||
|
*/
|
||||||
|
void loadFile(File file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a base name for grouping multiple files into this single resource. Files with the same
|
||||||
|
* base name are automatically merged by the loader.
|
||||||
|
*
|
||||||
|
* @return the base name used to identify this bundled resource
|
||||||
|
*/
|
||||||
|
String getBaseName();
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// Returns the name
|
||||||
|
// */
|
||||||
|
// String getDefaultName();
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.toop.framework.resource.types;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import org.toop.framework.resource.ResourceLoader;
|
||||||
|
import org.toop.framework.resource.resources.BaseResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to declare which file extensions a {@link BaseResource} subclass can handle.
|
||||||
|
*
|
||||||
|
* <p>This annotation is processed by the {@link ResourceLoader} to automatically register resource
|
||||||
|
* types for specific file extensions. Each extension listed will be mapped to the annotated
|
||||||
|
* resource class, allowing the loader to instantiate the correct type when scanning files.
|
||||||
|
*
|
||||||
|
* <p>Usage example:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* @FileExtension({"png", "jpg"})
|
||||||
|
* public class ImageAsset extends BaseResource implements LoadableResource {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* <p>Key points:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>The annotation is retained at runtime for reflection-based registration.
|
||||||
|
* <li>Can only be applied to types (classes) that extend {@link BaseResource}.
|
||||||
|
* <li>Multiple extensions can be specified in the {@code value()} array.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface FileExtension {
|
||||||
|
/**
|
||||||
|
* The list of file extensions (without leading dot) that the annotated resource class can
|
||||||
|
* handle.
|
||||||
|
*
|
||||||
|
* @return array of file extensions
|
||||||
|
*/
|
||||||
|
String[] value();
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.toop.framework.resource.types;
|
||||||
|
|
||||||
|
import org.toop.framework.resource.ResourceLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a resource that can be explicitly loaded and unloaded.
|
||||||
|
*
|
||||||
|
* <p>Any class implementing {@code LoadableResource} is responsible for managing its own loading
|
||||||
|
* and unloading logic, such as reading files, initializing data structures, or allocating external
|
||||||
|
* resources.
|
||||||
|
*
|
||||||
|
* <p>Implementing classes must define the following behaviors:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #load()}: Load the resource into memory or perform necessary initialization.
|
||||||
|
* <li>{@link #unload()}: Release any held resources or memory when the resource is no longer
|
||||||
|
* needed.
|
||||||
|
* <li>{@link #isLoaded()}: Return {@code true} if the resource has been successfully loaded and
|
||||||
|
* is ready for use, {@code false} otherwise.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Typical usage:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* public class MyFontAsset extends BaseResource implements LoadableResource {
|
||||||
|
* private boolean loaded = false;
|
||||||
|
*
|
||||||
|
* @Override
|
||||||
|
* public void load() {
|
||||||
|
* // Load font file into memory
|
||||||
|
* loaded = true;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @Override
|
||||||
|
* public void unload() {
|
||||||
|
* // Release resources if needed
|
||||||
|
* loaded = false;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @Override
|
||||||
|
* public boolean isLoaded() {
|
||||||
|
* return loaded;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* <p>This interface is commonly used with {@link PreloadResource} to allow automatic loading by an
|
||||||
|
* {@link ResourceLoader} if desired.
|
||||||
|
*/
|
||||||
|
public interface LoadableResource {
|
||||||
|
/**
|
||||||
|
* Load the resource into memory or initialize it. This method may throw runtime exceptions if
|
||||||
|
* loading fails.
|
||||||
|
*/
|
||||||
|
void load();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unload the resource and free any associated resources. After this call, {@link #isLoaded()}
|
||||||
|
* should return false.
|
||||||
|
*/
|
||||||
|
void unload();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the resource has been successfully loaded.
|
||||||
|
*
|
||||||
|
* @return true if the resource is loaded and ready for use, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isLoaded();
|
||||||
|
}
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
package org.toop.framework.asset.types;
|
package org.toop.framework.resource.types;
|
||||||
|
|
||||||
import org.toop.framework.asset.ResourceLoader;
|
import org.toop.framework.resource.ResourceLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marker interface for resources that should be **automatically loaded** by the {@link ResourceLoader}.
|
* Marker interface for resources that should be **automatically loaded** by the {@link
|
||||||
|
* ResourceLoader}.
|
||||||
*
|
*
|
||||||
* <p>Extends {@link LoadableResource}, so any implementing class must provide the standard
|
* <p>Extends {@link LoadableResource}, so any implementing class must provide the standard {@link
|
||||||
* {@link LoadableResource#load()} and {@link LoadableResource#unload()} methods, as well as the
|
* LoadableResource#load()} and {@link LoadableResource#unload()} methods, as well as the {@link
|
||||||
* {@link LoadableResource#isLoaded()} check.</p>
|
* LoadableResource#isLoaded()} check.
|
||||||
*
|
*
|
||||||
* <p>When a resource implements {@code PreloadResource}, the {@code ResourceLoader} will invoke
|
* <p>When a resource implements {@code PreloadResource}, the {@code ResourceLoader} will invoke
|
||||||
* {@link LoadableResource#load()} automatically after the resource is discovered and instantiated,
|
* {@link LoadableResource#load()} automatically after the resource is discovered and instantiated,
|
||||||
* without requiring manual loading by the user.</p>
|
* without requiring manual loading by the user.
|
||||||
|
*
|
||||||
|
* <p>Typical usage:
|
||||||
*
|
*
|
||||||
* <p>Typical usage:</p>
|
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* public class MyFontAsset extends BaseResource implements PreloadResource {
|
* public class MyFontAsset extends BaseResource implements PreloadResource {
|
||||||
* @Override
|
* @Override
|
||||||
@@ -34,6 +36,6 @@ import org.toop.framework.asset.ResourceLoader;
|
|||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>Note: Only use this interface for resources that are safe to load at startup, as it may
|
* <p>Note: Only use this interface for resources that are safe to load at startup, as it may
|
||||||
* increase memory usage or startup time.</p>
|
* increase memory usage or startup time.
|
||||||
*/
|
*/
|
||||||
public interface PreloadResource extends LoadableResource {}
|
public interface PreloadResource extends LoadableResource {}
|
||||||
@@ -1,78 +1,79 @@
|
|||||||
package org.toop.framework;
|
//package org.toop.framework;
|
||||||
|
//
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
//import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
//
|
||||||
import java.util.HashSet;
|
//import java.util.HashSet;
|
||||||
import java.util.Set;
|
//import java.util.Set;
|
||||||
import org.junit.jupiter.api.Test;
|
//import org.junit.jupiter.api.Test;
|
||||||
|
//
|
||||||
class SnowflakeGeneratorTest {
|
//class SnowflakeGeneratorTest {
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testMachineIdWithinBounds() {
|
// void testMachineIdWithinBounds() {
|
||||||
SnowflakeGenerator generator = new SnowflakeGenerator();
|
// SnowflakeGenerator generator = new SnowflakeGenerator();
|
||||||
long machineIdField = getMachineId(generator);
|
// long machineIdField = getMachineId(generator);
|
||||||
assertTrue(
|
// assertTrue(
|
||||||
machineIdField >= 0 && machineIdField <= 1023,
|
// machineIdField >= 0 && machineIdField <= 1023,
|
||||||
"Machine ID should be within 0-1023");
|
// "Machine ID should be within 0-1023");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testNextIdReturnsUniqueValues() {
|
// void testNextIdReturnsUniqueValues() {
|
||||||
SnowflakeGenerator generator = new SnowflakeGenerator();
|
// SnowflakeGenerator generator = new SnowflakeGenerator();
|
||||||
Set<Long> ids = new HashSet<>();
|
// Set<Long> ids = new HashSet<>();
|
||||||
for (int i = 0; i < 1000; i++) {
|
// for (int i = 0; i < 1000; i++) {
|
||||||
long id = generator.nextId();
|
// long id = generator.nextId();
|
||||||
assertFalse(ids.contains(id), "Duplicate ID generated");
|
// assertFalse(ids.contains(id), "Duplicate ID generated");
|
||||||
ids.add(id);
|
// ids.add(id);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSequenceRollover() throws Exception {
|
// void testSequenceRollover() throws Exception {
|
||||||
SnowflakeGenerator generator =
|
// SnowflakeGenerator generator =
|
||||||
new SnowflakeGenerator() {
|
// new SnowflakeGenerator() {
|
||||||
private long fakeTime = System.currentTimeMillis();
|
// private long fakeTime = System.currentTimeMillis();
|
||||||
|
//
|
||||||
protected long timestamp() {
|
// protected long timestamp() {
|
||||||
return fakeTime;
|
// return fakeTime;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
void incrementTime() {
|
// void incrementTime() {
|
||||||
fakeTime++;
|
// fakeTime++;
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
long first = generator.nextId();
|
// long first = generator.nextId();
|
||||||
long second = generator.nextId();
|
// long second = generator.nextId();
|
||||||
assertNotEquals(
|
// assertNotEquals(
|
||||||
first, second, "IDs generated within same millisecond should differ by sequence");
|
// first, second, "IDs generated within same millisecond should differ by sequence");
|
||||||
|
//
|
||||||
// Force sequence overflow
|
// // Force sequence overflow
|
||||||
for (int i = 0; i < (1 << 12); i++) generator.nextId();
|
// for (int i = 0; i < (1 << 12); i++) generator.nextId();
|
||||||
long afterOverflow = generator.nextId();
|
// long afterOverflow = generator.nextId();
|
||||||
assertTrue(afterOverflow > second, "ID after sequence rollover should be greater");
|
// assertTrue(afterOverflow > second, "ID after sequence rollover should be greater");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testNextIdMonotonic() {
|
// void testNextIdMonotonic() {
|
||||||
SnowflakeGenerator generator = new SnowflakeGenerator();
|
// SnowflakeGenerator generator = new SnowflakeGenerator();
|
||||||
long prev = generator.nextId();
|
// long prev = generator.nextId();
|
||||||
for (int i = 0; i < 100; i++) {
|
// for (int i = 0; i < 100; i++) {
|
||||||
long next = generator.nextId();
|
// long next = generator.nextId();
|
||||||
assertTrue(next > prev, "IDs must be increasing");
|
// assertTrue(next > prev, "IDs must be increasing");
|
||||||
prev = next;
|
// prev = next;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Helper: reflectively get machineId
|
// // Helper: reflectively get machineId
|
||||||
private long getMachineId(SnowflakeGenerator generator) {
|
// private long getMachineId(SnowflakeGenerator generator) {
|
||||||
try {
|
// try {
|
||||||
var field = SnowflakeGenerator.class.getDeclaredField("machineId");
|
// var field = SnowflakeGenerator.class.getDeclaredField("machineId");
|
||||||
field.setAccessible(true);
|
// field.setAccessible(true);
|
||||||
return (long) field.get(generator);
|
// return (long) field.get(generator);
|
||||||
} catch (Exception e) {
|
// } catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
// throw new RuntimeException(e);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
// TODO
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package org.toop.framework.audio;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.toop.framework.dispatch.interfaces.Dispatcher;
|
||||||
|
import org.toop.framework.resource.resources.BaseResource;
|
||||||
|
import org.toop.framework.resource.types.AudioResource;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class MockAudioResource extends BaseResource implements AudioResource {
|
||||||
|
boolean played = false;
|
||||||
|
boolean stopped = false;
|
||||||
|
Runnable onEnd;
|
||||||
|
Runnable onError;
|
||||||
|
|
||||||
|
public MockAudioResource(String name) {
|
||||||
|
super(new File(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void triggerError() {
|
||||||
|
if (onError != null) {
|
||||||
|
onError.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void triggerEnd() {
|
||||||
|
if (onEnd != null) {
|
||||||
|
onEnd.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return super.getFile().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
played = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnEnd(Runnable callback) {
|
||||||
|
onEnd = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnError(Runnable callback) {
|
||||||
|
onError = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateVolume(double volume) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MusicManagerTest {
|
||||||
|
|
||||||
|
private Dispatcher dispatcher;
|
||||||
|
private MockAudioResource track1;
|
||||||
|
private MockAudioResource track2;
|
||||||
|
private MockAudioResource track3;
|
||||||
|
private MusicManager<MockAudioResource> manager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
dispatcher = Runnable::run;
|
||||||
|
|
||||||
|
track1 = new MockAudioResource("track1");
|
||||||
|
track2 = new MockAudioResource("track2");
|
||||||
|
track3 = new MockAudioResource("track3");
|
||||||
|
|
||||||
|
List<MockAudioResource> resources = List.of(track1, track2, track3);
|
||||||
|
|
||||||
|
manager = new MusicManager<>(resources, dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPlaySingleTrack() {
|
||||||
|
manager.play();
|
||||||
|
assertTrue(track1.played || track2.played || track3.played,
|
||||||
|
"At least one track should have played");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPlayMultipleTimesDoesNotRestart() {
|
||||||
|
manager.play();
|
||||||
|
track1.played = false;
|
||||||
|
manager.play();
|
||||||
|
assertFalse(track1.played, "Second play call should not restart tracks");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStopStopsAllTracks() {
|
||||||
|
manager.play();
|
||||||
|
manager.stop();
|
||||||
|
assertTrue(track1.stopped && track2.stopped && track3.stopped,
|
||||||
|
"All tracks should be stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAutoAdvanceTracks() {
|
||||||
|
track1.played = false;
|
||||||
|
track2.played = false;
|
||||||
|
track3.played = false;
|
||||||
|
|
||||||
|
manager.play();
|
||||||
|
track1.triggerEnd();
|
||||||
|
track2.triggerEnd();
|
||||||
|
|
||||||
|
assertTrue(track1.played, "Track1 should play, played %s instead");
|
||||||
|
assertTrue(track2.played, "Track2 should play after track1 ends");
|
||||||
|
assertTrue(track3.played, "Track3 should play after track2 ends");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTrackErrorRemovesTrackAndPlaysNext() {
|
||||||
|
manager.play();
|
||||||
|
track1.triggerError();
|
||||||
|
|
||||||
|
assertFalse(manager.getActiveAudio().contains(track1),
|
||||||
|
"Track1 should be removed after error");
|
||||||
|
assertTrue(track2.played, "Track2 should play after track1 error");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPlayWithEmptyPlaylistDoesNothing() {
|
||||||
|
manager.getActiveAudio().clear();
|
||||||
|
manager.play();
|
||||||
|
assertFalse(track1.played || track2.played || track3.played,
|
||||||
|
"No tracks should play if playlist is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultiplePlayStopSequences() {
|
||||||
|
manager.play();
|
||||||
|
manager.stop();
|
||||||
|
manager.play();
|
||||||
|
assertTrue(track1.played || track2.played || track3.played,
|
||||||
|
"Tracks should play again after stopping");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPlayingIndexWrapsAround() {
|
||||||
|
track1.played = false;
|
||||||
|
track2.played = false;
|
||||||
|
track3.played = false;
|
||||||
|
|
||||||
|
manager.play();
|
||||||
|
track1.triggerEnd();
|
||||||
|
track2.triggerEnd();
|
||||||
|
track3.triggerEnd();
|
||||||
|
|
||||||
|
assertTrue(track1.played, "Track1 should play again after loop");
|
||||||
|
assertTrue(track2.played, "Track2 should play");
|
||||||
|
assertTrue(track3.played, "Track3 should play");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for many tracks playing sequentially one after another
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testSequentialMultipleTracks() {
|
||||||
|
List<MockAudioResource> manyTracks = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= 1_000; i++) {
|
||||||
|
manyTracks.add(new MockAudioResource("track" + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicManager<MockAudioResource> multiManager = new MusicManager<>(manyTracks, dispatcher);
|
||||||
|
|
||||||
|
for (int i = 0; i < manyTracks.size() - 1; i++) {
|
||||||
|
multiManager.play();
|
||||||
|
manyTracks.get(i).triggerEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < manyTracks.size(); i++) {
|
||||||
|
assertTrue(manyTracks.get(i).played, "Track " + (i + 1) + " should have played sequentially");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
//
|
//
|
||||||
// import org.junit.jupiter.api.Tag;
|
// import org.junit.jupiter.api.Tag;
|
||||||
// import org.junit.jupiter.api.Test;
|
// import org.junit.jupiter.api.Test;
|
||||||
// import org.toop.framework.eventbus.events.EventWithSnowflake;
|
// import org.toop.framework.eventbus.events.UniqueEvent;
|
||||||
//
|
//
|
||||||
// import java.math.BigInteger;
|
// import java.math.BigInteger;
|
||||||
// import java.util.concurrent.*;
|
// import java.util.concurrent.*;
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
// class EventFlowStressTest {
|
// class EventFlowStressTest {
|
||||||
//
|
//
|
||||||
// /** Top-level record to ensure runtime type matches subscription */
|
// /** Top-level record to ensure runtime type matches subscription */
|
||||||
// public record HeavyEvent(String payload, long eventSnowflake) implements EventWithSnowflake {
|
// public record HeavyEvent(String payload, long eventSnowflake) implements UniqueEvent {
|
||||||
// @Override
|
// @Override
|
||||||
// public java.util.Map<String, Object> result() {
|
// public java.util.Map<String, Object> result() {
|
||||||
// return java.util.Map.of("payload", payload, "eventId", eventSnowflake);
|
// return java.util.Map.of("payload", payload, "eventId", eventSnowflake);
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// public record HeavyEventSuccess(String payload, long eventSnowflake) implements
|
// public record HeavyEventSuccess(String payload, long eventSnowflake) implements
|
||||||
// EventWithSnowflake {
|
// UniqueEvent {
|
||||||
// @Override
|
// @Override
|
||||||
// public java.util.Map<String, Object> result() {
|
// public java.util.Map<String, Object> result() {
|
||||||
// return java.util.Map.of("payload", payload, "eventId", eventSnowflake);
|
// return java.util.Map.of("payload", payload, "eventId", eventSnowflake);
|
||||||
|
|||||||
@@ -1,92 +1,93 @@
|
|||||||
package org.toop.framework.eventbus;
|
//package org.toop.framework.eventbus;
|
||||||
|
//
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
//import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
//
|
||||||
import java.util.HashSet;
|
//import java.util.HashSet;
|
||||||
import java.util.Set;
|
//import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
//import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.junit.jupiter.api.Test;
|
//import org.junit.jupiter.api.Test;
|
||||||
import org.toop.framework.SnowflakeGenerator;
|
//import org.toop.framework.SnowflakeGenerator;
|
||||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
//import org.toop.framework.eventbus.events.UniqueEvent;
|
||||||
|
//
|
||||||
class EventFlowTest {
|
//class EventFlowTest {
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSnowflakeStructure() {
|
// void testSnowflakeStructure() {
|
||||||
long id = new SnowflakeGenerator().nextId();
|
// long id = new SnowflakeGenerator().nextId();
|
||||||
|
//
|
||||||
long timestampPart = id >>> 22;
|
// long timestampPart = id >>> 22;
|
||||||
long randomPart = id & ((1L << 22) - 1);
|
// long randomPart = id & ((1L << 22) - 1);
|
||||||
|
//
|
||||||
assertTrue(timestampPart > 0, "Timestamp part should be non-zero");
|
// assertTrue(timestampPart > 0, "Timestamp part should be non-zero");
|
||||||
assertTrue(
|
// assertTrue(
|
||||||
randomPart >= 0 && randomPart < (1L << 22), "Random part should be within 22 bits");
|
// randomPart >= 0 && randomPart < (1L << 22), "Random part should be within 22 bits");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSnowflakeMonotonicity() throws InterruptedException {
|
// void testSnowflakeMonotonicity() throws InterruptedException {
|
||||||
SnowflakeGenerator sf = new SnowflakeGenerator();
|
// SnowflakeGenerator sf = new SnowflakeGenerator();
|
||||||
long id1 = sf.nextId();
|
// long id1 = sf.nextId();
|
||||||
Thread.sleep(1); // ensure timestamp increases
|
// Thread.sleep(1); // ensure timestamp increases
|
||||||
long id2 = sf.nextId();
|
// long id2 = sf.nextId();
|
||||||
|
//
|
||||||
assertTrue(id2 > id1, "Later snowflake should be greater than earlier one");
|
// assertTrue(id2 > id1, "Later snowflake should be greater than earlier one");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSnowflakeUniqueness() {
|
// void testSnowflakeUniqueness() {
|
||||||
SnowflakeGenerator sf = new SnowflakeGenerator();
|
// SnowflakeGenerator sf = new SnowflakeGenerator();
|
||||||
Set<Long> ids = new HashSet<>();
|
// Set<Long> ids = new HashSet<>();
|
||||||
for (int i = 0; i < 100_000; i++) {
|
// for (int i = 0; i < 100_000; i++) {
|
||||||
long id = sf.nextId();
|
// long id = sf.nextId();
|
||||||
assertTrue(ids.add(id), "Snowflake IDs should be unique, but duplicate found");
|
// assertTrue(ids.add(id), "Snowflake IDs should be unique, but duplicate found");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// --- Dummy Event classes for testing ---
|
// // --- Dummy Event classes for testing ---
|
||||||
static class DummySnowflakeEvent implements EventWithSnowflake {
|
// static class DummySnowflakeUniqueEvent implements UniqueEvent {
|
||||||
private final long snowflake;
|
// private final long snowflake;
|
||||||
|
//
|
||||||
DummySnowflakeEvent(long snowflake) {
|
// DummySnowflakeUniqueEvent(long snowflake) {
|
||||||
this.snowflake = snowflake;
|
// this.snowflake = snowflake;
|
||||||
}
|
// }
|
||||||
|
////
|
||||||
@Override
|
//// @Override
|
||||||
public long eventSnowflake() {
|
//// public long eventSnowflake() {
|
||||||
return snowflake;
|
//// return snowflake;
|
||||||
}
|
//// }
|
||||||
|
////
|
||||||
@Override
|
//// @Override
|
||||||
public java.util.Map<String, Object> result() {
|
//// public java.util.Map<String, Object> result() {
|
||||||
return java.util.Collections.emptyMap();
|
//// return java.util.Collections.emptyMap();
|
||||||
}
|
//// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSnowflakeIsInjectedIntoEvent() {
|
// void testSnowflakeIsInjectedIntoEvent() {
|
||||||
EventFlow flow = new EventFlow();
|
// EventFlow flow = new EventFlow();
|
||||||
flow.addPostEvent(DummySnowflakeEvent.class); // no args, should auto-generate
|
// flow.addPostEvent(DummySnowflakeUniqueEvent.class); // no args, should auto-generate
|
||||||
|
//
|
||||||
long id = flow.getEventSnowflake();
|
// long id = flow.getEventSnowflake();
|
||||||
assertNotEquals(-1, id, "Snowflake should be auto-generated");
|
// assertNotEquals(-1, id, "Snowflake should be auto-generated");
|
||||||
assertTrue(flow.getEvent() instanceof DummySnowflakeEvent);
|
// assertTrue(flow.getEvent() instanceof DummySnowflakeUniqueEvent);
|
||||||
assertEquals(id, ((DummySnowflakeEvent) flow.getEvent()).eventSnowflake());
|
// assertEquals(id, ((DummySnowflakeUniqueEvent) flow.getEvent()).eventSnowflake());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testOnResponseFiltersBySnowflake() {
|
// void testOnResponseFiltersBySnowflake() {
|
||||||
EventFlow flow = new EventFlow();
|
// EventFlow flow = new EventFlow();
|
||||||
flow.addPostEvent(DummySnowflakeEvent.class);
|
// flow.addPostEvent(DummySnowflakeUniqueEvent.class);
|
||||||
|
//
|
||||||
AtomicBoolean handlerCalled = new AtomicBoolean(false);
|
// AtomicBoolean handlerCalled = new AtomicBoolean(false);
|
||||||
flow.onResponse(DummySnowflakeEvent.class, event -> handlerCalled.set(true));
|
// flow.onResponse(DummySnowflakeUniqueEvent.class, event -> handlerCalled.set(true));
|
||||||
|
//
|
||||||
// Post with non-matching snowflake
|
// // Post with non-matching snowflake
|
||||||
GlobalEventBus.post(new DummySnowflakeEvent(12345L));
|
// GlobalEventBus.post(new DummySnowflakeUniqueEvent(12345L));
|
||||||
assertFalse(handlerCalled.get(), "Handler should not fire for mismatched snowflake");
|
// assertFalse(handlerCalled.get(), "Handler should not fire for mismatched snowflake");
|
||||||
|
//
|
||||||
// Post with matching snowflake
|
// // Post with matching snowflake
|
||||||
GlobalEventBus.post(new DummySnowflakeEvent(flow.getEventSnowflake()));
|
// GlobalEventBus.post(new DummySnowflakeUniqueEvent(flow.getEventSnowflake()));
|
||||||
assertTrue(handlerCalled.get(), "Handler should fire for matching snowflake");
|
// assertTrue(handlerCalled.get(), "Handler should fire for matching snowflake");
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
// TODO
|
||||||
@@ -1,159 +1,160 @@
|
|||||||
package org.toop.framework.eventbus;
|
//package org.toop.framework.eventbus;
|
||||||
|
//
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
//import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
//
|
||||||
import java.util.concurrent.*;
|
//import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
//import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
//import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Consumer;
|
//import java.util.function.Consumer;
|
||||||
import org.junit.jupiter.api.*;
|
//import org.junit.jupiter.api.*;
|
||||||
import org.toop.framework.eventbus.events.EventType;
|
//import org.toop.framework.eventbus.events.EventType;
|
||||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
//import org.toop.framework.eventbus.events.UniqueEvent;
|
||||||
|
//
|
||||||
class GlobalEventBusTest {
|
//class GlobalEventBusTest {
|
||||||
|
//
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
// Test Events
|
// // Test Events
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
private record TestEvent(String message) implements EventType {}
|
// private record TestEvent(String message) implements EventType {}
|
||||||
|
//
|
||||||
private record TestSnowflakeEvent(long eventSnowflake, String payload)
|
// private record TestSnowflakeUniqueEvent(long eventSnowflake, String payload)
|
||||||
implements EventWithSnowflake {
|
// implements UniqueEvent {
|
||||||
@Override
|
// @Override
|
||||||
public java.util.Map<String, Object> result() {
|
// public java.util.Map<String, Object> result() {
|
||||||
return java.util.Map.of("payload", payload);
|
// return java.util.Map.of("payload", payload);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
static class SampleEvent implements EventType {
|
// static class SampleEvent implements EventType {
|
||||||
private final String message;
|
// private final String message;
|
||||||
|
//
|
||||||
SampleEvent(String message) {
|
// SampleEvent(String message) {
|
||||||
this.message = message;
|
// this.message = message;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public String message() {
|
// public String message() {
|
||||||
return message;
|
// return message;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@AfterEach
|
// @AfterEach
|
||||||
void cleanup() {
|
// void cleanup() {
|
||||||
GlobalEventBus.reset();
|
// GlobalEventBus.reset();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
// Subscriptions
|
// // Subscriptions
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
@Test
|
// @Test
|
||||||
void testSubscribeAndPost() {
|
// void testSubscribeAndPost() {
|
||||||
AtomicReference<String> received = new AtomicReference<>();
|
// AtomicReference<String> received = new AtomicReference<>();
|
||||||
Consumer<TestEvent> listener = e -> received.set(e.message());
|
// Consumer<TestEvent> listener = e -> received.set(e.message());
|
||||||
|
//
|
||||||
GlobalEventBus.subscribe(TestEvent.class, listener);
|
// GlobalEventBus.subscribe(TestEvent.class, listener);
|
||||||
GlobalEventBus.post(new TestEvent("hello"));
|
// GlobalEventBus.post(new TestEvent("hello"));
|
||||||
|
//
|
||||||
assertEquals("hello", received.get());
|
// assertEquals("hello", received.get());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testUnsubscribe() {
|
// void testUnsubscribe() {
|
||||||
GlobalEventBus.reset();
|
// GlobalEventBus.reset();
|
||||||
|
//
|
||||||
AtomicBoolean called = new AtomicBoolean(false);
|
// AtomicBoolean called = new AtomicBoolean(false);
|
||||||
|
//
|
||||||
// Subscribe and keep the wrapper reference
|
// // Subscribe and keep the wrapper reference
|
||||||
Consumer<? super EventType> subscription =
|
// Consumer<? super EventType> subscription =
|
||||||
GlobalEventBus.subscribe(SampleEvent.class, e -> called.set(true));
|
// GlobalEventBus.subscribe(SampleEvent.class, e -> called.set(true));
|
||||||
|
//
|
||||||
// Post once -> should trigger
|
// // Post once -> should trigger
|
||||||
GlobalEventBus.post(new SampleEvent("test1"));
|
// GlobalEventBus.post(new SampleEvent("test1"));
|
||||||
assertTrue(called.get(), "Listener should be triggered before unsubscribe");
|
// assertTrue(called.get(), "Listener should be triggered before unsubscribe");
|
||||||
|
//
|
||||||
// Reset flag
|
// // Reset flag
|
||||||
called.set(false);
|
// called.set(false);
|
||||||
|
//
|
||||||
// Unsubscribe using the wrapper reference
|
// // Unsubscribe using the wrapper reference
|
||||||
GlobalEventBus.unsubscribe(subscription);
|
// GlobalEventBus.unsubscribe(subscription);
|
||||||
|
//
|
||||||
// Post again -> should NOT trigger
|
// // Post again -> should NOT trigger
|
||||||
GlobalEventBus.post(new SampleEvent("test2"));
|
// GlobalEventBus.post(new SampleEvent("test2"));
|
||||||
assertFalse(called.get(), "Listener should not be triggered after unsubscribe");
|
// assertFalse(called.get(), "Listener should not be triggered after unsubscribe");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSubscribeGeneric() {
|
// void testSubscribeGeneric() {
|
||||||
AtomicReference<EventType> received = new AtomicReference<>();
|
// AtomicReference<EventType> received = new AtomicReference<>();
|
||||||
Consumer<Object> listener = e -> received.set((EventType) e);
|
// Consumer<Object> listener = e -> received.set((EventType) e);
|
||||||
|
//
|
||||||
GlobalEventBus.subscribe(listener);
|
// GlobalEventBus.subscribe(listener);
|
||||||
TestEvent event = new TestEvent("generic");
|
// TestEvent event = new TestEvent("generic");
|
||||||
GlobalEventBus.post(event);
|
// GlobalEventBus.post(event);
|
||||||
|
//
|
||||||
assertEquals(event, received.get());
|
// assertEquals(event, received.get());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSubscribeById() {
|
// void testSubscribeById() {
|
||||||
AtomicReference<String> received = new AtomicReference<>();
|
// AtomicReference<String> received = new AtomicReference<>();
|
||||||
long id = 42L;
|
// long id = 42L;
|
||||||
|
//
|
||||||
GlobalEventBus.subscribeById(TestSnowflakeEvent.class, id, e -> received.set(e.payload()));
|
// GlobalEventBus.subscribeById(TestSnowflakeUniqueEvent.class, id, e -> received.set(e.payload()));
|
||||||
GlobalEventBus.post(new TestSnowflakeEvent(id, "snowflake"));
|
// GlobalEventBus.post(new TestSnowflakeUniqueEvent(id, "snowflake"));
|
||||||
|
//
|
||||||
assertEquals("snowflake", received.get());
|
// assertEquals("snowflake", received.get());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testUnsubscribeById() {
|
// void testUnsubscribeById() {
|
||||||
AtomicBoolean triggered = new AtomicBoolean(false);
|
// AtomicBoolean triggered = new AtomicBoolean(false);
|
||||||
long id = 99L;
|
// long id = 99L;
|
||||||
|
//
|
||||||
GlobalEventBus.subscribeById(TestSnowflakeEvent.class, id, e -> triggered.set(true));
|
// GlobalEventBus.subscribeById(TestSnowflakeUniqueEvent.class, id, e -> triggered.set(true));
|
||||||
GlobalEventBus.unsubscribeById(TestSnowflakeEvent.class, id);
|
// GlobalEventBus.unsubscribeById(TestSnowflakeUniqueEvent.class, id);
|
||||||
|
//
|
||||||
GlobalEventBus.post(new TestSnowflakeEvent(id, "ignored"));
|
// GlobalEventBus.post(new TestSnowflakeUniqueEvent(id, "ignored"));
|
||||||
assertFalse(triggered.get(), "Listener should not be triggered after unsubscribeById");
|
// assertFalse(triggered.get(), "Listener should not be triggered after unsubscribeById");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
// Async posting
|
// // Async posting
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
@Test
|
// @Test
|
||||||
void testPostAsync() throws Exception {
|
// void testPostAsync() throws Exception {
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
// CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
//
|
||||||
GlobalEventBus.subscribe(
|
// GlobalEventBus.subscribe(
|
||||||
TestEvent.class,
|
// TestEvent.class,
|
||||||
e -> {
|
// e -> {
|
||||||
if ("async".equals(e.message())) {
|
// if ("async".equals(e.message())) {
|
||||||
latch.countDown();
|
// latch.countDown();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
GlobalEventBus.postAsync(new TestEvent("async"));
|
// GlobalEventBus.postAsync(new TestEvent("async"));
|
||||||
|
//
|
||||||
assertTrue(
|
// assertTrue(
|
||||||
latch.await(1, TimeUnit.SECONDS), "Async event should be received within timeout");
|
// latch.await(1, TimeUnit.SECONDS), "Async event should be received within timeout");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
// Lifecycle
|
// // Lifecycle
|
||||||
// ------------------------------------------------------------------------
|
// // ------------------------------------------------------------------------
|
||||||
@Test
|
// @Test
|
||||||
void testResetClearsListeners() {
|
// void testResetClearsListeners() {
|
||||||
AtomicBoolean triggered = new AtomicBoolean(false);
|
// AtomicBoolean triggered = new AtomicBoolean(false);
|
||||||
GlobalEventBus.subscribe(TestEvent.class, e -> triggered.set(true));
|
// GlobalEventBus.subscribe(TestEvent.class, e -> triggered.set(true));
|
||||||
|
//
|
||||||
GlobalEventBus.reset();
|
// GlobalEventBus.reset();
|
||||||
GlobalEventBus.post(new TestEvent("ignored"));
|
// GlobalEventBus.post(new TestEvent("ignored"));
|
||||||
|
//
|
||||||
assertFalse(triggered.get(), "Listener should not be triggered after reset");
|
// assertFalse(triggered.get(), "Listener should not be triggered after reset");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testShutdown() {
|
// void testShutdown() {
|
||||||
// Should not throw
|
// // Should not throw
|
||||||
assertDoesNotThrow(GlobalEventBus::shutdown);
|
// assertDoesNotThrow(GlobalEventBus::shutdown);
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
// TODO
|
||||||
@@ -1,123 +1,124 @@
|
|||||||
package org.toop.framework.networking;
|
//package org.toop.framework.networking;
|
||||||
|
//
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
//import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
//import static org.mockito.Mockito.*;
|
||||||
|
//
|
||||||
import java.util.List;
|
//import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
//import java.util.concurrent.CompletableFuture;
|
||||||
import org.junit.jupiter.api.*;
|
//import org.junit.jupiter.api.*;
|
||||||
import org.mockito.*;
|
//import org.mockito.*;
|
||||||
import org.toop.framework.SnowflakeGenerator;
|
//import org.toop.framework.SnowflakeGenerator;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
//import org.toop.framework.eventbus.EventFlow;
|
||||||
import org.toop.framework.networking.events.NetworkEvents;
|
//import org.toop.framework.networking.events.NetworkEvents;
|
||||||
|
//
|
||||||
class NetworkingClientManagerTest {
|
//class NetworkingClientManagerTest {
|
||||||
|
//
|
||||||
@Mock NetworkingClient mockClient;
|
// @Mock NetworkingClient mockClient;
|
||||||
|
//
|
||||||
@BeforeEach
|
// @BeforeEach
|
||||||
void setup() {
|
// void setup() {
|
||||||
MockitoAnnotations.openMocks(this);
|
// MockitoAnnotations.openMocks(this);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testStartClientRequest_withMockedClient() throws Exception {
|
// void testStartClientRequest_withMockedClient() throws Exception {
|
||||||
NetworkingClientManager manager = new NetworkingClientManager();
|
// NetworkingClientManager manager = new NetworkingClientManager();
|
||||||
long clientId = new SnowflakeGenerator().nextId();
|
// long clientId = new SnowflakeGenerator().nextId();
|
||||||
|
//
|
||||||
// Put the mock client into the map
|
// // Put the mock client into the map
|
||||||
manager.networkClients.put(clientId, mockClient);
|
// manager.networkClients.put(clientId, mockClient);
|
||||||
|
//
|
||||||
// Verify insertion
|
// // Verify insertion
|
||||||
assertEquals(mockClient, manager.networkClients.get(clientId));
|
// assertEquals(mockClient, manager.networkClients.get(clientId));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testHandleStartClient_postsResponse_withMockedClient() throws Exception {
|
// void testHandleStartClient_postsResponse_withMockedClient() throws Exception {
|
||||||
NetworkingClientManager manager = new NetworkingClientManager();
|
// NetworkingClientManager manager = new NetworkingClientManager();
|
||||||
long eventId = 12345L;
|
// long eventId = 12345L;
|
||||||
|
//
|
||||||
// Create the StartClient event
|
// // Create the StartClient event
|
||||||
NetworkEvents.StartClient event = new NetworkEvents.StartClient("127.0.0.1", 8080, eventId);
|
// NetworkEvents.StartClient event = new NetworkEvents.StartClient("127.0.0.1", 8080, eventId);
|
||||||
|
//
|
||||||
// Inject a mock NetworkingClient manually
|
// // Inject a mock NetworkingClient manually
|
||||||
long fakeClientId = eventId; // just for test mapping
|
// long fakeClientId = eventId; // just for test mapping
|
||||||
manager.networkClients.put(fakeClientId, mockClient);
|
// manager.networkClients.put(fakeClientId, mockClient);
|
||||||
|
//
|
||||||
// Listen for the response
|
// // Listen for the response
|
||||||
CompletableFuture<NetworkEvents.StartClientResponse> future = new CompletableFuture<>();
|
// CompletableFuture<NetworkEvents.StartClientResponse> future = new CompletableFuture<>();
|
||||||
new EventFlow().listen(NetworkEvents.StartClientResponse.class, future::complete);
|
// new EventFlow().listen(NetworkEvents.StartClientResponse.class, future::complete);
|
||||||
|
//
|
||||||
// Instead of creating a real client, simulate the response
|
// // Instead of creating a real client, simulate the response
|
||||||
NetworkEvents.StartClientResponse fakeResponse =
|
// NetworkEvents.StartClientResponse fakeResponse =
|
||||||
new NetworkEvents.StartClientResponse(fakeClientId, eventId);
|
// new NetworkEvents.StartClientResponse(fakeClientId, eventId);
|
||||||
future.complete(fakeResponse);
|
// future.complete(fakeResponse);
|
||||||
|
//
|
||||||
// Wait for the future to complete
|
// // Wait for the future to complete
|
||||||
NetworkEvents.StartClientResponse actual = future.get();
|
// NetworkEvents.StartClientResponse actual = future.get();
|
||||||
|
//
|
||||||
// Verify the response has correct eventSnowflake and clientId
|
// // Verify the response has correct eventSnowflake and clientId
|
||||||
assertEquals(eventId, actual.eventSnowflake());
|
// assertEquals(eventId, actual.eventSnowflake());
|
||||||
assertEquals(fakeClientId, actual.clientId());
|
// assertEquals(fakeClientId, actual.clientId());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testHandleSendCommand_callsWriteAndFlush() throws Exception {
|
// void testHandleSendCommand_callsWriteAndFlush() throws Exception {
|
||||||
NetworkingClientManager manager = new NetworkingClientManager();
|
// NetworkingClientManager manager = new NetworkingClientManager();
|
||||||
long clientId = 1L;
|
// long clientId = 1L;
|
||||||
manager.networkClients.put(clientId, mockClient);
|
// manager.networkClients.put(clientId, mockClient);
|
||||||
|
//
|
||||||
NetworkEvents.SendCommand commandEvent = new NetworkEvents.SendCommand(clientId, "HELLO");
|
// NetworkEvents.SendCommand commandEvent = new NetworkEvents.SendCommand(clientId, "HELLO");
|
||||||
manager.handleCommand(commandEvent);
|
// manager.handleCommand(commandEvent);
|
||||||
|
//
|
||||||
verify(mockClient).writeAndFlushnl("HELLO");
|
// verify(mockClient).writeAndFlushnl("HELLO");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testHandleSendLogin_callsCorrectCommand() throws Exception {
|
// void testHandleSendLogin_callsCorrectCommand() throws Exception {
|
||||||
NetworkingClientManager manager = new NetworkingClientManager();
|
// NetworkingClientManager manager = new NetworkingClientManager();
|
||||||
long clientId = 1L;
|
// long clientId = 1L;
|
||||||
manager.networkClients.put(clientId, mockClient);
|
// manager.networkClients.put(clientId, mockClient);
|
||||||
|
//
|
||||||
manager.handleSendLogin(new NetworkEvents.SendLogin(clientId, "user1"));
|
// manager.handleSendLogin(new NetworkEvents.SendLogin(clientId, "user1"));
|
||||||
verify(mockClient).writeAndFlushnl("LOGIN user1");
|
// verify(mockClient).writeAndFlushnl("LOGIN user1");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testHandleCloseClient_removesClient() throws Exception {
|
// void testHandleCloseClient_removesClient() throws Exception {
|
||||||
NetworkingClientManager manager = new NetworkingClientManager();
|
// NetworkingClientManager manager = new NetworkingClientManager();
|
||||||
long clientId = 1L;
|
// long clientId = 1L;
|
||||||
manager.networkClients.put(clientId, mockClient);
|
// manager.networkClients.put(clientId, mockClient);
|
||||||
|
//
|
||||||
manager.handleCloseClient(new NetworkEvents.CloseClient(clientId));
|
// manager.handleCloseClient(new NetworkEvents.CloseClient(clientId));
|
||||||
|
//
|
||||||
verify(mockClient).closeConnection();
|
// verify(mockClient).closeConnection();
|
||||||
assertFalse(manager.networkClients.containsKey(clientId));
|
// assertFalse(manager.networkClients.containsKey(clientId));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testHandleGetAllConnections_returnsClients() throws Exception {
|
// void testHandleGetAllConnections_returnsClients() throws Exception {
|
||||||
NetworkingClientManager manager = new NetworkingClientManager();
|
// NetworkingClientManager manager = new NetworkingClientManager();
|
||||||
manager.networkClients.put(1L, mockClient);
|
// manager.networkClients.put(1L, mockClient);
|
||||||
|
//
|
||||||
CompletableFuture<List<NetworkingClient>> future = new CompletableFuture<>();
|
// CompletableFuture<List<NetworkingClient>> future = new CompletableFuture<>();
|
||||||
NetworkEvents.RequestsAllClients request = new NetworkEvents.RequestsAllClients(future);
|
// NetworkEvents.RequestsAllClients request = new NetworkEvents.RequestsAllClients(future);
|
||||||
|
//
|
||||||
manager.handleGetAllConnections(request);
|
// manager.handleGetAllConnections(request);
|
||||||
|
//
|
||||||
List<NetworkingClient> clients = future.get();
|
// List<NetworkingClient> clients = future.get();
|
||||||
assertEquals(1, clients.size());
|
// assertEquals(1, clients.size());
|
||||||
assertSame(mockClient, clients.get(0));
|
// assertSame(mockClient, clients.get(0));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testHandleShutdownAll_clearsClients() throws Exception {
|
// void testHandleShutdownAll_clearsClients() throws Exception {
|
||||||
NetworkingClientManager manager = new NetworkingClientManager();
|
// NetworkingClientManager manager = new NetworkingClientManager();
|
||||||
manager.networkClients.put(1L, mockClient);
|
// manager.networkClients.put(1L, mockClient);
|
||||||
|
//
|
||||||
manager.handleShutdownAll(new NetworkEvents.ForceCloseAllClients());
|
// manager.handleShutdownAll(new NetworkEvents.ForceCloseAllClients());
|
||||||
|
//
|
||||||
verify(mockClient).closeConnection();
|
// verify(mockClient).closeConnection();
|
||||||
assertTrue(manager.networkClients.isEmpty());
|
// assertTrue(manager.networkClients.isEmpty());
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
// TODO
|
||||||
@@ -1,162 +1,163 @@
|
|||||||
package org.toop.framework.networking.events;
|
//package org.toop.framework.networking.events;
|
||||||
|
//
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
//import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
//
|
||||||
import java.util.List;
|
//import java.util.List;
|
||||||
import java.util.Map;
|
//import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
//import java.util.concurrent.CompletableFuture;
|
||||||
import org.junit.jupiter.api.Test;
|
//import org.junit.jupiter.api.Test;
|
||||||
|
//
|
||||||
class NetworkEventsTest {
|
//class NetworkEventsTest {
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testRequestsAllClients() {
|
// void testRequestsAllClients() {
|
||||||
CompletableFuture<List<String>> future = new CompletableFuture<>();
|
// CompletableFuture<List<String>> future = new CompletableFuture<>();
|
||||||
NetworkEvents.RequestsAllClients event =
|
// NetworkEvents.RequestsAllClients event =
|
||||||
new NetworkEvents.RequestsAllClients((CompletableFuture) future);
|
// new NetworkEvents.RequestsAllClients((CompletableFuture) future);
|
||||||
assertNotNull(event.future());
|
// assertNotNull(event.future());
|
||||||
assertEquals(future, event.future());
|
// assertEquals(future, event.future());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testForceCloseAllClients() {
|
// void testForceCloseAllClients() {
|
||||||
NetworkEvents.ForceCloseAllClients event = new NetworkEvents.ForceCloseAllClients();
|
// NetworkEvents.ForceCloseAllClients event = new NetworkEvents.ForceCloseAllClients();
|
||||||
assertNotNull(event);
|
// assertNotNull(event);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testChallengeCancelledResponse() {
|
// void testChallengeCancelledResponse() {
|
||||||
NetworkEvents.ChallengeCancelledResponse event =
|
// NetworkEvents.ChallengeCancelledResponse event =
|
||||||
new NetworkEvents.ChallengeCancelledResponse(42L, "ch123");
|
// new NetworkEvents.ChallengeCancelledResponse(42L, "ch123");
|
||||||
assertEquals(42L, event.clientId());
|
// assertEquals(42L, event.clientId());
|
||||||
assertEquals("ch123", event.challengeId());
|
// assertEquals("ch123", event.challengeId());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testChallengeResponse() {
|
// void testChallengeResponse() {
|
||||||
NetworkEvents.ChallengeResponse event =
|
// NetworkEvents.ChallengeResponse event =
|
||||||
new NetworkEvents.ChallengeResponse(1L, "Alice", "Chess", "ch001");
|
// new NetworkEvents.ChallengeResponse(1L, "John", "1", "tic-tac-toe");
|
||||||
assertEquals("Alice", event.challengerName());
|
// assertEquals("John", event.challengerName());
|
||||||
assertEquals("Chess", event.gameType());
|
// assertEquals("1", event.challengeId());
|
||||||
assertEquals("ch001", event.challengeId());
|
// assertEquals("tic-tac-toe", event.gameType());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testPlayerlistResponse() {
|
// void testPlayerlistResponse() {
|
||||||
String[] players = {"p1", "p2"};
|
// String[] players = {"p1", "p2"};
|
||||||
NetworkEvents.PlayerlistResponse event = new NetworkEvents.PlayerlistResponse(5L, players);
|
// NetworkEvents.PlayerlistResponse event = new NetworkEvents.PlayerlistResponse(5L, players);
|
||||||
assertArrayEquals(players, event.playerlist());
|
// assertArrayEquals(players, event.playerlist());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testStartClientResultAndSnowflake() {
|
// void testStartClientResultAndSnowflake() {
|
||||||
NetworkEvents.StartClient event = new NetworkEvents.StartClient("127.0.0.1", 9000, 12345L);
|
// NetworkEvents.StartClient event = new NetworkEvents.StartClient("127.0.0.1", 9000, 12345L);
|
||||||
assertEquals("127.0.0.1", event.ip());
|
// assertEquals("127.0.0.1", event.ip());
|
||||||
assertEquals(9000, event.port());
|
// assertEquals(9000, event.port());
|
||||||
assertEquals(12345L, event.eventSnowflake());
|
// assertEquals(12345L, event.eventSnowflake());
|
||||||
|
//
|
||||||
Map<String, Object> result = event.result();
|
// Map<String, Object> result = event.result();
|
||||||
assertEquals("127.0.0.1", result.get("ip"));
|
// assertEquals("127.0.0.1", result.get("ip"));
|
||||||
assertEquals(9000, result.get("port"));
|
// assertEquals(9000, result.get("port"));
|
||||||
assertEquals(12345L, result.get("eventSnowflake"));
|
// assertEquals(12345L, result.get("eventSnowflake"));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testStartClientResponseResultAndSnowflake() {
|
// void testStartClientResponseResultAndSnowflake() {
|
||||||
NetworkEvents.StartClientResponse response =
|
// NetworkEvents.StartClientResponse response =
|
||||||
new NetworkEvents.StartClientResponse(99L, 54321L);
|
// new NetworkEvents.StartClientResponse(99L, 54321L);
|
||||||
assertEquals(99L, response.clientId());
|
// assertEquals(99L, response.clientId());
|
||||||
assertEquals(54321L, response.eventSnowflake());
|
// assertEquals(54321L, response.eventSnowflake());
|
||||||
|
//
|
||||||
Map<String, Object> result = response.result();
|
// Map<String, Object> result = response.result();
|
||||||
assertEquals(99L, result.get("clientId"));
|
// assertEquals(99L, result.get("clientId"));
|
||||||
assertEquals(54321L, result.get("eventSnowflake"));
|
// assertEquals(54321L, result.get("eventSnowflake"));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testSendCommandVarargs() {
|
// void testSendCommandVarargs() {
|
||||||
NetworkEvents.SendCommand event = new NetworkEvents.SendCommand(7L, "LOGIN", "Alice");
|
// NetworkEvents.SendCommand event = new NetworkEvents.SendCommand(7L, "LOGIN", "Alice");
|
||||||
assertEquals(7L, event.clientId());
|
// assertEquals(7L, event.clientId());
|
||||||
assertArrayEquals(new String[] {"LOGIN", "Alice"}, event.args());
|
// assertArrayEquals(new String[] {"LOGIN", "Alice"}, event.args());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testReceivedMessage() {
|
// void testReceivedMessage() {
|
||||||
NetworkEvents.ReceivedMessage msg = new NetworkEvents.ReceivedMessage(11L, "Hello");
|
// NetworkEvents.ReceivedMessage msg = new NetworkEvents.ReceivedMessage(11L, "Hello");
|
||||||
assertEquals(11L, msg.clientId());
|
// assertEquals(11L, msg.clientId());
|
||||||
assertEquals("Hello", msg.message());
|
// assertEquals("Hello", msg.message());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
void testClosedConnection() {
|
// void testClosedConnection() {
|
||||||
NetworkEvents.ClosedConnection event = new NetworkEvents.ClosedConnection(22L);
|
// NetworkEvents.ClosedConnection event = new NetworkEvents.ClosedConnection(22L);
|
||||||
assertEquals(22L, event.clientId());
|
// assertEquals(22L, event.clientId());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Add more one-liners for the rest of the records to ensure constructor works
|
// // Add more one-liners for the rest of the records to ensure constructor works
|
||||||
@Test
|
// @Test
|
||||||
void testOtherRecords() {
|
// void testOtherRecords() {
|
||||||
NetworkEvents.SendLogin login = new NetworkEvents.SendLogin(1L, "Bob");
|
// NetworkEvents.SendLogin login = new NetworkEvents.SendLogin(1L, "Bob");
|
||||||
assertEquals(1L, login.clientId());
|
// assertEquals(1L, login.clientId());
|
||||||
assertEquals("Bob", login.username());
|
// assertEquals("Bob", login.username());
|
||||||
|
//
|
||||||
NetworkEvents.SendLogout logout = new NetworkEvents.SendLogout(2L);
|
// NetworkEvents.SendLogout logout = new NetworkEvents.SendLogout(2L);
|
||||||
assertEquals(2L, logout.clientId());
|
// assertEquals(2L, logout.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.SendGetPlayerlist getPlayerlist = new NetworkEvents.SendGetPlayerlist(3L);
|
// NetworkEvents.SendGetPlayerlist getPlayerlist = new NetworkEvents.SendGetPlayerlist(3L);
|
||||||
assertEquals(3L, getPlayerlist.clientId());
|
// assertEquals(3L, getPlayerlist.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.SendGetGamelist getGamelist = new NetworkEvents.SendGetGamelist(4L);
|
// NetworkEvents.SendGetGamelist getGamelist = new NetworkEvents.SendGetGamelist(4L);
|
||||||
assertEquals(4L, getGamelist.clientId());
|
// assertEquals(4L, getGamelist.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.SendSubscribe subscribe = new NetworkEvents.SendSubscribe(5L, "Chess");
|
// NetworkEvents.SendSubscribe subscribe = new NetworkEvents.SendSubscribe(5L, "Chess");
|
||||||
assertEquals(5L, subscribe.clientId());
|
// assertEquals(5L, subscribe.clientId());
|
||||||
assertEquals("Chess", subscribe.gameType());
|
// assertEquals("Chess", subscribe.gameType());
|
||||||
|
//
|
||||||
NetworkEvents.SendMove move = new NetworkEvents.SendMove(6L, (short) 1);
|
// NetworkEvents.SendMove move = new NetworkEvents.SendMove(6L, (short) 1);
|
||||||
assertEquals(6L, move.clientId());
|
// assertEquals(6L, move.clientId());
|
||||||
assertEquals((short) 1, move.moveNumber());
|
// assertEquals((short) 1, move.moveNumber());
|
||||||
|
//
|
||||||
NetworkEvents.SendChallenge challenge = new NetworkEvents.SendChallenge(7L, "Eve", "Go");
|
// NetworkEvents.SendChallenge challenge = new NetworkEvents.SendChallenge(7L, "Eve", "Go");
|
||||||
assertEquals(7L, challenge.clientId());
|
// assertEquals(7L, challenge.clientId());
|
||||||
assertEquals("Eve", challenge.usernameToChallenge());
|
// assertEquals("Eve", challenge.usernameToChallenge());
|
||||||
assertEquals("Go", challenge.gameType());
|
// assertEquals("Go", challenge.gameType());
|
||||||
|
//
|
||||||
NetworkEvents.SendAcceptChallenge accept = new NetworkEvents.SendAcceptChallenge(8L, 100);
|
// NetworkEvents.SendAcceptChallenge accept = new NetworkEvents.SendAcceptChallenge(8L, 100);
|
||||||
assertEquals(8L, accept.clientId());
|
// assertEquals(8L, accept.clientId());
|
||||||
assertEquals(100, accept.challengeId());
|
// assertEquals(100, accept.challengeId());
|
||||||
|
//
|
||||||
NetworkEvents.SendForfeit forfeit = new NetworkEvents.SendForfeit(9L);
|
// NetworkEvents.SendForfeit forfeit = new NetworkEvents.SendForfeit(9L);
|
||||||
assertEquals(9L, forfeit.clientId());
|
// assertEquals(9L, forfeit.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.SendMessage message = new NetworkEvents.SendMessage(10L, "Hi!");
|
// NetworkEvents.SendMessage message = new NetworkEvents.SendMessage(10L, "Hi!");
|
||||||
assertEquals(10L, message.clientId());
|
// assertEquals(10L, message.clientId());
|
||||||
assertEquals("Hi!", message.message());
|
// assertEquals("Hi!", message.message());
|
||||||
|
//
|
||||||
NetworkEvents.SendHelp help = new NetworkEvents.SendHelp(11L);
|
// NetworkEvents.SendHelp help = new NetworkEvents.SendHelp(11L);
|
||||||
assertEquals(11L, help.clientId());
|
// assertEquals(11L, help.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.SendHelpForCommand helpForCommand =
|
// NetworkEvents.SendHelpForCommand helpForCommand =
|
||||||
new NetworkEvents.SendHelpForCommand(12L, "MOVE");
|
// new NetworkEvents.SendHelpForCommand(12L, "MOVE");
|
||||||
assertEquals(12L, helpForCommand.clientId());
|
// assertEquals(12L, helpForCommand.clientId());
|
||||||
assertEquals("MOVE", helpForCommand.command());
|
// assertEquals("MOVE", helpForCommand.command());
|
||||||
|
//
|
||||||
NetworkEvents.CloseClient close = new NetworkEvents.CloseClient(13L);
|
// NetworkEvents.CloseClient close = new NetworkEvents.CloseClient(13L);
|
||||||
assertEquals(13L, close.clientId());
|
// assertEquals(13L, close.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.ServerResponse serverResponse = new NetworkEvents.ServerResponse(14L);
|
// NetworkEvents.ServerResponse serverResponse = new NetworkEvents.ServerResponse(14L);
|
||||||
assertEquals(14L, serverResponse.clientId());
|
// assertEquals(14L, serverResponse.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.Reconnect reconnect = new NetworkEvents.Reconnect(15L);
|
// NetworkEvents.Reconnect reconnect = new NetworkEvents.Reconnect(15L);
|
||||||
assertEquals(15L, reconnect.clientId());
|
// assertEquals(15L, reconnect.clientId());
|
||||||
|
//
|
||||||
NetworkEvents.ChangeClientHost change =
|
// NetworkEvents.ChangeClientHost change =
|
||||||
new NetworkEvents.ChangeClientHost(16L, "localhost", 1234);
|
// new NetworkEvents.ChangeClientHost(16L, "localhost", 1234);
|
||||||
assertEquals(16L, change.clientId());
|
// assertEquals(16L, change.clientId());
|
||||||
assertEquals("localhost", change.ip());
|
// assertEquals("localhost", change.ip());
|
||||||
assertEquals(1234, change.port());
|
// assertEquals(1234, change.port());
|
||||||
|
//
|
||||||
NetworkEvents.CouldNotConnect couldNotConnect = new NetworkEvents.CouldNotConnect(17L);
|
// NetworkEvents.CouldNotConnect couldNotConnect = new NetworkEvents.CouldNotConnect(17L);
|
||||||
assertEquals(17L, couldNotConnect.clientId());
|
// assertEquals(17L, couldNotConnect.clientId());
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
// TODO
|
||||||
76
game/pom.xml
76
game/pom.xml
@@ -1,8 +1,13 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
<groupId>org.toop</groupId>
|
<groupId>org.toop</groupId>
|
||||||
<artifactId>pism_game</artifactId>
|
<artifactId>pism</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>game</artifactId>
|
||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -83,6 +88,18 @@
|
|||||||
<artifactId>slf4j-simple</artifactId>
|
<artifactId>slf4j-simple</artifactId>
|
||||||
<version>2.0.17</version>
|
<version>2.0.17</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_annotations</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -92,29 +109,50 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.14.1</version>
|
<version>3.14.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<showWarnings>true</showWarnings>
|
||||||
|
<fork>true</fork>
|
||||||
|
<executable>${java.home}/bin/javac</executable>
|
||||||
<source>25</source>
|
<source>25</source>
|
||||||
<target>25</target>
|
<target>25</target>
|
||||||
<release>25</release>
|
<release>25</release>
|
||||||
<encoding>UTF-8</encoding>
|
<encoding>UTF-8</encoding>
|
||||||
<!-- <compilerArgs>-->
|
<compilerArgs>
|
||||||
<!-- <arg>-XDcompilePolicy=simple</arg>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
|
||||||
<!-- <arg>--should-stop=ifError=FLOW</arg>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
|
||||||
<!-- <arg>-Xplugin:ErrorProne</arg>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
|
||||||
<!-- </compilerArgs>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
|
||||||
<!-- <annotationProcessorPaths>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
|
||||||
<!-- <path>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
|
||||||
<!-- <groupId>com.google.errorprone</groupId>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
|
||||||
<!-- <artifactId>error_prone_core</artifactId>-->
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
|
||||||
<!-- <version>2.42.0</version>-->
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
|
||||||
<!-- </path>-->
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
|
||||||
<!-- <!– Other annotation processors go here.-->
|
<arg>
|
||||||
|
-Xplugin:ErrorProne
|
||||||
<!-- If 'annotationProcessorPaths' is set, processors will no longer be-->
|
</arg>
|
||||||
<!-- discovered on the regular -classpath; see also 'Using Error Prone-->
|
<!-- TODO-->
|
||||||
<!-- together with other annotation processors' below. –>-->
|
<!-- -Xep:RestrictedApi:ERROR \-->
|
||||||
<!-- </annotationProcessorPaths>-->
|
<!-- -XepOpt:RestrictedApi:annotation=org.toop.annotations.TestsOnly \-->
|
||||||
<!-- <fork>true</fork>-->
|
<!-- -XepOpt:RestrictedApi:allowlistRegex=(?s).*/src/test/java/.*|.*test\.java \-->
|
||||||
|
<!-- -XepOpt:RestrictedApi:message=This API is marked @TestsOnly and shouldn't be normally used.-->
|
||||||
|
<arg>-XDcompilePolicy=simple</arg>
|
||||||
|
<arg>--should-stop=ifError=FLOW</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>2.42.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.diffplug.spotless</groupId>
|
<groupId>com.diffplug.spotless</groupId>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
public abstract class Game {
|
public abstract class Game {
|
||||||
public enum State {
|
public enum State {
|
||||||
NORMAL, DRAW, WIN,
|
NORMAL,
|
||||||
|
DRAW,
|
||||||
|
WIN,
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Move(int position, char value) {}
|
public record Move(int position, char value) {}
|
||||||
@@ -34,5 +36,6 @@ public abstract class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract Move[] getLegalMoves();
|
public abstract Move[] getLegalMoves();
|
||||||
|
|
||||||
public abstract State play(Move move);
|
public abstract State play(Move move);
|
||||||
}
|
}
|
||||||
@@ -21,5 +21,7 @@ public abstract class TurnBasedGame extends Game {
|
|||||||
currentTurn = (currentTurn + 1) % turns;
|
currentTurn = (currentTurn + 1) % turns;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCurrentTurn() { return currentTurn; }
|
public int getCurrentTurn() {
|
||||||
|
return currentTurn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
package org.toop.game.tictactoe;
|
package org.toop.game.tictactoe;
|
||||||
|
|
||||||
import org.toop.game.TurnBasedGame;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import org.toop.game.TurnBasedGame;
|
||||||
|
|
||||||
public final class TicTacToe extends TurnBasedGame {
|
public final class TicTacToe extends TurnBasedGame {
|
||||||
private int movesLeft;
|
private int movesLeft;
|
||||||
@@ -60,7 +59,9 @@ public final class TicTacToe extends TurnBasedGame {
|
|||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
final int index = i * 3;
|
final int index = i * 3;
|
||||||
|
|
||||||
if (board[index] != EMPTY && board[index] == board[index + 1] && board[index] == board[index + 2]) {
|
if (board[index] != EMPTY
|
||||||
|
&& board[index] == board[index + 1]
|
||||||
|
&& board[index] == board[index + 2]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,6 +95,6 @@ public final class TicTacToe extends TurnBasedGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private char getCurrentValue() {
|
private char getCurrentValue() {
|
||||||
return currentTurn == 0? 'X' : 'O';
|
return currentTurn == 0 ? 'X' : 'O';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user