Skip to content

Commit 55669e7

Browse files
authored
feat: store Host programs in datagraph (#98)
This PR stores the programs running on Hosts inside the datagraph. This makes it persistent between layer changes and sessions.
1 parent 2bee1df commit 55669e7

File tree

12 files changed

+200
-131
lines changed

12 files changed

+200
-131
lines changed

src/context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export class GlobalContext {
3333
private setNetwork(datagraph: DataGraph, layer: Layer) {
3434
this.datagraph = datagraph;
3535
this.viewport.clear();
36+
if (this.viewgraph) {
37+
this.viewgraph.destroy();
38+
}
3639
this.viewgraph = new ViewGraph(this.datagraph, this.viewport, layer);
3740
this.setIpGenerator();
3841
}

src/graphics/renderables/device_info.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Device } from "../../types/devices";
22
import { DeviceType } from "../../types/devices/device";
3-
import { CreateDevice } from "../../types/devices/utils";
43
import { RoutingTableEntry } from "../../types/graphs/datagraph";
54
import { RemoveDeviceMove } from "../../types/undo-redo";
65
import { urManager } from "../../types/viewportManager";
@@ -46,18 +45,8 @@ export class DeviceInfo extends StyledInfo {
4645
createRightBarButton(
4746
"Delete device",
4847
() => {
49-
const deviceData: CreateDevice = {
50-
id: this.device.id,
51-
type: this.device.getType(),
52-
x: this.device.x,
53-
y: this.device.y,
54-
ip: this.device.ip.toString(),
55-
mask: this.device.ipMask.toString(),
56-
};
57-
const move = new RemoveDeviceMove(
58-
deviceData,
59-
this.device.getConnections(),
60-
);
48+
const deviceData = this.device.getCreateDevice();
49+
const move = new RemoveDeviceMove(deviceData);
6150
this.device.delete();
6251
urManager.push(move);
6352
},

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
deselectElement,
55
loadFromFile,
66
saveToFile,
7+
saveToLocalStorage,
78
urManager,
89
} from "./types/viewportManager";
910
import { DataGraph } from "./types/graphs/datagraph";
@@ -116,7 +117,6 @@ async function loadAssets(otherPromises: Promise<void>[]) {
116117
undoIcon.alt = "Undo Icon";
117118
undoButton.appendChild(undoIcon);
118119

119-
console.log(undoIcon.style.filter);
120120
urManager.suscribe(() => {
121121
undoButton.disabled = !urManager.canUndo();
122122
undoIcon.style.opacity = urManager.canUndo() ? "1" : "0.5"; // Full opacity for active, reduced for inactive
@@ -208,6 +208,7 @@ async function loadAssets(otherPromises: Promise<void>[]) {
208208

209209
if (selectedLayer) {
210210
ctx.changeViewGraph(selectedLayer);
211+
saveToLocalStorage(ctx);
211212
// LeftBar is reset
212213
leftBar.setButtonsByLayer(selectedLayer);
213214
deselectElement();

src/types/devices/device.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import { Colors, ZIndexLevels } from "../../utils";
1818
import { Position } from "../common";
1919
import { DeviceInfo } from "../../graphics/renderables/device_info";
2020
import { IpAddress } from "../../packets/ip";
21-
import { DeviceId } from "../graphs/datagraph";
2221
import { DragDeviceMove, AddEdgeMove } from "../undo-redo";
22+
import { DeviceId } from "../graphs/datagraph";
2323
import { Layer } from "./layer";
24+
import { CreateDevice } from "./utils";
2425

2526
export { Layer } from "./layer";
2627

@@ -103,6 +104,12 @@ export abstract class Device extends Sprite {
103104
return Array.from(this.connections.values());
104105
}
105106

107+
/// Returns the data needed to create the device
108+
getCreateDevice(): CreateDevice {
109+
const node = this.viewgraph.getDataGraph().getDevice(this.id);
110+
return { id: this.id, node };
111+
}
112+
106113
addConnection(adyacentId: DeviceId) {
107114
this.connections.add(adyacentId);
108115
}
@@ -235,6 +242,11 @@ export abstract class Device extends Sprite {
235242
Device.connectionTarget = null;
236243
}
237244

245+
// Cleans up related resources
246+
destroy() {
247+
// do nothing
248+
}
249+
238250
// Return the device’s type.
239251
abstract getType(): DeviceType;
240252

src/types/devices/host.ts

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ import { sendPacket } from "../packet";
99
import { Ticker } from "pixi.js";
1010
import { DeviceId } from "../graphs/datagraph";
1111
import { Layer } from "./layer";
12+
import { isHost, RunningProgram } from "../graphs/datagraph";
1213

1314
const DEFAULT_ECHO_DELAY = 250; // ms
1415

16+
const ECHO_SERVER_NAME = "Echo server";
17+
18+
type ProgramTicker = (ticker: Ticker) => void;
19+
type Pid = number;
20+
1521
export class Host extends Device {
16-
currentProgram: (ticker: Ticker) => void = undefined;
22+
private runningPrograms = new Map<Pid, ProgramTicker>();
23+
private programId = 0;
1724

1825
constructor(
1926
id: DeviceId,
@@ -23,6 +30,7 @@ export class Host extends Device {
2330
mask: IpAddress,
2431
) {
2532
super(id, PcImage, viewgraph, position, ip, mask);
33+
this.loadRunningPrograms();
2634
}
2735

2836
showInfo(): void {
@@ -53,30 +61,58 @@ export class Host extends Device {
5361
);
5462
const destination = dropdownContainer.querySelector("select");
5563

64+
// TODO: extract into classes
5665
const programList: ProgramInfo[] = [
57-
{ name: "No program", start: () => this.stopProgram() },
5866
{
5967
name: "Send ICMP echo",
6068
inputs: [dropdownContainer],
6169
start: () => this.sendSingleEcho(destination.value),
6270
},
6371
{
64-
name: "Echo server",
72+
name: ECHO_SERVER_NAME,
6573
inputs: [dropdownContainer],
66-
start: () => this.startEchoServer(destination.value),
74+
start: () => this.startNewEchoServer(destination.value),
6775
},
6876
];
6977
return programList;
7078
}
7179

72-
sendSingleEcho(id: string) {
73-
this.stopProgram();
80+
private addRunningProgram(program: RunningProgram) {
81+
this.viewgraph.getDataGraph().modifyDevice(this.id, (device) => {
82+
if (!isHost(device)) {
83+
console.error("Node is not a Host");
84+
return;
85+
}
86+
device.runningPrograms.push(program);
87+
});
88+
}
89+
90+
private loadRunningPrograms() {
91+
const device = this.viewgraph.getDataGraph().getDevice(this.id);
92+
if (!isHost(device)) {
93+
console.error("Node is not a Host");
94+
return;
95+
}
96+
device.runningPrograms.forEach((program) => {
97+
if (program.name !== ECHO_SERVER_NAME) {
98+
console.error("Unknown program: ", program.name);
99+
return;
100+
}
101+
this.startEchoServer(program.inputs[0]);
102+
});
103+
}
104+
105+
private sendSingleEcho(id: string) {
74106
const dst = parseInt(id);
75107
sendPacket(this.viewgraph, "ICMP", this.id, dst);
76108
}
77109

78-
startEchoServer(id: string) {
79-
this.stopProgram();
110+
private startNewEchoServer(id: string) {
111+
this.addRunningProgram({ name: "Echo server", inputs: [id] });
112+
this.startEchoServer(id);
113+
}
114+
115+
private startEchoServer(id: string) {
80116
const dst = parseInt(id);
81117
let progress = 0;
82118
const send = (ticker: Ticker) => {
@@ -88,13 +124,29 @@ export class Host extends Device {
88124
sendPacket(this.viewgraph, "ICMP", this.id, dst);
89125
progress -= delay;
90126
};
91-
Ticker.shared.add(send, this);
92-
this.currentProgram = send;
127+
this.startProgram(send);
93128
}
94129

95-
stopProgram() {
96-
if (this.currentProgram) {
97-
Ticker.shared.remove(this.currentProgram, this);
130+
private startProgram(tick: (ticker: Ticker) => void): Pid {
131+
const pid = ++this.programId;
132+
this.runningPrograms.set(pid, tick);
133+
Ticker.shared.add(tick, this);
134+
return pid;
135+
}
136+
137+
// TODO: this is unused
138+
stopProgram(pid: Pid) {
139+
const tick = this.runningPrograms.get(pid);
140+
if (!tick) {
141+
console.error("Pid not found: ", pid);
142+
return;
98143
}
144+
Ticker.shared.remove(tick, this);
145+
this.runningPrograms.delete(pid);
146+
}
147+
148+
destroy() {
149+
this.runningPrograms.forEach((tick) => Ticker.shared.remove(tick, this));
150+
this.runningPrograms.clear();
99151
}
100152
}

src/types/devices/utils.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IpAddress } from "../../packets/ip";
2+
import { GraphNode } from "../graphs/datagraph";
23
import { DeviceId } from "../graphs/datagraph";
34
import { ViewGraph } from "../graphs/viewgraph";
45
import { Device, DeviceType } from "./device";
@@ -7,22 +8,18 @@ import { Router } from "./router";
78

89
export interface CreateDevice {
910
id: DeviceId;
10-
type: DeviceType;
11-
x: number;
12-
y: number;
13-
ip: string;
14-
mask: string;
11+
node: GraphNode;
1512
}
1613

1714
export function createDevice(
1815
deviceInfo: CreateDevice,
1916
viewgraph: ViewGraph,
2017
): Device {
21-
const position: { x: number; y: number } = deviceInfo;
22-
const ip = IpAddress.parse(deviceInfo.ip);
23-
const mask = IpAddress.parse(deviceInfo.mask);
18+
const position: { x: number; y: number } = deviceInfo.node;
19+
const ip = IpAddress.parse(deviceInfo.node.ip);
20+
const mask = IpAddress.parse(deviceInfo.node.mask);
2421

25-
switch (deviceInfo.type) {
22+
switch (deviceInfo.node.type) {
2623
case DeviceType.Router:
2724
return new Router(deviceInfo.id, viewgraph, position, ip, mask);
2825
case DeviceType.Host:

src/types/graphs/datagraph.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,28 @@ export interface RoutingTableEntry {
2222
iface: DeviceId;
2323
}
2424

25+
interface HostGraphNode extends CommonGraphNode {
26+
type: DeviceType.Host;
27+
runningPrograms: RunningProgram[];
28+
}
29+
30+
export interface RunningProgram {
31+
name: string;
32+
inputs: string[];
33+
}
34+
2535
// Typescript type guard
2636
export function isRouter(node: GraphNode): node is RouterGraphNode {
2737
return node.type === DeviceType.Router;
2838
}
2939

30-
export type GraphNode = CommonGraphNode | RouterGraphNode;
40+
export function isHost(node: GraphNode): node is HostGraphNode {
41+
return node.type === DeviceType.Host;
42+
}
43+
44+
export type GraphNode = CommonGraphNode | RouterGraphNode | HostGraphNode;
3145

32-
export type GraphDataNode = CommonDataNode | RouterDataNode;
46+
export type GraphDataNode = CommonDataNode | RouterDataNode | HostDataNode;
3347

3448
interface CommonDataNode {
3549
id: DeviceId;
@@ -46,6 +60,11 @@ interface RouterDataNode extends CommonDataNode {
4660
routingTable: RoutingTableEntry[];
4761
}
4862

63+
interface HostDataNode extends CommonDataNode {
64+
type: DeviceType.Host;
65+
runningPrograms: RunningProgram[];
66+
}
67+
4968
export type GraphData = GraphDataNode[];
5069

5170
export interface NewDevice {
@@ -92,8 +111,8 @@ export class DataGraph {
92111
};
93112
if (isRouter(info)) {
94113
graphData.push({ ...graphNode, routingTable: info.routingTable });
95-
} else {
96-
graphData.push(graphNode);
114+
} else if (isHost(info)) {
115+
graphData.push({ ...graphNode, runningPrograms: info.runningPrograms });
97116
}
98117
});
99118
return graphData;
@@ -106,6 +125,7 @@ export class DataGraph {
106125
...deviceInfo,
107126
connections: new Set<number>(),
108127
routingTable: [],
128+
runningPrograms: [],
109129
};
110130
this.devices.set(id, graphnode);
111131
console.log(`Device added with ID ${id}`);
@@ -172,10 +192,21 @@ export class DataGraph {
172192
this.notifyChanges();
173193
}
174194

195+
// Get a device by its ID.
196+
// WARNING: don't modify the device directly, use `modifyDevice` instead
175197
getDevice(id: DeviceId): GraphNode | undefined {
176198
return this.devices.get(id);
177199
}
178200

201+
// Modify a device in the graph, notifying subscribers of any changes
202+
modifyDevice(id: DeviceId, fn: (d: GraphNode | undefined) => void) {
203+
const device = this.devices.get(id);
204+
fn(device);
205+
if (device) {
206+
this.notifyChanges();
207+
}
208+
}
209+
179210
// Get all connections of a device
180211
getConnections(id: DeviceId): DeviceId[] {
181212
const deviceInfo = this.devices.get(id);
@@ -292,9 +323,8 @@ export class DataGraph {
292323
});
293324
}
294325

295-
console.log(parents);
296-
297326
const table: RoutingTableEntry[] = [];
327+
298328
parents.forEach((currentId, childId) => {
299329
const dstId = childId;
300330
if (dstId === id) {

0 commit comments

Comments
 (0)