package bench;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import megamek.common.compute.Compute;
import megamek.common.game.Game;
import megamek.common.game.IGame;
import megamek.common.loaders.MekSummaryCache;
import megamek.common.scenario.Scenario;
import megamek.common.scenario.ScenarioLoader;
import megamek.common.units.Entity;
import megamek.server.Server;
import megamek.server.totalWarfare.TWGameManager;
/**
* Does two games in one JVM actually give you two independent games?
*
*
The multi-tenancy question hinges on this. If {@code scenario.createGame()} hands
* back Entity instances that are shared with a previously created game - because the
* unit cache hands out references rather than copies - then two servers in one JVM are
* silently corrupting each other and no amount of memory saving is worth it.
*
*
Checks, in order of how fatal a failure would be:
*
* - Entity object identity across two games from the same scenario.
* - Entity ID space overlap (expected to overlap; only a problem if objects are shared too).
* - Game/Board/Player object identity.
* - Whether two Servers can bind and coexist at all.
*
*/
public final class IsolationCheck {
public static void main(String[] args) throws Exception {
String scenarioPath = args.length > 0
? args[0]
: "data/scenarios/TrainingScenarios/1-FirstRun.mms";
Compute.d6();
MekSummaryCache cache = MekSummaryCache.getInstance();
while (!cache.isInitialized()) {
Thread.sleep(250L);
}
System.out.printf("unit cache ready (%d units)%n%n", cache.getAllMeks().length);
IGame gameA = load(scenarioPath);
IGame gameB = load(scenarioPath);
List a = new ArrayList<>(((Game) gameA).getEntitiesVector());
List b = new ArrayList<>(((Game) gameB).getEntitiesVector());
System.out.printf("game A entities: %d, game B entities: %d%n", a.size(), b.size());
// 1. THE question: are any Entity objects literally shared between games?
Set identitiesA = new HashSet<>();
for (Entity e : a) {
identitiesA.add(System.identityHashCode(e));
}
int shared = 0;
for (Entity e : b) {
if (identitiesA.contains(System.identityHashCode(e))) {
shared++;
}
}
System.out.printf("%n[1] shared Entity instances between games: %d -> %s%n",
shared, shared == 0 ? "PASS (independent objects)" : "FAIL (games would corrupt each other)");
// 2. ID overlap is fine on its own - each Game indexes its own entities.
Set idsA = new HashSet<>();
for (Entity e : a) {
idsA.add(e.getId());
}
int idOverlap = 0;
for (Entity e : b) {
if (idsA.contains(e.getId())) {
idOverlap++;
}
}
System.out.printf("[2] entity ID overlap: %d of %d -> %s%n",
idOverlap, b.size(),
"expected; per-Game id space, only fatal if [1] failed");
// 3. Container objects must also be distinct.
System.out.printf("[3] Game objects distinct: %s%n", gameA != gameB);
System.out.printf(" Board objects distinct: %s%n",
gameA.getBoard(0) != gameB.getBoard(0));
System.out.printf(" Player lists distinct: %s%n",
gameA.getPlayersList() != gameB.getPlayersList());
// 4. Can two Servers coexist in one JVM?
System.out.println();
TWGameManager gmA = new TWGameManager();
TWGameManager gmB = new TWGameManager();
Server serverA = new Server("", 9901, gmA);
Server serverB = new Server("", 9902, gmB);
serverA.setGame(gameA);
serverB.setGame(gameB);
System.out.printf("[4] two Servers constructed and bound: PASS (ports 9901/9902)%n");
System.out.printf(" server A game == game A: %s%n", serverA.getGame() == gameA);
System.out.printf(" server B game == game B: %s%n", serverB.getGame() == gameB);
System.out.printf(" servers see different games: %s%n", serverA.getGame() != serverB.getGame());
// 5. The blocker. Server's constructor ends with `serverInstance = this`, and
// AbstractGameManager.send() routes every outbound packet through
// Server.getServerInstance(). So the moment a second Server exists, the first
// game's manager starts transmitting to the second game's connections.
// Isolation of the *data* (checks 1-3) is real; isolation of the *transport*
// is not.
Server current = Server.getServerInstance();
System.out.println();
System.out.printf("[5] Server.getServerInstance() == serverB (newest): %s%n", current == serverB);
System.out.printf(" Server.getServerInstance() == serverA (first): %s%n", current == serverA);
System.out.printf(" -> game A's manager would send packets to %s%n",
current == serverB ? "SERVER B ** WRONG GAME **" : "server A");
System.out.printf(" verdict: %s%n",
current == serverA
? "ok"
: "FAIL - concurrent games in one JVM corrupt each other's transport");
serverA.die();
serverB.die();
System.exit(0);
}
private static IGame load(String scenarioPath) throws Exception {
ScenarioLoader loader = new ScenarioLoader(new File(scenarioPath));
Scenario scenario = loader.load();
return scenario.createGame();
}
}