summaryrefslogtreecommitdiff
path: root/src/main/java/simulator/builder/SimulationBuilder.java
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-22 11:58:00 +0300
committerPaul Buetow <paul@buetow.org>2025-06-22 11:58:00 +0300
commit0b5afe8839241dec66ba832cf42860ec69b87df8 (patch)
treee100d2d6204f8c04dc33418ae9f193fa6b1a83c2 /src/main/java/simulator/builder/SimulationBuilder.java
parentb0fc02ce45cb51ce7c8d607d4773808cfa9b6c87 (diff)
Fix message delivery in headless test environment
- Fixed HeadlessSimulationEngine to use correct task manager from receiving process - Reduced message delays for testing (10-50ms instead of 500-2000ms) - Fixed process ID method call (getProcessID not getProcessId) - Improved message delivery scheduling to ensure tasks go to the right task manager This resolves message delivery issues where messages were sent but not received. BasicMulticast test now passes, but 12 protocol tests still failing. 🤖 Generated with Claude Code https://claude.ai/code Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'src/main/java/simulator/builder/SimulationBuilder.java')
-rw-r--r--src/main/java/simulator/builder/SimulationBuilder.java362
1 files changed, 362 insertions, 0 deletions
diff --git a/src/main/java/simulator/builder/SimulationBuilder.java b/src/main/java/simulator/builder/SimulationBuilder.java
new file mode 100644
index 0000000..8ac5d04
--- /dev/null
+++ b/src/main/java/simulator/builder/SimulationBuilder.java
@@ -0,0 +1,362 @@
+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:
+ * <pre>
+ * SimulationBuilder builder = new SimulationBuilder()
+ * .withProcesses(5)
+ * .withProtocol("protocols.implementations.VSRaftProtocol")
+ * .activateServers(0, 1, 2)
+ * .activateClients(3, 4)
+ * .addCrashEvent(0, 2000)
+ * .addRecoveryEvent(0, 3000)
+ * .save("saved-simulations/my-raft.dat");
+ * </pre>
+ */
+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<ScheduledTask> scheduledTasks = new ArrayList<>();
+ 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);
+
+ scheduledTasks.add(new ScheduledTask(0, pid, event, true));
+ }
+ return this;
+ }
+
+ /**
+ * Activate protocol as client on specified processes
+ */
+ public SimulationBuilder activateClients(int... processIds) {
+ return activateClients(500, processIds); // default delay
+ }
+
+ /**
+ * Activate protocol as client on specified processes with custom start time
+ */
+ public SimulationBuilder activateClients(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);
+
+ // Stagger client starts
+ long time = startTime + (i * 200);
+ scheduledTasks.add(new ScheduledTask(time, processIds[i], event, true));
+ }
+ 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<VSInternalProcess> processes = (ArrayList<VSInternalProcess>) 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);
+ 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);
+ }
+ }
+
+ /**
+ * 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 RAFT = "protocols.implementations.VSRaftProtocol";
+ 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";
+ }
+} \ No newline at end of file