Skip to content

Instantly share code, notes, and snippets.

@efortner
Created November 13, 2025 21:57
Show Gist options
  • Select an option

  • Save efortner/f273c05756e70d3ba490a00c4b67d5b5 to your computer and use it in GitHub Desktop.

Select an option

Save efortner/f273c05756e70d3ba490a00c4b67d5b5 to your computer and use it in GitHub Desktop.
Network Simulator (v1)
/*
- Network of nodes === devices
- Devices can join the network by going to an entry node and asking to join
- Devices can communicate to all other devices once within the network via message
- Devices can drop out of the network, return, then see messages that were given to them while they were gone
- Messages are raw bytes sent between devices
Non-functional requirements:
- Up to 1000 devices
type NodeId = string;
interface Node {
id: string;
primaryNetworkNode: PrimaryNode;
}
interface PrimaryNode {
connectedNodes: Node[];
messageQueues: Map<NodeId, Messages[]>;
requestToJoin: (node: Node) => Promise<boolean>;
drop: (node: Node) => Promise<boolean>;
isNetworkMember: (node: Node) => Promise<boolean>;
}
interface Message {
byteBuffer: Buffer;
}
*/
const MAX_NODES = 1000;
interface Message {
id: string;
byteBuffer: Buffer;
}
type NodeId = string;
interface NodeProps {
readonly id: NodeId;
readonly primaryNode: PrimaryNode;
readonly totalSimulatedActions: number;
}
export class Node {
private _remainingSimulatedActions: number;
constructor(private readonly props: NodeProps) {
this._remainingSimulatedActions = props.totalSimulatedActions;
}
public get id() {
return this.props.id;
}
public get primaryNode() {
return this.props.primaryNode;
}
public get remainingSimulatedActions() {
return this._remainingSimulatedActions;
}
public readonly receiveMessage = (message: Message) => {
console.info(`${this.id} received message ${message.id}`);
};
public readonly run = async (): Promise<void> => {
if (this.remainingSimulatedActions === 0) {
throw new Error(`${this.id} has no remaining actions`);
}
const allAvailableActions = [
this.sendMessageAction,
this.runJoinAction,
this.runDropAction,
];
const nextAction =
allAvailableActions[
Math.trunc(Math.random() * allAvailableActions.length)
]!;
await nextAction();
this._remainingSimulatedActions -= 1;
};
private readonly sendMessageAction = async (): Promise<void> => {
const { allNodes } = this.props.primaryNode;
const recipient =
allNodes[Math.trunc(Math.random() * MAX_NODES) % allNodes.length]!;
await this.props.primaryNode.sendMessage(this, recipient, {
id: `Message-${this.id}-To-${recipient.id}-${this.remainingSimulatedActions}`,
byteBuffer: Buffer.from(`Hello, ${recipient.id}`),
});
};
private readonly runJoinAction = async (): Promise<void> => {
await this.props.primaryNode.requestToJoin(this);
};
private readonly runDropAction = async (): Promise<void> => {
await this.props.primaryNode.drop(this);
};
}
export class PrimaryNode {
private readonly _connectedNodes: Set<NodeId> = new Set();
private readonly _messageQueues: Map<NodeId, Message[]> = new Map();
private readonly _allNodes: Node[] = [];
constructor() {}
public readonly addNodes = (nodes: Node[]): void => {
this._allNodes.push(...nodes);
};
public readonly requestToJoin = async (node: Node): Promise<boolean> => {
if (!this._connectedNodes.has(node.id)) {
this._connectedNodes.add(node.id);
console.info(`${node.id} was added to the network`);
return true;
}
console.info(`${node.id} could not be added to the network`);
return false;
};
public readonly drop = async (node: Node): Promise<boolean> => {
if (this._connectedNodes.has(node.id)) {
this._connectedNodes.delete(node.id);
console.info(`${node.id} dropped from the network`);
return true;
}
console.info(`${node.id} could not be dropped from the network`);
return false;
};
public readonly isNetworkMember = async (node: Node): Promise<boolean> => {
return this._connectedNodes.has(node.id);
};
public readonly getConnectedNodes = async (): Promise<Node[]> => {
return [...this._allNodes.filter((node) => this.isNetworkMember(node))];
};
public readonly deliverMessages = async (): Promise<void> => {
this.getConnectedNodes().then((connectedNodes) =>
connectedNodes.map((node) => {
const nodeMessageQueue = this._messageQueues.get(node.id);
if (nodeMessageQueue !== undefined) {
nodeMessageQueue.forEach((message) => node.receiveMessage(message));
this._messageQueues.set(node.id, []);
}
}),
);
};
public get allNodes() {
return [...this._allNodes];
}
public readonly sendMessage = async (
sender: Node,
recipient: Node,
message: Message,
): Promise<void> => {
if (!this._connectedNodes.has(sender.id)) {
console.info(
`${sender.id} attempted to send a message but is not part of this network`,
);
return;
}
const nodeMessageQueue = this._messageQueues.get(recipient.id);
if (nodeMessageQueue !== undefined) {
nodeMessageQueue.push(message);
} else {
this._messageQueues.set(recipient.id, [message]);
}
console.info(`${sender.id} sent ${message.id} to ${recipient.id}`);
};
}
(async () => {
const TOTAL_NODES = 10;
const MAX_NODE_ACTIONS = 10;
const primaryNode = new PrimaryNode();
const nodes: Node[] = Array.from({ length: TOTAL_NODES }).map(
(_, index) =>
new Node({
id: `Node-${index}`,
totalSimulatedActions: Math.trunc(Math.random() * MAX_NODE_ACTIONS),
primaryNode,
}),
);
primaryNode.addNodes(nodes);
const getTurnsRemaining = () =>
primaryNode.allNodes
.map((node) => node.remainingSimulatedActions)
.reduce((first, second) => {
if (first > second) {
return first;
}
return second;
});
while (getTurnsRemaining() > 0) {
await Promise.all(
primaryNode.allNodes
.filter((node) => node.remainingSimulatedActions > 0)
.map((node) => node.run()),
);
await primaryNode.deliverMessages();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment