mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
Merge remote-tracking branch 'origin/UI' into AudioDisplay
This commit is contained in:
@@ -26,10 +26,13 @@ public class MusicManager<T extends AudioResource> implements org.toop.framework
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
|
||||
public MusicManager(List<T> resources) {
|
||||
public MusicManager(List<T> resources, boolean shuffleMusic) {
|
||||
this.dispatcher = new JavaFXDispatcher();
|
||||
this.resources = resources;
|
||||
createShuffled();
|
||||
// Shuffle if wanting to shuffle
|
||||
if (shuffleMusic) createShuffled();
|
||||
else backgroundMusic.addAll(resources);
|
||||
// ------------------------------
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -134,7 +134,8 @@ public final class GlobalEventBus {
|
||||
for (Consumer<? super EventType> listener : classListeners) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable ignored) {
|
||||
} catch (Throwable e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +147,8 @@ public final class GlobalEventBus {
|
||||
for (Consumer<? super EventType> listener : genericListeners) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable ignored) {
|
||||
} catch (Throwable e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,17 @@ import java.lang.reflect.RecordComponent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MUST HAVE long identifier at the end.
|
||||
* e.g.
|
||||
*
|
||||
* <pre>{@code
|
||||
* public record uniqueEventResponse(String content, long identifier) implements ResponseToUniqueEvent {};
|
||||
* public record uniqueEventResponse(long identifier) implements ResponseToUniqueEvent {};
|
||||
* public record uniqueEventResponse(String content, int number, long identifier) implements ResponseToUniqueEvent {};
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
public interface ResponseToUniqueEvent extends UniqueEvent {
|
||||
default Map<String, Object> result() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
/**
|
||||
* MUST HAVE long identifier at the end.
|
||||
* e.g.
|
||||
*
|
||||
* <pre>{@code
|
||||
* public record uniqueEvent(String content, long identifier) implements UniqueEvent {};
|
||||
* public record uniqueEvent(long identifier) implements UniqueEvent {};
|
||||
* public record uniqueEvent(String content, int number, long identifier) implements UniqueEvent {};
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
public interface UniqueEvent extends EventType {
|
||||
default long getIdentifier() {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.toop.framework.networking;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.SnowflakeGenerator;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.networking.events.NetworkEvents;
|
||||
import org.toop.framework.networking.exceptions.ClientNotFoundException;
|
||||
import org.toop.framework.networking.interfaces.NetworkingClientManager;
|
||||
|
||||
public class NetworkingClientEventListener {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(NetworkingClientEventListener.class);
|
||||
private final NetworkingClientManager clientManager;
|
||||
|
||||
/** Starts a connection manager, to manage, connections. */
|
||||
public NetworkingClientEventListener(NetworkingClientManager clientManager) {
|
||||
this.clientManager = clientManager;
|
||||
new EventFlow()
|
||||
.listen(this::handleStartClient)
|
||||
.listen(this::handleCommand)
|
||||
.listen(this::handleSendLogin)
|
||||
.listen(this::handleSendLogout)
|
||||
.listen(this::handleSendGetPlayerlist)
|
||||
.listen(this::handleSendGetGamelist)
|
||||
.listen(this::handleSendSubscribe)
|
||||
.listen(this::handleSendMove)
|
||||
.listen(this::handleSendChallenge)
|
||||
.listen(this::handleSendAcceptChallenge)
|
||||
.listen(this::handleSendForfeit)
|
||||
.listen(this::handleSendMessage)
|
||||
.listen(this::handleSendHelp)
|
||||
.listen(this::handleSendHelpForCommand)
|
||||
.listen(this::handleCloseClient)
|
||||
.listen(this::handleReconnect)
|
||||
.listen(this::handleChangeAddress)
|
||||
.listen(this::handleGetAllConnections)
|
||||
.listen(this::handleShutdownAll);
|
||||
}
|
||||
|
||||
void handleStartClient(NetworkEvents.StartClient event) {
|
||||
long clientId = SnowflakeGenerator.nextId();
|
||||
clientManager.startClient(
|
||||
clientId,
|
||||
event.networkingClient(),
|
||||
event.networkingConnector(),
|
||||
() -> new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(clientId, true, event.identifier())).postEvent(),
|
||||
() -> new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(clientId, false, event.identifier())).postEvent()
|
||||
);
|
||||
}
|
||||
|
||||
private void sendCommand(long clientId, String command) {
|
||||
try {
|
||||
clientManager.sendCommand(clientId, command);
|
||||
} catch (ClientNotFoundException e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCommand(NetworkEvents.SendCommand event) {
|
||||
String args = String.join(" ", event.args());
|
||||
sendCommand(event.clientId(), args);
|
||||
}
|
||||
|
||||
private void handleSendLogin(NetworkEvents.SendLogin event) {
|
||||
sendCommand(event.clientId(), String.format("LOGIN %s", event.username()));
|
||||
}
|
||||
|
||||
private void handleSendLogout(NetworkEvents.SendLogout event) {
|
||||
sendCommand(event.clientId(), "LOGOUT");
|
||||
}
|
||||
|
||||
private void handleSendGetPlayerlist(NetworkEvents.SendGetPlayerlist event) {
|
||||
sendCommand(event.clientId(), "GET PLAYERLIST");
|
||||
}
|
||||
|
||||
private void handleSendGetGamelist(NetworkEvents.SendGetGamelist event) {
|
||||
sendCommand(event.clientId(), "GET GAMELIST");
|
||||
}
|
||||
|
||||
private void handleSendSubscribe(NetworkEvents.SendSubscribe event) {
|
||||
sendCommand(event.clientId(), String.format("SUBSCRIBE %s", event.gameType()));
|
||||
}
|
||||
|
||||
private void handleSendMove(NetworkEvents.SendMove event) {
|
||||
sendCommand(event.clientId(), String.format("MOVE %d", event.moveNumber()));
|
||||
}
|
||||
|
||||
private void handleSendChallenge(NetworkEvents.SendChallenge event) {
|
||||
sendCommand(event.clientId(), String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType()));
|
||||
}
|
||||
|
||||
private void handleSendAcceptChallenge(NetworkEvents.SendAcceptChallenge event) {
|
||||
sendCommand(event.clientId(), String.format("CHALLENGE ACCEPT %d", event.challengeId()));
|
||||
}
|
||||
|
||||
private void handleSendForfeit(NetworkEvents.SendForfeit event) {
|
||||
sendCommand(event.clientId(), "FORFEIT");
|
||||
}
|
||||
|
||||
private void handleSendMessage(NetworkEvents.SendMessage event) {
|
||||
sendCommand(event.clientId(), String.format("MESSAGE %s", event.message()));
|
||||
}
|
||||
|
||||
private void handleSendHelp(NetworkEvents.SendHelp event) {
|
||||
sendCommand(event.clientId(), "HELP");
|
||||
}
|
||||
|
||||
private void handleSendHelpForCommand(NetworkEvents.SendHelpForCommand event) {
|
||||
sendCommand(event.clientId(), String.format("HELP %s", event.command()));
|
||||
}
|
||||
|
||||
private void handleReconnect(NetworkEvents.Reconnect event) {
|
||||
clientManager.startClient(
|
||||
event.clientId(),
|
||||
event.networkingClient(),
|
||||
event.networkingConnector(),
|
||||
() -> new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(true, event.identifier())).postEvent(),
|
||||
() -> new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, event.identifier())).postEvent()
|
||||
);
|
||||
}
|
||||
|
||||
private void handleChangeAddress(NetworkEvents.ChangeAddress event) {
|
||||
clientManager.startClient(
|
||||
event.clientId(),
|
||||
event.networkingClient(),
|
||||
event.networkingConnector(),
|
||||
() -> new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(true, event.identifier())).postEvent(),
|
||||
() -> new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, event.identifier())).postEvent()
|
||||
);
|
||||
}
|
||||
|
||||
void handleCloseClient(NetworkEvents.CloseClient event) {
|
||||
try {
|
||||
this.clientManager.closeClient(event.clientId());
|
||||
} catch (ClientNotFoundException e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
void handleGetAllConnections(NetworkEvents.RequestsAllClients request) {
|
||||
// List<NetworkingClient> a = new ArrayList<>(this.networkClients.values());
|
||||
// request.future().complete(a);
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
@@ -2,196 +2,118 @@ package org.toop.framework.networking;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.SnowflakeGenerator;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.networking.events.NetworkEvents;
|
||||
|
||||
public class NetworkingClientManager {
|
||||
import org.toop.framework.networking.exceptions.ClientNotFoundException;
|
||||
import org.toop.framework.networking.exceptions.CouldNotConnectException;
|
||||
import org.toop.framework.networking.interfaces.NetworkingClient;
|
||||
import org.toop.framework.networking.types.NetworkingConnector;
|
||||
|
||||
public class NetworkingClientManager implements org.toop.framework.networking.interfaces.NetworkingClientManager {
|
||||
private static final Logger logger = LogManager.getLogger(NetworkingClientManager.class);
|
||||
private final Map<Long, NetworkingClient> networkClients = new ConcurrentHashMap<>();
|
||||
|
||||
/** Map of serverId -> Server instances */
|
||||
final Map<Long, NetworkingClient> networkClients = new ConcurrentHashMap<>();
|
||||
public NetworkingClientManager() {}
|
||||
|
||||
/** Starts a connection manager, to manage, connections. */
|
||||
public NetworkingClientManager() throws NetworkingInitializationException {
|
||||
try {
|
||||
new EventFlow()
|
||||
.listen(this::handleStartClient)
|
||||
.listen(this::handleCommand)
|
||||
.listen(this::handleSendLogin)
|
||||
.listen(this::handleSendLogout)
|
||||
.listen(this::handleSendGetPlayerlist)
|
||||
.listen(this::handleSendGetGamelist)
|
||||
.listen(this::handleSendSubscribe)
|
||||
.listen(this::handleSendMove)
|
||||
.listen(this::handleSendChallenge)
|
||||
.listen(this::handleSendAcceptChallenge)
|
||||
.listen(this::handleSendForfeit)
|
||||
.listen(this::handleSendMessage)
|
||||
.listen(this::handleSendHelp)
|
||||
.listen(this::handleSendHelpForCommand)
|
||||
.listen(this::handleCloseClient)
|
||||
.listen(this::handleChangeClientHost)
|
||||
.listen(this::handleGetAllConnections)
|
||||
.listen(this::handleShutdownAll);
|
||||
logger.info("NetworkingClientManager initialized");
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize the client manager", e);
|
||||
throw e;
|
||||
private void connectHelper(
|
||||
long id,
|
||||
NetworkingClient nClient,
|
||||
NetworkingConnector nConnector,
|
||||
Runnable onSuccess,
|
||||
Runnable onFailure
|
||||
) {
|
||||
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
Runnable connectTask = new Runnable() {
|
||||
int attempts = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
NetworkingClient qClient = networkClients.get(id);
|
||||
if (qClient != null) {
|
||||
qClient.closeConnection();
|
||||
networkClients.remove(id);
|
||||
}
|
||||
|
||||
try {
|
||||
nClient.connect(id, nConnector.host(), nConnector.port());
|
||||
networkClients.put(id, nClient);
|
||||
logger.info("New client started successfully for {}:{}", nConnector.host(), nConnector.port());
|
||||
onSuccess.run();
|
||||
scheduler.shutdown();
|
||||
} catch (CouldNotConnectException e) {
|
||||
attempts++;
|
||||
if (attempts < nConnector.reconnectAttempts()) {
|
||||
logger.warn("Could not connect to {}:{}. Retrying in {} {}",
|
||||
nConnector.host(), nConnector.port(), nConnector.timeout(), nConnector.timeUnit());
|
||||
scheduler.schedule(this, nConnector.timeout(), nConnector.timeUnit());
|
||||
} else {
|
||||
logger.error("Failed to start client for {}:{} after {} attempts", nConnector.host(), nConnector.port(), attempts);
|
||||
onFailure.run();
|
||||
scheduler.shutdown();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Unexpected exception during startClient", e);
|
||||
onFailure.run();
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startClient(
|
||||
long id,
|
||||
NetworkingClient nClient,
|
||||
NetworkingConnector nConnector,
|
||||
Runnable onSuccess,
|
||||
Runnable onFailure
|
||||
) {
|
||||
connectHelper(
|
||||
id,
|
||||
nClient,
|
||||
nConnector,
|
||||
onSuccess,
|
||||
onFailure
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommand(long id, String command) throws ClientNotFoundException {
|
||||
logger.info("Sending command to client for {}:{}", id, command);
|
||||
if (command.isEmpty()) {
|
||||
IllegalArgumentException e = new IllegalArgumentException("command is empty");
|
||||
logger.error("Invalid command received", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
long startClientRequest(String ip, int port) {
|
||||
long connectionId = SnowflakeGenerator.nextId();
|
||||
try {
|
||||
NetworkingClient client =
|
||||
new NetworkingClient(
|
||||
() -> new NetworkingGameClientHandler(connectionId),
|
||||
ip,
|
||||
port,
|
||||
connectionId);
|
||||
client.setConnectionId(connectionId);
|
||||
this.networkClients.put(connectionId, client);
|
||||
logger.info("New client started successfully for {}:{}", ip, port);
|
||||
} catch (Exception e) {
|
||||
logger.error(e);
|
||||
NetworkingClient client = this.networkClients.get(id);
|
||||
if (client == null) {
|
||||
throw new ClientNotFoundException(id);
|
||||
}
|
||||
return connectionId;
|
||||
|
||||
String toSend = command.trim();
|
||||
|
||||
if (toSend.endsWith("\n")) { client.writeAndFlush(toSend); }
|
||||
else { client.writeAndFlush(toSend + "\n"); }
|
||||
|
||||
}
|
||||
|
||||
private long startClientRequest(String ip, int port, long clientId) {
|
||||
try { // With EventFlow
|
||||
NetworkingClient client =
|
||||
new NetworkingClient(
|
||||
() -> new NetworkingGameClientHandler(clientId), ip, port, clientId);
|
||||
client.setConnectionId(clientId);
|
||||
this.networkClients.replace(clientId, client);
|
||||
logger.info(
|
||||
"New client started successfully for {}:{}, replaced: {}", ip, port, clientId);
|
||||
} catch (Exception e) {
|
||||
logger.error(e);
|
||||
@Override
|
||||
public void closeClient(long id) throws ClientNotFoundException {
|
||||
NetworkingClient client = this.networkClients.get(id);
|
||||
if (client == null) {
|
||||
throw new ClientNotFoundException(id);
|
||||
}
|
||||
logger.info("Client {} started", clientId);
|
||||
return clientId;
|
||||
}
|
||||
|
||||
void handleStartClient(NetworkEvents.StartClient event) {
|
||||
long id = this.startClientRequest(event.ip(), event.port());
|
||||
new Thread(
|
||||
() ->
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
NetworkEvents.StartClientResponse.class,
|
||||
id,
|
||||
event.eventSnowflake())
|
||||
.asyncPostEvent())
|
||||
.start();
|
||||
}
|
||||
|
||||
void handleCommand(
|
||||
NetworkEvents.SendCommand
|
||||
event) { // TODO: Move this to ServerConnection class, keep it internal.
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
String args = String.join(" ", event.args());
|
||||
sendCommand(client, args);
|
||||
}
|
||||
|
||||
void handleSendLogin(NetworkEvents.SendLogin event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, String.format("LOGIN %s", event.username()));
|
||||
}
|
||||
|
||||
private void handleSendLogout(NetworkEvents.SendLogout event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, "LOGOUT");
|
||||
}
|
||||
|
||||
private void handleSendGetPlayerlist(NetworkEvents.SendGetPlayerlist event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, "GET PLAYERLIST");
|
||||
}
|
||||
|
||||
private void handleSendGetGamelist(NetworkEvents.SendGetGamelist event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, "GET GAMELIST");
|
||||
}
|
||||
|
||||
private void handleSendSubscribe(NetworkEvents.SendSubscribe event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, String.format("SUBSCRIBE %s", event.gameType()));
|
||||
}
|
||||
|
||||
private void handleSendMove(NetworkEvents.SendMove event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, String.format("MOVE %d", event.moveNumber()));
|
||||
}
|
||||
|
||||
private void handleSendChallenge(NetworkEvents.SendChallenge event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(
|
||||
client,
|
||||
String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType()));
|
||||
}
|
||||
|
||||
private void handleSendAcceptChallenge(NetworkEvents.SendAcceptChallenge event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, String.format("CHALLENGE ACCEPT %d", event.challengeId()));
|
||||
}
|
||||
|
||||
private void handleSendForfeit(NetworkEvents.SendForfeit event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, "FORFEIT");
|
||||
}
|
||||
|
||||
private void handleSendMessage(NetworkEvents.SendMessage event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, String.format("MESSAGE %s", event.message()));
|
||||
}
|
||||
|
||||
private void handleSendHelp(NetworkEvents.SendHelp event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, "HELP");
|
||||
}
|
||||
|
||||
private void handleSendHelpForCommand(NetworkEvents.SendHelpForCommand event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
sendCommand(client, String.format("HELP %s", event.command()));
|
||||
}
|
||||
|
||||
private void sendCommand(NetworkingClient client, String command) {
|
||||
logger.info(
|
||||
"Preparing to send command: {} to server: {}:{}. clientId: {}",
|
||||
command.trim(),
|
||||
client.getHost(),
|
||||
client.getPort(),
|
||||
client.getId());
|
||||
client.writeAndFlushnl(command);
|
||||
}
|
||||
|
||||
private void handleChangeClientHost(NetworkEvents.ChangeClientHost event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
client.closeConnection();
|
||||
startClientRequest(event.ip(), event.port(), event.clientId());
|
||||
}
|
||||
|
||||
void handleCloseClient(NetworkEvents.CloseClient event) {
|
||||
NetworkingClient client = this.networkClients.get(event.clientId());
|
||||
client.closeConnection();
|
||||
this.networkClients.remove(event.clientId());
|
||||
logger.info("Client {} closed successfully.", event.clientId());
|
||||
}
|
||||
|
||||
void handleGetAllConnections(NetworkEvents.RequestsAllClients request) {
|
||||
List<NetworkingClient> a = new ArrayList<>(this.networkClients.values());
|
||||
request.future().complete(a);
|
||||
}
|
||||
|
||||
public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) {
|
||||
this.networkClients.values().forEach(NetworkingClient::closeConnection);
|
||||
this.networkClients.clear();
|
||||
logger.info("All servers shut down");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.toop.framework.networking;
|
||||
package org.toop.framework.networking.clients;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
@@ -9,27 +9,27 @@ import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import java.util.function.Supplier;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.networking.events.NetworkEvents;
|
||||
import org.toop.framework.networking.exceptions.CouldNotConnectException;
|
||||
import org.toop.framework.networking.handlers.NetworkingGameClientHandler;
|
||||
import org.toop.framework.networking.interfaces.NetworkingClient;
|
||||
|
||||
public class NetworkingClient {
|
||||
private static final Logger logger = LogManager.getLogger(NetworkingClient.class);
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
private long connectionId;
|
||||
private String host;
|
||||
private int port;
|
||||
public class TournamentNetworkingClient implements NetworkingClient {
|
||||
private static final Logger logger = LogManager.getLogger(TournamentNetworkingClient.class);
|
||||
private Channel channel;
|
||||
private NetworkingGameClientHandler handler;
|
||||
|
||||
public NetworkingClient(
|
||||
Supplier<NetworkingGameClientHandler> handlerFactory,
|
||||
String host,
|
||||
int port,
|
||||
long connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
public TournamentNetworkingClient() {}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return (InetSocketAddress) channel.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(long clientId, String host, int port) throws CouldNotConnectException {
|
||||
try {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
|
||||
@@ -40,7 +40,7 @@ public class NetworkingClient {
|
||||
new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) {
|
||||
handler = handlerFactory.get();
|
||||
NetworkingGameClientHandler handler = new NetworkingGameClientHandler(clientId);
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast(new LineBasedFrameDecoder(1024)); // split at \n
|
||||
@@ -52,53 +52,28 @@ public class NetworkingClient {
|
||||
});
|
||||
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
|
||||
this.channel = channelFuture.channel();
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create networking client instance", e);
|
||||
} catch (Exception _) {
|
||||
throw new CouldNotConnectException(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkingGameClientHandler getHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void setConnectionId(long connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
public boolean isChannelActive() {
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return this.channel != null && this.channel.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeAndFlush(String msg) {
|
||||
String literalMsg = msg.replace("\n", "\\n").replace("\r", "\\r");
|
||||
if (isChannelActive()) {
|
||||
if (isActive()) {
|
||||
this.channel.writeAndFlush(msg);
|
||||
logger.info(
|
||||
"Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
|
||||
logger.info("Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg);
|
||||
} else {
|
||||
logger.warn("Cannot send message: '{}', connection inactive. ", literalMsg);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeAndFlushnl(String msg) {
|
||||
if (isChannelActive()) {
|
||||
this.channel.writeAndFlush(msg + "\r\n");
|
||||
logger.info("Connection {} sent message: '{}'", this.channel.remoteAddress(), msg);
|
||||
} else {
|
||||
logger.warn("Cannot send message: '{}', connection inactive.", msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConnection() {
|
||||
if (this.channel != null && this.channel.isActive()) {
|
||||
this.channel
|
||||
@@ -109,11 +84,6 @@ public class NetworkingClient {
|
||||
logger.info(
|
||||
"Connection {} closed successfully",
|
||||
this.channel.remoteAddress());
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
new NetworkEvents.ClosedConnection(
|
||||
this.connectionId))
|
||||
.asyncPostEvent();
|
||||
} else {
|
||||
logger.error(
|
||||
"Error closing connection {}. Error: {}",
|
||||
@@ -123,8 +93,4 @@ public class NetworkingClient {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return this.connectionId;
|
||||
}
|
||||
}
|
||||
@@ -3,158 +3,215 @@ package org.toop.framework.networking.events;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.toop.framework.eventbus.events.GenericEvent;
|
||||
import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
|
||||
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||
import org.toop.framework.eventbus.events.EventsBase;
|
||||
import org.toop.annotations.AutoResponseResult;
|
||||
import org.toop.framework.networking.NetworkingClient;
|
||||
import org.toop.framework.eventbus.events.*;
|
||||
import org.toop.framework.networking.interfaces.NetworkingClient;
|
||||
import org.toop.framework.networking.types.NetworkingConnector;
|
||||
|
||||
/**
|
||||
* A collection of networking-related event records for use with the {@link
|
||||
* org.toop.framework.eventbus.GlobalEventBus}.
|
||||
* Defines all event types related to the networking subsystem.
|
||||
* <p>
|
||||
* These events are used in conjunction with the {@link org.toop.framework.eventbus.GlobalEventBus}
|
||||
* and {@link org.toop.framework.eventbus.EventFlow} to communicate between components
|
||||
* such as networking clients, managers, and listeners.
|
||||
* </p>
|
||||
*
|
||||
* <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 (UniqueEvent) and those without
|
||||
* (GenericEvent).
|
||||
* <h2>Important</h2>
|
||||
* For all {@link UniqueEvent} and {@link ResponseToUniqueEvent} types:
|
||||
* the {@code identifier} field is automatically generated and injected
|
||||
* by {@link org.toop.framework.eventbus.EventFlow}. It should <strong>never</strong>
|
||||
* be manually assigned by user code. (Exceptions may apply)
|
||||
*/
|
||||
public class NetworkEvents extends EventsBase {
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Generic Request & Response Events (no identifier)
|
||||
// ------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Requests all active client connections.
|
||||
*
|
||||
* <p>This is a blocking event. The result will be delivered via the provided {@link
|
||||
* CompletableFuture}.
|
||||
*
|
||||
* @param future CompletableFuture to receive the list of active {@link NetworkingClient}
|
||||
* instances.
|
||||
* Requests a list of all active networking clients.
|
||||
* <p>
|
||||
* This is a blocking request that returns the list asynchronously
|
||||
* via the provided {@link CompletableFuture}.
|
||||
*/
|
||||
public record RequestsAllClients(CompletableFuture<List<NetworkingClient>> future)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Forces all active client connections to close immediately. */
|
||||
/** Signals all active clients should be forcefully closed. */
|
||||
public record ForceCloseAllClients() implements GenericEvent {}
|
||||
|
||||
/** Response indicating a challenge was cancelled. */
|
||||
public record ChallengeCancelledResponse(long clientId, String challengeId) implements GenericEvent {}
|
||||
/** Indicates a challenge was cancelled by the server. */
|
||||
public record ChallengeCancelledResponse(long clientId, String challengeId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response indicating a challenge was received. */
|
||||
/** Indicates an incoming challenge from another player. */
|
||||
public record ChallengeResponse(long clientId, String challengerName, String challengeId, String gameType)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response containing a list of players for a client. */
|
||||
public record PlayerlistResponse(long clientId, String[] playerlist) implements GenericEvent {}
|
||||
/** Contains the list of players currently available on the server. */
|
||||
public record PlayerlistResponse(long clientId, String[] playerlist)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response containing a list of games for a client. */
|
||||
public record GamelistResponse(long clientId, String[] gamelist) implements GenericEvent {}
|
||||
/** Contains the list of available game types for a client. */
|
||||
public record GamelistResponse(long clientId, String[] gamelist)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response indicating a game match information for a client. */
|
||||
/** Provides match information when a new game starts. */
|
||||
public record GameMatchResponse(long clientId, String playerToMove, String gameType, String opponent)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response indicating the result of a game. */
|
||||
public record GameResultResponse(long clientId, String condition) implements GenericEvent {}
|
||||
/** Indicates the outcome or completion of a game. */
|
||||
public record GameResultResponse(long clientId, String condition)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response indicating a game move occurred. */
|
||||
public record GameMoveResponse(long clientId, String player, String move, String details) implements GenericEvent {}
|
||||
/** Indicates that a game move has been processed or received. */
|
||||
public record GameMoveResponse(long clientId, String player, String move, String details)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response indicating it is the player's turn. */
|
||||
/** Indicates it is the current player's turn to move. */
|
||||
public record YourTurnResponse(long clientId, String message)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to send login credentials for a client. */
|
||||
public record SendLogin(long clientId, String username) implements GenericEvent {}
|
||||
/** Requests a login operation for the given client. */
|
||||
public record SendLogin(long clientId, String username)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to log out a client. */
|
||||
public record SendLogout(long clientId) implements GenericEvent {}
|
||||
/** Requests logout for the specified client. */
|
||||
public record SendLogout(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to retrieve the player list for a client. */
|
||||
public record SendGetPlayerlist(long clientId) implements GenericEvent {}
|
||||
/** Requests the player list from the server. */
|
||||
public record SendGetPlayerlist(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to retrieve the game list for a client. */
|
||||
public record SendGetGamelist(long clientId) implements GenericEvent {}
|
||||
/** Requests the game list from the server. */
|
||||
public record SendGetGamelist(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to subscribe a client to a game type. */
|
||||
public record SendSubscribe(long clientId, String gameType) implements GenericEvent {}
|
||||
/** Requests a subscription to updates for a given game type. */
|
||||
public record SendSubscribe(long clientId, String gameType)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to make a move in a game. */
|
||||
public record SendMove(long clientId, short moveNumber) implements GenericEvent {}
|
||||
/** Sends a game move command to the server. */
|
||||
public record SendMove(long clientId, short moveNumber)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to challenge another player. */
|
||||
public record SendChallenge(long clientId, String usernameToChallenge, String gameType) implements GenericEvent {}
|
||||
/** Requests to challenge another player to a game. */
|
||||
public record SendChallenge(long clientId, String usernameToChallenge, String gameType)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to accept a challenge. */
|
||||
public record SendAcceptChallenge(long clientId, int challengeId) implements GenericEvent {}
|
||||
/** Requests to accept an existing challenge. */
|
||||
public record SendAcceptChallenge(long clientId, int challengeId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to forfeit a game. */
|
||||
public record SendForfeit(long clientId) implements GenericEvent {}
|
||||
/** Requests to forfeit the current game. */
|
||||
public record SendForfeit(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to send a message from a client. */
|
||||
public record SendMessage(long clientId, String message) implements GenericEvent {}
|
||||
/** Sends a chat or informational message from a client. */
|
||||
public record SendMessage(long clientId, String message)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to display help to a client. */
|
||||
public record SendHelp(long clientId) implements GenericEvent {}
|
||||
/** Requests general help information from the server. */
|
||||
public record SendHelp(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to display help for a specific command. */
|
||||
public record SendHelpForCommand(long clientId, String command) implements GenericEvent {}
|
||||
/** Requests help information specific to a given command. */
|
||||
public record SendHelpForCommand(long clientId, String command)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to close a specific client connection. */
|
||||
public record CloseClient(long clientId) implements GenericEvent {}
|
||||
/** Requests to close an active client connection. */
|
||||
public record CloseClient(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** A generic event indicating a raw server response. */
|
||||
public record ServerResponse(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
/**
|
||||
* Event to start a new client connection.
|
||||
* Sends a raw command string to the server.
|
||||
*
|
||||
* <p>Carries IP, port, and a unique event ID for correlation with responses.
|
||||
*
|
||||
* @param ip Server IP address.
|
||||
* @param port Server port.
|
||||
* @param eventSnowflake Unique event identifier for correlation.
|
||||
*/
|
||||
public record StartClient(String ip, int port, long eventSnowflake) implements UniqueEvent {}
|
||||
|
||||
/**
|
||||
* Response confirming a client was started.
|
||||
*
|
||||
* @param clientId The client ID assigned to the new connection.
|
||||
* @param identifier Event ID used for correlation.
|
||||
*/
|
||||
@AutoResponseResult
|
||||
public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {}
|
||||
|
||||
/** Generic server response. */
|
||||
public record ServerResponse(long clientId) implements GenericEvent {}
|
||||
|
||||
/**
|
||||
* Request to send a command to a server.
|
||||
*
|
||||
* @param clientId The client connection ID.
|
||||
* @param clientId The client ID to send the command from.
|
||||
* @param args The command arguments.
|
||||
*/
|
||||
public record SendCommand(long clientId, String... args) implements GenericEvent {}
|
||||
public record SendCommand(long clientId, String... args)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** WIP (Not working) Request to reconnect a client to a previous address. */
|
||||
public record Reconnect(long clientId) implements GenericEvent {}
|
||||
/** Event fired when a message is received from the server. */
|
||||
public record ReceivedMessage(long clientId, String message)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Indicates that a client connection has been closed. */
|
||||
public record ClosedConnection(long clientId)
|
||||
implements GenericEvent {}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Unique Request & Response Events (with identifier)
|
||||
// ------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Response triggered when a message is received from a server.
|
||||
* Requests creation and connection of a new client.
|
||||
* <p>
|
||||
* The {@code identifier} is automatically assigned by {@link org.toop.framework.eventbus.EventFlow}
|
||||
* to correlate with its corresponding {@link StartClientResponse}.
|
||||
* </p>
|
||||
*
|
||||
* @param clientId The connection ID that received the message.
|
||||
* @param message The message content.
|
||||
* @param networkingClient The client instance to start.
|
||||
* @param networkingConnector Connection details (host, port, etc.).
|
||||
* @param identifier Automatically injected unique identifier.
|
||||
*/
|
||||
public record ReceivedMessage(long clientId, String message) implements GenericEvent {}
|
||||
public record StartClient(
|
||||
NetworkingClient networkingClient,
|
||||
NetworkingConnector networkingConnector,
|
||||
long identifier)
|
||||
implements UniqueEvent {}
|
||||
|
||||
/**
|
||||
* Request to change a client connection to a new server.
|
||||
* Response confirming that a client has been successfully started.
|
||||
* <p>
|
||||
* The {@code identifier} value is automatically propagated from
|
||||
* the original {@link StartClient} request by {@link org.toop.framework.eventbus.EventFlow}.
|
||||
* </p>
|
||||
*
|
||||
* @param clientId The client connection ID.
|
||||
* @param ip The new server IP.
|
||||
* @param port The new server port.
|
||||
* @param clientId The newly assigned client ID.
|
||||
* @param successful Whether the connection succeeded.
|
||||
* @param identifier Automatically injected correlation ID.
|
||||
*/
|
||||
public record ChangeClientHost(long clientId, String ip, int port) implements GenericEvent {}
|
||||
@AutoResponseResult
|
||||
public record StartClientResponse(long clientId, boolean successful, long identifier)
|
||||
implements ResponseToUniqueEvent {}
|
||||
|
||||
/** WIP (Not working) Response indicating that the client could not connect. */
|
||||
public record CouldNotConnect(long clientId) implements GenericEvent {}
|
||||
/**
|
||||
* Requests reconnection of an existing client using its previous configuration.
|
||||
* <p>
|
||||
* The {@code identifier} is automatically injected by {@link org.toop.framework.eventbus.EventFlow}.
|
||||
* </p>
|
||||
*/
|
||||
public record Reconnect(
|
||||
long clientId,
|
||||
NetworkingClient networkingClient,
|
||||
NetworkingConnector networkingConnector,
|
||||
long identifier)
|
||||
implements UniqueEvent {}
|
||||
|
||||
/** Event indicating a client connection was closed. */
|
||||
public record ClosedConnection(long clientId) implements GenericEvent {}
|
||||
/** Response to a {@link Reconnect} event, carrying the success result. */
|
||||
public record ReconnectResponse(boolean successful, long identifier)
|
||||
implements ResponseToUniqueEvent {}
|
||||
|
||||
/**
|
||||
* Requests to change the connection target (host/port) for a client.
|
||||
* <p>
|
||||
* The {@code identifier} is automatically injected by {@link org.toop.framework.eventbus.EventFlow}.
|
||||
* </p>
|
||||
*/
|
||||
public record ChangeAddress(
|
||||
long clientId,
|
||||
NetworkingClient networkingClient,
|
||||
NetworkingConnector networkingConnector,
|
||||
long identifier)
|
||||
implements UniqueEvent {}
|
||||
|
||||
/** Response to a {@link ChangeAddress} event, carrying the success result. */
|
||||
public record ChangeAddressResponse(boolean successful, long identifier)
|
||||
implements ResponseToUniqueEvent {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.toop.framework.networking.exceptions;
|
||||
|
||||
/**
|
||||
* Thrown when an operation is attempted on a networking client
|
||||
* that does not exist or has already been closed.
|
||||
*/
|
||||
public class ClientNotFoundException extends RuntimeException {
|
||||
|
||||
private final long clientId;
|
||||
|
||||
public ClientNotFoundException(long clientId) {
|
||||
super("Networking client with ID " + clientId + " was not found.");
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public ClientNotFoundException(long clientId, Throwable cause) {
|
||||
super("Networking client with ID " + clientId + " was not found.", cause);
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public long getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.toop.framework.networking.exceptions;
|
||||
|
||||
public class CouldNotConnectException extends RuntimeException {
|
||||
|
||||
private final long clientId;
|
||||
|
||||
public CouldNotConnectException(long clientId) {
|
||||
super("Networking client with ID " + clientId + " could not connect.");
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public CouldNotConnectException(long clientId, Throwable cause) {
|
||||
super("Networking client with ID " + clientId + " could not connect.", cause);
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public long getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.toop.framework.networking;
|
||||
package org.toop.framework.networking.exceptions;
|
||||
|
||||
public class NetworkingInitializationException extends RuntimeException {
|
||||
public NetworkingInitializationException(String message, Throwable cause) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.toop.framework.networking;
|
||||
package org.toop.framework.networking.handlers;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
@@ -1,12 +0,0 @@
|
||||
//package org.toop.frontend.networking.handlers;
|
||||
//
|
||||
//import io.netty.channel.ChannelHandlerContext;
|
||||
//import org.apache.logging.log4j.LogManager;
|
||||
//import org.apache.logging.log4j.Logger;
|
||||
//import org.toop.frontend.networking.NetworkingGameClientHandler;
|
||||
//
|
||||
//public class NetworkingTicTacToeClientHandler extends NetworkingGameClientHandler {
|
||||
// static final Logger logger = LogManager.getLogger(NetworkingTicTacToeClientHandler.class);
|
||||
//
|
||||
//
|
||||
//}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.toop.framework.networking.interfaces;
|
||||
|
||||
import org.toop.framework.networking.exceptions.CouldNotConnectException;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public interface NetworkingClient {
|
||||
InetSocketAddress getAddress();
|
||||
void connect(long clientId, String host, int port) throws CouldNotConnectException;
|
||||
boolean isActive();
|
||||
void writeAndFlush(String msg);
|
||||
void closeConnection();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.toop.framework.networking.interfaces;
|
||||
|
||||
import org.toop.framework.networking.exceptions.ClientNotFoundException;
|
||||
import org.toop.framework.networking.exceptions.CouldNotConnectException;
|
||||
import org.toop.framework.networking.types.NetworkingConnector;
|
||||
|
||||
public interface NetworkingClientManager {
|
||||
void startClient(
|
||||
long id,
|
||||
NetworkingClient nClient,
|
||||
NetworkingConnector nConnector,
|
||||
Runnable onSuccess,
|
||||
Runnable onFailure
|
||||
) throws CouldNotConnectException;
|
||||
void sendCommand(long id, String command) throws ClientNotFoundException;
|
||||
void closeClient(long id) throws ClientNotFoundException;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.toop.framework.networking.types;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public record NetworkingConnector(String host, int port, int reconnectAttempts, long timeout, TimeUnit timeUnit) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.toop.framework.networking.types;
|
||||
|
||||
public record ServerCommand(long clientId, String command) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.toop.framework.networking.types;
|
||||
|
||||
public record ServerMessage(String message) {}
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.toop.framework.audio;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.toop.framework.resource.ResourceMeta;
|
||||
import org.toop.framework.resource.resources.BaseResource;
|
||||
import org.toop.framework.resource.types.AudioResource;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for SoundEffectManager.
|
||||
*/
|
||||
class MockSoundEffectResource extends BaseResource implements AudioResource {
|
||||
boolean played = false;
|
||||
boolean stopped = false;
|
||||
|
||||
public MockSoundEffectResource(String name) {
|
||||
super(new File(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getFile().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play() {
|
||||
played = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnEnd(Runnable callback) {}
|
||||
|
||||
@Override
|
||||
public void setOnError(Runnable callback) {}
|
||||
|
||||
@Override
|
||||
public void updateVolume(double volume) {}
|
||||
}
|
||||
|
||||
public class SoundEffectManagerTest {
|
||||
|
||||
private SoundEffectManager<MockAudioResource> manager;
|
||||
private MockAudioResource sfx1;
|
||||
private MockAudioResource sfx2;
|
||||
private MockAudioResource sfx3;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
sfx1 = new MockAudioResource("explosion.wav");
|
||||
sfx2 = new MockAudioResource("laser.wav");
|
||||
sfx3 = new MockAudioResource("jump.wav");
|
||||
|
||||
List<ResourceMeta<MockAudioResource>> resources = List.of(
|
||||
new ResourceMeta<>("explosion", sfx1),
|
||||
new ResourceMeta<>("laser", sfx2),
|
||||
new ResourceMeta<>("jump", sfx3)
|
||||
);
|
||||
|
||||
manager = new SoundEffectManager<>(resources);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPlayValidSound() {
|
||||
manager.play("explosion", false);
|
||||
assertTrue(sfx1.played, "Sound 'explosion' should be played");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPlayInvalidSoundLogsWarning() {
|
||||
// Nothing should crash or throw
|
||||
assertDoesNotThrow(() -> manager.play("nonexistent", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStopValidSound() {
|
||||
manager.stop("laser");
|
||||
assertTrue(sfx2.stopped, "Sound 'laser' should be stopped");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStopInvalidSoundDoesNotThrow() {
|
||||
assertDoesNotThrow(() -> manager.stop("does_not_exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetActiveAudioReturnsAll() {
|
||||
Collection<MockAudioResource> active = manager.getActiveAudio();
|
||||
assertEquals(3, active.size(), "All three sounds should be in active audio list");
|
||||
assertTrue(active.containsAll(List.of(sfx1, sfx2, sfx3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDuplicateResourceKeepsLast() {
|
||||
MockAudioResource oldRes = new MockAudioResource("duplicate_old.wav");
|
||||
MockAudioResource newRes = new MockAudioResource("duplicate_new.wav");
|
||||
|
||||
List<ResourceMeta<MockAudioResource>> list = new ArrayList<>();
|
||||
list.add(new ResourceMeta<>("dup", oldRes));
|
||||
list.add(new ResourceMeta<>("dup", newRes)); // duplicate key
|
||||
|
||||
SoundEffectManager<MockAudioResource> dupManager = new SoundEffectManager<>(list);
|
||||
dupManager.play("dup", false);
|
||||
|
||||
assertTrue(newRes.played, "New duplicate resource should override old one");
|
||||
assertFalse(oldRes.played, "Old duplicate resource should be discarded");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user