package simulator.builder; import simulator.*; import core.*; import prefs.*; import events.*; import events.internal.*; import serialize.VSSerialize; import java.io.*; import java.lang.reflect.*; import java.util.*; import java.util.ArrayList; /** * Builder for creating DS-Sim simulations programmatically without GUI. * * Example usage: *
 * SimulationBuilder builder = new SimulationBuilder()
 *     .withProcesses(5)
 *     .withProtocol("protocols.implementations.VSRaftProtocol")
 *     .activateServers(0, 1, 2)
 *     .activateClientsAt(1000, 3, 4)
 *     .addCrashEvent(0, 2000)
 *     .addRecoveryEvent(0, 3000)
 *     .save("saved-simulations/my-raft.dat");
 * 
*/ public class SimulationBuilder { private VSDefaultPrefs prefs; private VSSimulator simulator; private VSSimulatorVisualization visualization; private VSTaskManager taskManager; private String protocolClass; private int numProcesses = 3; // default private List scheduledTasks = new ArrayList<>(); private Map> protocolLongOverrides = new HashMap<>(); private int simulationDuration = 10000; // default 10 seconds /** * Internal class to hold task scheduling information */ private static class ScheduledTask { long time; int processId; VSAbstractEvent event; boolean isGlobalTimed; ScheduledTask(long time, int processId, VSAbstractEvent event, boolean isGlobalTimed) { this.time = time; this.processId = processId; this.event = event; this.isGlobalTimed = isGlobalTimed; } } /** * Initialize the builder with default preferences */ public SimulationBuilder() throws Exception { // Initialize preferences prefs = new VSDefaultPrefs(); prefs.fillWithDefaults(); // Initialize registered events VSRegisteredEvents.init(prefs); } /** * Set the simulation duration in milliseconds */ public SimulationBuilder withDuration(int durationMs) { this.simulationDuration = durationMs; return this; } /** * Set the number of processes in the simulation */ public SimulationBuilder withProcesses(int count) { this.numProcesses = count; return this; } /** * Set the protocol class to use */ public SimulationBuilder withProtocol(String protocolClassName) { this.protocolClass = protocolClassName; return this; } /** * Activate protocol as server on specified processes */ public SimulationBuilder activateServers(int... processIds) { for (int pid : processIds) { VSProtocolEvent event = new VSProtocolEvent(); event.onInit(); // Initialize the event first setProtocolClassname(event, protocolClass); setIsServer(event, true); attachProtocolOverrides(event, pid); scheduledTasks.add(new ScheduledTask(0, pid, event, true)); } return this; } /** * Activate protocol as client on specified processes */ public SimulationBuilder activateClients(int... processIds) { return activateClientsAt(500L, processIds); // default delay } /** * Activate protocol as client on specified processes with custom start time */ public SimulationBuilder activateClientsAt(long startTime, int... processIds) { for (int i = 0; i < processIds.length; i++) { VSProtocolEvent event = new VSProtocolEvent(); event.onInit(); // Initialize the event first setProtocolClassname(event, protocolClass); setIsServer(event, false); attachProtocolOverrides(event, processIds[i]); // Stagger client starts long time = startTime + (i * 200); scheduledTasks.add(new ScheduledTask(time, processIds[i], event, true)); } return this; } /** * Override a long preference on the protocol instance for a given process. * * @param processId the process index in the simulation * @param key the protocol preference key * @param value the value to apply * @return this builder */ public SimulationBuilder setProtocolLong(int processId, String key, long value) { protocolLongOverrides .computeIfAbsent(Integer.valueOf(processId), pid -> new HashMap<>()) .put(key, Long.valueOf(value)); for (ScheduledTask scheduledTask : scheduledTasks) { if (scheduledTask.processId == processId && scheduledTask.event instanceof VSProtocolEvent protocolEvent) { protocolEvent.setLongOverride(key, value); } } return this; } /** * Add a process crash event */ public SimulationBuilder addCrashEvent(int processId, long time) { try { // Use reflection to create crash event Class crashClass = Class.forName("events.implementations.VSProcessCrashEvent"); VSAbstractEvent crashEvent = (VSAbstractEvent) crashClass.getDeclaredConstructor().newInstance(); scheduledTasks.add(new ScheduledTask(time, processId, crashEvent, true)); } catch (Exception e) { throw new RuntimeException("Failed to create crash event", e); } return this; } /** * Add a process recovery event */ public SimulationBuilder addRecoveryEvent(int processId, long time) { try { // Use reflection to create recovery event Class recoverClass = Class.forName("events.implementations.VSProcessRecoverEvent"); VSAbstractEvent recoverEvent = (VSAbstractEvent) recoverClass.getDeclaredConstructor().newInstance(); scheduledTasks.add(new ScheduledTask(time, processId, recoverEvent, true)); } catch (Exception e) { throw new RuntimeException("Failed to create recovery event", e); } return this; } /** * Add a custom event */ public SimulationBuilder addEvent(String eventClassName, int processId, long time) { try { Class eventClass = Class.forName(eventClassName); VSAbstractEvent event = (VSAbstractEvent) eventClass.getDeclaredConstructor().newInstance(); scheduledTasks.add(new ScheduledTask(time, processId, event, true)); } catch (Exception e) { throw new RuntimeException("Failed to create event: " + eventClassName, e); } return this; } /** * Set protocol classname using reflection (since field is private) */ private void setProtocolClassname(VSProtocolEvent event, String classname) { try { Field field = VSProtocolEvent.class.getDeclaredField("protocolClassname"); field.setAccessible(true); field.set(event, classname); } catch (Exception e) { throw new RuntimeException("Failed to set protocol classname", e); } } /** * Set isServer flag using reflection (field is called isClientProtocol) */ private void setIsServer(VSProtocolEvent event, boolean isServer) { try { // The field is actually called isClientProtocol, and server = !client Field field = VSProtocolEvent.class.getDeclaredField("isClientProtocol"); field.setAccessible(true); field.set(event, !isServer); // Invert: server means NOT client // Also set protocol activation to true Field activationField = VSProtocolEvent.class.getDeclaredField("isProtocolActivation"); activationField.setAccessible(true); activationField.set(event, true); } catch (Exception e) { throw new RuntimeException("Failed to set protocol flags", e); } } /** * Build the simulation (must be called before save) */ private void build() throws Exception { if (simulator != null) { return; // Already built } // Set simulation duration prefs.setInteger("sim.seconds", simulationDuration / 1000); // Set network delay parameters for message delivery prefs.setInteger("process.msg.delay.min", 10); // 10ms minimum delay prefs.setInteger("process.msg.delay.max", 50); // 50ms maximum delay // Create simulator without frame for headless simulator = new VSSimulator(prefs, null); // Create visualization without GUI VSLogging logging = new VSLogging(); visualization = new VSSimulatorVisualization(prefs, simulator, logging); // Set visualization in simulator using reflection Field vizField = VSSimulator.class.getDeclaredField("simulatorVisualization"); vizField.setAccessible(true); vizField.set(simulator, visualization); // Add processes if needed (default is 3) Method addProcessMethod = VSSimulatorVisualization.class.getDeclaredMethod("addProcess"); addProcessMethod.setAccessible(true); // Remove default processes if we want fewer if (numProcesses < 3) { Field processesField = VSSimulatorVisualization.class.getDeclaredField("processes"); processesField.setAccessible(true); ArrayList processes = (ArrayList) processesField.get(visualization); while (processes.size() > numProcesses) { processes.remove(processes.size() - 1); } } // Add more processes if needed for (int i = 3; i < numProcesses; i++) { addProcessMethod.invoke(visualization); } // Get task manager taskManager = visualization.getTaskManager(); // Initialize all events with their processes for (ScheduledTask st : scheduledTasks) { VSInternalProcess process = visualization.getProcess(st.processId); if (process == null) { throw new IllegalStateException( "No process " + st.processId + " exists for " + st.event.getClass().getSimpleName() + " at time " + st.time); } st.event.init(process); // For protocol events, update the shortname after init if (st.event instanceof VSProtocolEvent) { VSProtocolEvent protocolEvent = (VSProtocolEvent) st.event; // Force shortname update by calling the method via reflection try { Method createShortname = VSProtocolEvent.class.getDeclaredMethod("createShortname", String.class); createShortname.setAccessible(true); String shortname = (String) createShortname.invoke(protocolEvent, (String)null); protocolEvent.setShortname(shortname); } catch (Exception e) { // Ignore } } // Create task VSTask task = new VSTask(st.time, process, st.event, st.isGlobalTimed ? VSTask.GLOBAL : VSTask.LOCAL); taskManager.addTask(task, VSTaskManager.PROGRAMMED); } } /** * Apply any stored protocol overrides to a protocol activation event. */ private void attachProtocolOverrides(VSProtocolEvent event, int processId) { Map longOverrides = protocolLongOverrides.get(Integer.valueOf(processId)); if (longOverrides == null || longOverrides.isEmpty()) { return; } for (Map.Entry entry : longOverrides.entrySet()) { event.setLongOverride(entry.getKey(), entry.getValue().longValue()); } } /** * Save the simulation to a file */ public SimulationBuilder save(String filename) throws Exception { build(); File outputFile = new File(filename); outputFile.getParentFile().mkdirs(); VSSerialize serialize = new VSSerialize(); // Save using the serializer try { FileOutputStream fos = new FileOutputStream(outputFile); ObjectOutputStream oos = new ObjectOutputStream(fos); // Create serializable prefs from our prefs VSSerializablePrefs serializablePrefs = new VSSerializablePrefs(); // Copy all preferences for (String key : prefs.getIntegerKeySet()) { serializablePrefs.initInteger(key, prefs.getInteger(key)); } for (String key : prefs.getBooleanKeySet()) { serializablePrefs.initBoolean(key, prefs.getBoolean(key)); } for (String key : prefs.getStringKeySet()) { serializablePrefs.initString(key, prefs.getString(key)); } for (String key : prefs.getFloatKeySet()) { serializablePrefs.initFloat(key, prefs.getFloat(key)); } for (String key : prefs.getColorKeySet()) { serializablePrefs.initColor(key, prefs.getColor(key)); } for (String key : prefs.getVectorKeySet()) { serializablePrefs.initVector(key, prefs.getVector(key)); } for (String key : prefs.getLongKeySet()) { serializablePrefs.initLong(key, prefs.getLong(key)); } // Serialize preferences first serializablePrefs.serialize(serialize, oos); // Then serialize simulator simulator.serialize(serialize, oos); oos.close(); fos.close(); System.out.println("Simulation saved to: " + outputFile.getAbsolutePath()); } catch (Exception e) { throw new RuntimeException("Failed to save simulation", e); } return this; } /** * Get the built simulator (for testing/verification) */ public VSSimulator getSimulator() throws Exception { build(); return simulator; } /** * Fluent API for common protocol setups */ public static class Protocols { public static final String PING_PONG = "protocols.implementations.VSPingPongProtocol"; public static final String BERKLEY_TIME = "protocols.implementations.VSBerkelyTimeProtocol"; public static final String BROADCAST = "protocols.implementations.VSBroadcastProtocol"; public static final String ONE_PHASE_COMMIT = "protocols.implementations.VSOnePhaseCommitProtocol"; public static final String TWO_PHASE_COMMIT = "protocols.implementations.VSTwoPhaseCommitProtocol"; public static final String RELIABLE_MULTICAST = "protocols.implementations.VSReliableMulticastProtocol"; public static final String RAFT = "protocols.implementations.VSRaftProtocol"; } }