Skip to content

Commit ab88f16

Browse files
refactor: move graph functionality to new Graph ADT (#154)
This PR adds a `Graph` class with the simple logic of storing nodes and edges, and uses it internally in the `DataGraph` class. --------- Co-authored-by: Pedro Gallino <[email protected]>
1 parent e82a8c3 commit ab88f16

File tree

12 files changed

+180
-199
lines changed

12 files changed

+180
-199
lines changed

src/context.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ export class GlobalContext {
133133

134134
private setIpGenerator() {
135135
let maxIp = IpAddress.parse("10.0.0.0");
136-
this.datagraph.getDevices().forEach((device) => {
136+
for (const [, device] of this.datagraph.getDevices()) {
137137
if (isNetworkNode(device)) {
138138
const ip = IpAddress.parse(device.ip);
139139
if (maxIp.octets < ip.octets) {
140140
maxIp = ip;
141141
}
142142
}
143-
});
143+
}
144144
// TODO: we should use IpAddress instead of string here and in Datagraph
145145
const baseIp = maxIp.toString();
146146
const mask = "255.255.255.255";
@@ -149,14 +149,14 @@ export class GlobalContext {
149149

150150
private setMacGenerator() {
151151
let maxMac = MacAddress.parse("00:00:10:00:00:00");
152-
this.datagraph.getDevices().forEach((device) => {
152+
for (const [, device] of this.datagraph.getDevices()) {
153153
if (isLinkNode(device)) {
154154
const mac = MacAddress.parse(device.mac);
155155
if (maxMac.octets < mac.octets) {
156156
maxMac = mac;
157157
}
158158
}
159-
});
159+
}
160160
// TODO: we should use MacAddress instead of string here and in Datagraph
161161
const baseMac = maxMac.toString();
162162
this.macGenerator = new MacAddressGenerator(baseMac);

src/graphics/renderables/device_info.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ export class DeviceInfo extends StyledInfo {
4242
() => {
4343
const deviceData = this.device.getCreateDevice();
4444
const currLayer = this.device.viewgraph.getLayer();
45-
const move = new RemoveDeviceMove(
46-
currLayer,
47-
deviceData,
48-
this.device.viewgraph,
49-
);
45+
const move = new RemoveDeviceMove(currLayer, deviceData);
5046
this.device.delete();
5147
urManager.push(move);
5248
},

src/types/devices/device.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ export abstract class Device extends Container {
135135
/// Returns the data needed to create the device
136136
getCreateDevice(): CreateDevice {
137137
const node = this.viewgraph.getDataGraph().getDevice(this.id);
138-
return { id: this.id, node };
138+
const connections = this.viewgraph.getDataGraph().getConnections(this.id);
139+
return { id: this.id, node, connections };
139140
}
140141

141142
addConnection(adjacentId: DeviceId) {

src/types/devices/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Switch } from "./switch";
1111
export interface CreateDevice {
1212
id: DeviceId;
1313
node: GraphNode;
14+
connections: DeviceId[];
1415
}
1516

1617
export function createDevice(

src/types/graphs/datagraph.ts

Lines changed: 51 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { RunningProgram } from "../../programs";
22
import { DeviceType, Layer, layerFromType } from "../devices/device";
33
import { layerIncluded } from "../devices/layer";
4+
import { Graph, VertexId } from "./graph";
45

5-
export type DeviceId = number;
6+
export type DeviceId = VertexId;
67

78
interface CommonGraphNode {
89
x: number;
910
y: number;
1011
type: DeviceType;
11-
connections: Set<DeviceId>;
1212
}
1313

1414
interface LinkGraphNode extends CommonGraphNode {
@@ -120,34 +120,28 @@ export interface NewDevice {
120120
}
121121

122122
export class DataGraph {
123-
private devices = new Map<DeviceId, GraphNode>();
123+
// NOTE: we don't store data in edges yet
124+
private deviceGraph = new Graph<GraphNode, unknown>();
124125
private idCounter: DeviceId = 1;
125126
private onChanges: (() => void)[] = [];
126127

127128
static fromData(data: GraphData): DataGraph {
128129
const dataGraph = new DataGraph();
129130
data.forEach((nodeData: GraphDataNode) => {
130131
console.log(nodeData);
131-
const connections = new Set(nodeData.connections);
132132

133-
let graphNode: GraphNode;
133+
let graphNode: GraphNode = nodeData;
134134

135135
if (nodeData.type === DeviceType.Router) {
136136
// If the node is a router, include the routing table
137137
const routerNode = nodeData as RouterDataNode;
138138
graphNode = {
139139
...routerNode,
140-
connections: connections,
141140
routingTable: routerNode.routingTable || [], // Ensure routingTable exists
142141
};
143-
} else {
144-
graphNode = {
145-
...nodeData,
146-
connections: connections,
147-
};
148142
}
149143

150-
dataGraph.addDevice(nodeData.id, graphNode);
144+
dataGraph.addDevice(nodeData.id, graphNode, nodeData.connections);
151145
});
152146

153147
return dataGraph;
@@ -157,43 +151,45 @@ export class DataGraph {
157151
const graphData: GraphData = [];
158152

159153
// Serialize nodes
160-
this.getDevices().forEach((info, id) => {
154+
for (const [id, info] of this.deviceGraph.getVertices()) {
161155
const graphNode: GraphDataNode = {
162156
...info,
163157
id,
164-
connections: Array.from(info.connections.values()),
158+
connections: Array.from(this.deviceGraph.getNeighbors(id)),
165159
};
166160
graphData.push(graphNode);
167-
});
161+
}
168162
return graphData;
169163
}
170164

171165
// Add a new device to the graph
172166
addNewDevice(deviceInfo: NewDevice): DeviceId {
173167
const id = this.idCounter++;
174-
// const graphNode: GraphNode = DataGraph.createGraphNode(deviceInfo);
175168
const graphnode: GraphNode = {
176169
...deviceInfo,
177-
connections: new Set<number>(),
178170
routingTable: [],
179171
runningPrograms: [],
180172
arpTable: new Map(),
181173
};
182-
this.devices.set(id, graphnode);
174+
this.deviceGraph.setVertex(id, graphnode);
183175
console.log(`Device added with ID ${id}`);
184176
this.notifyChanges();
185177
return id;
186178
}
187179

188180
// Add a device to the graph
189-
addDevice(idDevice: DeviceId, deviceInfo: GraphNode) {
190-
if (this.devices.has(idDevice)) {
181+
addDevice(
182+
idDevice: DeviceId,
183+
deviceInfo: GraphNode,
184+
connections: DeviceId[],
185+
) {
186+
if (this.deviceGraph.hasVertex(idDevice)) {
191187
console.warn(`Device with ID ${idDevice} already exists in the graph.`);
192188
return;
193189
}
194-
this.devices.set(idDevice, deviceInfo);
195-
deviceInfo.connections.forEach((connectedId) => {
196-
this.devices.get(connectedId)?.connections.add(idDevice);
190+
this.deviceGraph.setVertex(idDevice, deviceInfo);
191+
connections.forEach((connectedId) => {
192+
this.deviceGraph.setEdge(idDevice, connectedId);
197193
});
198194
if (this.idCounter <= idDevice) {
199195
this.idCounter = idDevice + 1;
@@ -210,25 +206,21 @@ export class DataGraph {
210206
);
211207
return;
212208
}
213-
const device1 = this.devices.get(n1Id);
214-
const device2 = this.devices.get(n2Id);
215-
if (!device1) {
216-
console.warn(`Device with ID ${n1Id} does not exist in devices.`);
209+
if (!this.deviceGraph.hasVertex(n1Id)) {
210+
console.warn(`Device with ID ${n1Id} does not exist.`);
217211
return;
218212
}
219-
if (!device2) {
220-
console.warn(`Device with ID ${n2Id} does not exist in devices.`);
213+
if (!this.deviceGraph.hasVertex(n2Id)) {
214+
console.warn(`Device with ID ${n2Id} does not exist.`);
221215
return;
222-
// Check if an edge already exists between these two devices
223216
}
224-
if (device1.connections.has(n2Id)) {
217+
if (this.deviceGraph.hasEdge(n1Id, n2Id)) {
225218
console.warn(
226219
`Connection between ID ${n1Id} and ID ${n2Id} already exists.`,
227220
);
228221
return;
229222
}
230-
device1.connections.add(n2Id);
231-
device2.connections.add(n1Id);
223+
this.deviceGraph.setEdge(n1Id, n2Id);
232224

233225
console.log(
234226
`Connection created between devices ID: ${n1Id} and ID: ${n2Id}`,
@@ -238,101 +230,60 @@ export class DataGraph {
238230
}
239231

240232
updateDevicePosition(id: DeviceId, newValues: { x?: number; y?: number }) {
241-
const deviceGraphNode = this.devices.get(id);
233+
const deviceGraphNode = this.deviceGraph.getVertex(id);
242234
if (!deviceGraphNode) {
243235
console.warn("Device's id is not registered");
244236
return;
245237
}
246-
this.devices.set(id, { ...deviceGraphNode, ...newValues });
238+
this.deviceGraph.setVertex(id, { ...deviceGraphNode, ...newValues });
247239
this.notifyChanges();
248240
}
249241

250242
// Get a device by its ID.
251243
// WARNING: don't modify the device directly, use `modifyDevice` instead
252244
getDevice(id: DeviceId): GraphNode | undefined {
253-
return this.devices.get(id);
245+
return this.deviceGraph.getVertex(id);
254246
}
255247

256248
// Modify a device in the graph, notifying subscribers of any changes
257249
modifyDevice(id: DeviceId, fn: (d: GraphNode | undefined) => void) {
258-
const device = this.devices.get(id);
250+
const device = this.deviceGraph.getVertex(id);
259251
fn(device);
260252
if (device) {
261253
this.notifyChanges();
262254
}
263255
}
264256

265-
// Get all connections of a device
266-
getConnections(id: DeviceId): DeviceId[] {
267-
const deviceInfo = this.devices.get(id);
268-
return deviceInfo.connections
269-
? Array.from(deviceInfo.connections.values())
270-
: [];
271-
}
272-
273257
// Get all devices in the graph
274-
getDevices(): Map<DeviceId, GraphNode> {
275-
return this.devices;
258+
getDevices(): IterableIterator<[DeviceId, GraphNode]> {
259+
return this.deviceGraph.getVertices();
276260
}
277261

278262
// Get the number of devices in the graph
279263
getDeviceCount(): number {
280-
return this.devices.size;
264+
return this.deviceGraph.getVertexCount();
265+
}
266+
267+
// Get all connections of a device
268+
getConnections(id: DeviceId): DeviceId[] | undefined {
269+
return this.deviceGraph.getNeighbors(id);
281270
}
282271

283272
// Method to remove a device and all its connections
284273
removeDevice(id: DeviceId): void {
285-
const device = this.devices.get(id);
286-
287-
if (!device) {
274+
if (!this.deviceGraph.hasVertex(id)) {
288275
console.warn(`Device with ID ${id} does not exist in the graph.`);
289276
return;
290277
}
291-
292-
// Remove the connection of the current node in connected devices
293-
device.connections.forEach((connectedId) => {
294-
// can be done directly by the device
295-
const connectedDevice = this.devices.get(connectedId);
296-
if (connectedDevice) {
297-
connectedDevice.connections.delete(id);
298-
} else {
299-
console.warn(`Connected device ${connectedId} does not exist`);
300-
}
301-
});
302-
303-
// Remove the node from the graph
304-
this.devices.delete(id);
278+
this.deviceGraph.removeVertex(id);
305279
console.log(`Device with ID ${id} and its connections were removed.`);
306280
this.notifyChanges();
307281
this.regenerateAllRoutingTables();
308282
}
309283

310284
// Method to remove a connection (edge) between two devices by their IDs
311285
removeConnection(n1Id: DeviceId, n2Id: DeviceId): void {
312-
const device1 = this.devices.get(n1Id);
313-
const device2 = this.devices.get(n2Id);
314-
315-
if (!device1) {
316-
console.warn(`Device with ID ${n1Id} does not exist in the graph.`);
317-
return;
318-
}
319-
320-
if (!device2) {
321-
console.warn(`Device with ID ${n2Id} does not exist in the graph.`);
322-
return;
323-
}
324-
325-
// Check if the connection exists
326-
if (!device1.connections.has(n2Id) || !device2.connections.has(n1Id)) {
327-
console.warn(
328-
`Connection between ID ${n1Id} and ID ${n2Id} does not exist.`,
329-
);
330-
return;
331-
}
332-
333-
// Remove the connection in both devices
334-
device1.connections.delete(n2Id);
335-
device2.connections.delete(n1Id);
286+
this.deviceGraph.removeEdge(n1Id, n2Id);
336287

337288
console.log(
338289
`Connection removed between devices ID: ${n1Id} and ID: ${n2Id}`,
@@ -351,46 +302,42 @@ export class DataGraph {
351302

352303
regenerateAllRoutingTables() {
353304
console.log("Regenerating all routing tables");
354-
this.devices.forEach((_, id) => this.regenerateRoutingTable(id));
305+
for (const [id] of this.deviceGraph.getVertices()) {
306+
this.regenerateRoutingTable(id);
307+
}
355308
}
356309

357-
public regenerateRoutingTableClean(id: DeviceId): RoutingTableEntry[] {
310+
regenerateRoutingTableClean(id: DeviceId): RoutingTableEntry[] {
358311
return this.generateRoutingTable(id);
359312
}
360313

361-
public regenerateRoutingTable(id: DeviceId) {
362-
const router = this.devices.get(id);
314+
regenerateRoutingTable(id: DeviceId) {
315+
const router = this.deviceGraph.getVertex(id);
363316
if (!isRouter(router)) return;
364317

365318
router.routingTable = this.generateRoutingTable(id, true);
366-
// console.log(
367-
// `Routing table regenerated for router ID ${id}:`,
368-
// router.routingTable,
369-
// );
370319
}
371320

372321
private generateRoutingTable(
373322
id: DeviceId,
374323
preserveEdits = false,
375324
): RoutingTableEntry[] {
376-
const router = this.devices.get(id);
325+
const router = this.deviceGraph.getVertex(id);
377326
if (!isRouter(router)) {
378327
return [];
379328
}
380329

381-
// console.log(
382-
// `Regenerating ${preserveEdits ? "full" : "clean"} routing table for ID ${id}`,
383-
// );
384330
const parents = new Map<DeviceId, DeviceId>();
385331
parents.set(id, id);
386332
const queue = [id];
387333

388334
while (queue.length > 0) {
389335
const currentId = queue.shift();
390-
const current = this.devices.get(currentId);
336+
const current = this.deviceGraph.getVertex(currentId);
391337
if (isHost(current)) continue;
392338

393-
current.connections.forEach((connectedId) => {
339+
const neighbors = this.deviceGraph.getNeighbors(currentId);
340+
neighbors.forEach((connectedId) => {
394341
if (!parents.has(connectedId)) {
395342
parents.set(connectedId, currentId);
396343
queue.push(connectedId);
@@ -410,7 +357,7 @@ export class DataGraph {
410357
currentId = parentId;
411358
}
412359

413-
const dst = this.devices.get(dstId);
360+
const dst = this.deviceGraph.getVertex(dstId);
414361

415362
if (isNetworkNode(dst)) {
416363
newTable.push({

0 commit comments

Comments
 (0)