Files
Progetto-ingegneria-del-sof…/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java
T
2026-06-19 22:14:29 +02:00

220 lines
8.7 KiB
Java

package it.polimi.ingsw.gc14;
import it.polimi.ingsw.gc14.Controller.GameController;
import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Model.MiniModel;
import it.polimi.ingsw.gc14.Model.Player;
import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
import it.polimi.ingsw.gc14.Network.NetworkConfig;
import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
import java.net.*;
import java.rmi.RemoteException;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* Main server entry point.
*
* <p>Responsibilities of this class are intentionally limited to wiring:
* it creates all components, connects them together, restores a previously
* saved game if one exists, and starts the event-processing loop and the
* network servers.
*
* <p>All game-logic and event-routing decisions are delegated to
* {@link GameEventProcessor}; persistence is delegated to {@link SaveManager}.
*
* <p>The original {@code ServerLauncher} class is preserved and untouched.
* This class is a clean replacement that uses the new component structure.
*/
public class ServerLauncher {
/** Drives the main game-event loop. */
private final GameEventProcessor eventProcessor;
/**
* Constructs a {@code ServerLauncherTest} and wires all components together.
*
* @param actionQueue the shared event queue.
* @param gameController the server-side game controller.
* @param playerList the shared player-status map.
* @param broadcaster the broadcaster used to reach all connected clients.
* @param saveManager the save manager used to persist game state.
*/
public ServerLauncher(
BlockingQueue<NetworkEvent> actionQueue,
GameController gameController,
LimitedMap<String, Boolean> playerList,
CompositeClientBroadcaster broadcaster,
SaveManager saveManager) {
this.eventProcessor = new GameEventProcessor(
actionQueue, gameController, playerList, broadcaster, saveManager);
}
/**
* Initializes all server components, wires them together, restores a saved
* game if available, and starts the event-processing loop and both network
* servers.
*
* @param args command-line arguments (unused).
* @throws RemoteException if the RMI server cannot be created.
*/
static void main(String[] args) throws RemoteException {
LimitedMap<String, Boolean> playerList = new LimitedMap<>(5, () -> {});
BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>();
GameController gameController = new GameController();
String ip;
try {
ip = chooseNetworkInterface(new Scanner(System.in));
System.out.println(ip);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.setProperty("java.rmi.server.hostname", ip);
RMIServer rmiServer = new RMIServer(gameController, NetworkConfig.RMI_PORT, actionQueue, playerList, ip);
TCPServer tcpServer = new TCPServer(gameController, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT, actionQueue, playerList);
CompositeClientBroadcaster broadcaster = new CompositeClientBroadcaster(rmiServer, tcpServer);
SaveManager saveManager = new SaveManager(ServerLauncher.class);
restoreGameIfSaved(gameController, playerList, saveManager);
ServerLauncher launcher = new ServerLauncher(
actionQueue, gameController, playerList, broadcaster, saveManager);
// When all required players have joined, broadcast the initial MiniModel.
playerList.setAction(() -> new Thread(() -> {
MiniModel miniModel;
synchronized (gameController) {
Game game = gameController.getModel();
miniModel = new MiniModel(
game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),
game.getPlayers(), game.getAvailableTotems(),
game.getUpperListTribeCards(), game.getLowerListTribeCards(),
game.getUpperListBuilding(), game.getLowerListBuilding(),
game.getDisconnectedPlayers().entrySet().stream()
.filter(Map.Entry::getValue)
.map(e -> e.getKey().getUserName())
.collect(Collectors.toCollection(ArrayList::new))
);
}
broadcaster.notifyAll(miniModel);
}).start());
new Thread(() -> {
try { launcher.run(); }
catch (Exception e) {
System.out.println("Unexpected exception in game loop");
e.printStackTrace();
}
}).start();
rmiServer.start();
new Thread(() -> tcpServer.start()).start();
}
/**
* Runs the event-processing loop until the thread is interrupted.
*
* <p>A {@link ConcurrentModificationException} is caught and logged rather
*/
public void run() {
while (true) {
try {
eventProcessor.doFirstEvent();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
/**
* Attempts to restore a previously saved game.
*
* <p>The save is discarded if all but at most one player was offline at the
* time of the crash, since there would be nobody to resume the game with.
* Otherwise, the model is restored, the player-list limit is set, and each
* player who was offline at crash time is pre-populated as offline so the
* reconnection flow can handle them correctly.
*
* @param gameController the controller that will receive the restored model.
* @param playerList the player-status map to populate.
* @param saveManager the save manager to load from.
*/
private static void restoreGameIfSaved(
GameController gameController,
LimitedMap<String, Boolean> playerList,
SaveManager saveManager) {
Game game = saveManager.load();
if (game == null) return;
long disconnectedCount = game.getDisconnectedPlayers().entrySet().stream()
.filter(Map.Entry::getValue).count();
if (disconnectedCount >= game.getNPlayers() - 1) {
saveManager.delete();
System.out.println("Save discarded: too many players were offline at crash time.");
return;
}
gameController.setModel(game);
playerList.setLimit(game.getNPlayers());
for (Map.Entry<Player, Boolean> entry : game.getDisconnectedPlayers().entrySet()) {
if (entry.getValue()) {
playerList.put(entry.getKey().getUserName(), false);
}
}
System.out.println("Game restored from save (" + game.getNPlayers() + " players).");
}
/**
* Lists active non-loopback IPv4 network interfaces and prompts the operator
* to choose one. If only one interface is available it is selected automatically.
*
* @param scanner the scanner used to read the operator's choice.
* @return the IPv4 address of the selected interface.
* @throws Exception if no valid network interface is available.
*/
public static String chooseNetworkInterface(Scanner scanner) throws Exception {
List<String> ips = new ArrayList<>();
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue;
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
if (addr instanceof Inet4Address) {
System.out.println("[" + ips.size() + "] " + ni.getDisplayName()
+ " -> " + addr.getHostAddress());
ips.add(addr.getHostAddress());
}
}
}
if (ips.isEmpty()) throw new Exception("No active network interface available");
if (ips.size() == 1) {
System.out.println("One interface found, using: " + ips.get(0));
return ips.get(0);
}
System.out.print("Choose interface: ");
int choice = Integer.parseInt(scanner.nextLine().trim());
return ips.get(choice);
}
}