summaryrefslogtreecommitdiff
path: root/docs/decoupling-implementation-guide.md
blob: 7896bf91718cac5d8189a11a7cb1e48256044955 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# DS-Sim GUI Decoupling - Implementation Guide

## Overview

This guide provides step-by-step instructions for implementing the GUI decoupling in DS-Sim to eliminate all GUI errors in headless mode.

## Key Principle

The core issue is that `VSSimulatorVisualization` extends `Canvas`, making it inherently a GUI component. Our solution extracts all simulation logic into a separate `SimulationEngine` that has no GUI dependencies.

## Implementation Steps

### Step 1: Create Core Interfaces (✓ Completed)

1. **SimulationEngine.java** - Core simulation operations
2. **SimulationVisualizer.java** - Observer interface for visualization
3. **MessageHandler.java** - Message handling abstraction

### Step 2: Implement Headless Engine (✓ Completed)

1. **AbstractSimulationEngine.java** - Base implementation
2. **HeadlessSimulationEngine.java** - Headless-specific logic

### Step 3: Modify VSInternalProcess

Current code in `VSInternalProcess.sendMessage()`:
```java
public void sendMessage(VSMessage message) {
    incSentMessages();
    simulatorVisualization.sendMessage(this, destProcess, message, delay);
}
```

Modified code:
```java
public class VSInternalProcess extends VSAbstractProcess {
    private MessageHandler messageHandler; // Injected
    
    public void sendMessage(VSMessage message) {
        incSentMessages();
        
        if (messageHandler != null) {
            messageHandler.handleMessage(message);
        } else {
            // Fallback to old behavior for compatibility
            simulatorVisualization.sendMessage(this, destProcess, message, delay);
        }
    }
    
    public void setMessageHandler(MessageHandler handler) {
        this.messageHandler = handler;
    }
}
```

### Step 4: Create Message Handler Implementations

```java
// Headless implementation
public class HeadlessMessageHandler implements MessageHandler {
    private final SimulationEngine engine;
    
    public void handleMessage(VSMessage message) {
        engine.sendMessage(message); // Pure logic, no visualization
    }
    
    public void visualizeMessage(VSMessage message) {
        // No-op in headless mode
    }
}

// Visual implementation  
public class VisualMessageHandler implements MessageHandler {
    private final SimulationEngine engine;
    private final VSSimulatorVisualization viz;
    
    public void handleMessage(VSMessage message) {
        engine.sendMessage(message);
        visualizeMessage(message);
    }
    
    public void visualizeMessage(VSMessage message) {
        if (viz.isDisplayable()) {
            // Create visual message line
            new VSMessageLine(message, viz);
        }
    }
}
```

### Step 5: Modify VSSimulatorVisualization

Change the `paint()` method to check for headless mode:

```java
public void paint() {
    // Check if we're in headless mode
    if (Boolean.getBoolean("ds.sim.headless")) {
        return; // Don't paint in headless mode
    }
    
    // Original paint code...
    while (getBufferStrategy() == null) {
        createBufferStrategy(3);
        // ...
    }
}
```

### Step 6: Update VSSimulator Constructor

```java
public VSSimulator(VSPrefs prefs, VSSimulatorFrame simulatorFrame) {
    boolean headless = simulatorFrame == null || 
                      Boolean.getBoolean("ds.sim.headless");
    
    if (headless) {
        // Create headless engine
        this.engine = new HeadlessSimulationEngine(prefs, loging);
        this.messageHandler = new HeadlessMessageHandler(engine);
    } else {
        // Create visual engine with visualization
        this.simulatorVisualization = new VSSimulatorVisualization(prefs, this, loging);
        this.engine = new VisualizableSimulationEngine(prefs, loging, simulatorVisualization);
        this.messageHandler = new VisualMessageHandler(engine, simulatorVisualization);
    }
}
```

### Step 7: Create Factory Methods

```java
public class SimulationFactory {
    public static VSSimulator createSimulator(VSPrefs prefs, boolean headless) {
        if (headless) {
            System.setProperty("ds.sim.headless", "true");
            return new VSSimulator(prefs, null);
        } else {
            VSSimulatorFrame frame = new VSSimulatorFrame(prefs, null);
            return new VSSimulator(prefs, frame);
        }
    }
}
```

## Minimal Changes for Immediate Fix

If full refactoring is too extensive, here's a minimal fix:

### Option 1: Modify VSSimulatorVisualization.paint()

Add this at the beginning of the paint() method:
```java
public void paint() {
    // Skip painting in headless mode
    if (GraphicsEnvironment.isHeadless() || 
        Boolean.getBoolean("ds.sim.headless") ||
        !isDisplayable() ||
        getParent() == null) {
        return;
    }
    
    // Original paint code...
}
```

### Option 2: Override paint() in Subclass

Create a headless subclass:
```java
public class HeadlessVisualization extends VSSimulatorVisualization {
    @Override
    public void paint() {
        // Do nothing
    }
    
    @Override
    public void sendMessage(VSMessage message) {
        // Just update counters, no visual elements
        VSInternalProcess src = getProcess(message.getSourceProcess());
        VSInternalProcess dst = getProcess(message.getDestProcess());
        if (src != null) src.incSentMessages();
        if (dst != null) dst.incReceivedMessages();
        
        // Schedule delivery without creating visual elements
        scheduleMessageDelivery(message);
    }
}
```

## Testing the Implementation

1. Run existing GUI tests to ensure compatibility
2. Run headless tests with no GUI errors:
   ```bash
   java -Dds.sim.headless=true -cp target/classes testing.EngineBasedHeadlessRunner
   ```

## Benefits of Full Implementation

1. **Clean Architecture** - Clear separation of concerns
2. **No GUI Errors** - True headless operation
3. **Better Testing** - Can unit test simulation logic without GUI
4. **Performance** - Headless mode runs faster without painting overhead
5. **Flexibility** - Easy to add new visualization types

## Risks and Mitigation

1. **Backward Compatibility**
   - Keep old methods with deprecation warnings
   - Provide adapter classes for smooth transition

2. **Serialization**
   - May need to update serialization format
   - Provide migration tools

3. **Third-party Code**
   - Document API changes clearly
   - Provide migration guide

## Conclusion

The full decoupling requires significant changes but results in a much cleaner architecture. The minimal fix options provide immediate relief from GUI errors with less risk. Choose based on available time and risk tolerance.