Temporary refactor of code
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
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.ClientBroadcaster;
|
||||
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.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 ServerLauncherTest {
|
||||
|
||||
/** 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 ServerLauncherTest(
|
||||
BlockingQueue<NetworkEvent> actionQueue,
|
||||
GameController gameController,
|
||||
LimitedMap<String, Boolean> playerList,
|
||||
ClientBroadcaster broadcaster,
|
||||
SaveManager saveManager) {
|
||||
this.eventProcessor = new GameEventProcessor(
|
||||
actionQueue, gameController, playerList, broadcaster, saveManager);
|
||||
}
|
||||
|
||||
// ── Entry point ───────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Initialises 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.
|
||||
*/
|
||||
public 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, 1099, actionQueue, playerList, ip);
|
||||
TCPServer tcpServer = new TCPServer(gameController, 8080, 8081, actionQueue, playerList);
|
||||
ClientBroadcaster broadcaster = new CompositeClientBroadcaster(rmiServer, tcpServer);
|
||||
SaveManager saveManager = new SaveManager(ServerLauncherTest.class);
|
||||
|
||||
restoreGameIfSaved(gameController, playerList, saveManager);
|
||||
|
||||
ServerLauncherTest launcher = new ServerLauncherTest(
|
||||
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.orderLogicCard, game.getCurrentState(),
|
||||
game.getPlayers(), game.getAvailableTotems(),
|
||||
game.getUpperListTribeCards(), game.getLowerListTribeCards(),
|
||||
game.getUpperListBuilding(), game.getLowerListBuilding(),
|
||||
game.disconnetedPlayers.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();
|
||||
System.out.println("Server RMI: " + System.getProperty("java.rmi.server.hostname"));
|
||||
}
|
||||
|
||||
// ── Game loop ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Runs the event-processing loop until the thread is interrupted.
|
||||
*
|
||||
* <p>A {@link ConcurrentModificationException} is caught and logged rather
|
||||
* than propagated; it is caused by an unsafe {@link ArrayList} in
|
||||
* {@code TCPServer.clientHandlers} (tracked as a separate issue) and does
|
||||
* not leave the game in an inconsistent state.
|
||||
*/
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
eventProcessor.doFirstEvent();
|
||||
} catch (ConcurrentModificationException e) {
|
||||
System.err.println("Concurrent modification in notifyAll — skipping tick");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Crash recovery ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 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.disconnetedPlayers.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.disconnetedPlayers.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
playerList.put(entry.getKey().getUserName(), false);
|
||||
}
|
||||
}
|
||||
System.out.println("Game restored from save (" + game.getNPlayers() + " players).");
|
||||
}
|
||||
|
||||
// ── Network interface selection ───────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user