Basic networking #9

Merged
hamcha merged 17 commits from feature/basic-networking into master 2019-09-06 12:36:11 +00:00
8 changed files with 110 additions and 79 deletions
Showing only changes of commit 942aef279d - Show all commits

52
src/network/Client.ts Normal file
View file

@ -0,0 +1,52 @@
import { PeerMetadata, NetworkMessage } from "./types";
import EventEmitter from "eventemitter3";
export class Client extends EventEmitter {
public metadata: PeerMetadata;
public players: string[];
public constructor(metadata: PeerMetadata) {
super();
this.metadata = metadata;
this.players = [];
}
protected _received(data: NetworkMessage) {
this.emit("data", data);
switch (data.kind) {
// Someone changed name (or was forced to)
case "rename":
if (data.oldname == this.metadata.name) {
// We got a name change!
this.metadata.name = data.newname;
} else {
let idx = this.players.indexOf(data.oldname);
if (idx < 0) {
// Weird
console.warn(
`Someone (${data.oldname}) changed name but wasn't on the player list`
);
this.players.push(data.newname);
break;
}
this.players[idx] = data.newname;
}
this.emit("rename", data.oldname, data.newname);
break;
// A new player joined the room (this includes us)
case "player-joined":
this.players.push(data.name);
this.emit("player-joined", data.name);
break;
default:
// For most cases, we can just use the kind as event type
this.emit(data.kind, data);
}
}
public get name(): string {
return this.metadata.name;
}
}
export default Client;

View file

@ -1,18 +1,15 @@
import { PeerMetadata, NetworkMessage } from "./types";
import EventEmitter from "eventemitter3";
import Client from "./Client";
export class LocalClient extends EventEmitter {
public metadata: PeerMetadata;
export class LocalClient extends Client {
public receiver!: (data: NetworkMessage) => void;
public constructor(metadata: PeerMetadata) {
super();
this.metadata = metadata;
super(metadata);
}
public receive(data: NetworkMessage) {
this.emit("data", data);
//TODO
this._received(data);
}
public send<T extends NetworkMessage>(data: T) {

View file

@ -1,20 +0,0 @@
import Peer, { DataConnection } from "peerjs";
import { NetworkMessage } from "./types";
import EventEmitter from "eventemitter3";
export class NetworkPeer extends EventEmitter {
protected peer: Peer;
protected constructor(peer?: Peer) {
super();
this.peer = peer ? peer : new Peer();
this.peer.on("open", function(id) {
console.info("Peer ID assigned: %s", id);
});
}
protected send<T extends NetworkMessage>(conn: DataConnection, data: T) {
conn.send(data);
}
}
export default NetworkPeer;

View file

@ -1,16 +1,17 @@
import NetworkPeer from "./NetworkPeer";
import Peer, { DataConnection } from "peerjs";
import { PeerMetadata, NetworkMessage } from "./types";
import Client from "./Client";
export class PeerClient extends NetworkPeer {
export class PeerClient extends Client {
protected peer: Peer;
private connection?: DataConnection;
private metadata: PeerMetadata;
public players: string[];
public constructor(metadata: PeerMetadata, customPeer?: Peer) {
super(customPeer);
this.metadata = metadata;
this.players = [];
super(metadata);
this.peer = customPeer ? customPeer : new Peer();
this.peer.on("open", function(id) {
console.info("Peer ID assigned: %s", id);
});
}
public connect(peerid: string) {
@ -29,42 +30,13 @@ export class PeerClient extends NetworkPeer {
});
}
private _received(data: NetworkMessage) {
this.emit("data", data);
switch (data.kind) {
// Someone changed name (or was forced to)
case "rename":
if (data.oldname == this.metadata.name) {
// We got a name change!
this.metadata.name = data.newname;
} else {
let idx = this.players.indexOf(data.oldname);
if (idx < 0) {
// Weird
console.warn(
`Someone (${data.oldname}) changed name but wasn't on the player list`
);
this.players.push(data.newname);
break;
}
this.players[idx] = data.newname;
}
this.emit("rename", data.oldname, data.newname);
break;
// A new player joined the room (this includes us)
case "player-joined":
this.players.push(data.name);
this.emit("player-joined", data.name);
break;
default:
// For most cases, we can just use the kind as event type
this.emit(data.kind, data);
}
}
public get name(): string {
return this.metadata.name;
}
protected send<T extends NetworkMessage>(conn: DataConnection, data: T) {
conn.send(data);
}
}
export default PeerClient;

View file

@ -15,7 +15,8 @@ import {
NetworkPlayer,
AckMessage
} from "./types";
import { NetworkPeer, LocalClient } from ".";
import { LocalClient } from ".";
import EventEmitter from "eventemitter3";
// Increment name, add number at the end if not present
// Examples:
@ -32,7 +33,8 @@ function nextName(name: string): string {
return name.substr(0, i) + (Number(name.slice(i)) + 1);
}
export class PeerServer extends NetworkPeer {
export class PeerServer extends EventEmitter {
protected peer: Peer;
private room: Room;
public constructor(
@ -40,21 +42,27 @@ export class PeerServer extends NetworkPeer {
local: LocalClient,
customPeer?: Peer
) {
super(customPeer);
super();
let players: Record<string, Player> = {};
// Add local player to server
players[local.metadata.name] = {
players[local.name] = {
kind: "local",
name: local.metadata.name,
name: local.name,
client: local
};
local.receiver = this._received.bind(this, players[local.metadata.name]);
local.receiver = this._received.bind(this, players[local.name]);
this.room = {
info: roomInfo,
players
};
// Setup peer
this.peer = customPeer ? customPeer : new Peer();
this.peer.on("open", function(id) {
console.info("Peer ID assigned: %s", id);
});
this.peer.on("connection", conn => {
this._connection(conn);
});
@ -177,10 +185,18 @@ export class PeerServer extends NetworkPeer {
}
}
private get playerCount(): number {
public get playerCount(): number {
return Object.keys(this.room.players).length;
}
public get players() {
return this.room.players;
}
protected send<T extends NetworkMessage>(conn: DataConnection, data: T) {
conn.send(data);
}
private broadcast<T extends NetworkMessage>(message: T) {
for (const playerName in this.room.players) {
const player = this.room.players[playerName];

View file

@ -1,5 +1,5 @@
export * from "./PeerClient";
export * from "./LocalClient";
export * from "./NetworkPeer";
export * from "./Client";
export * from "./PeerServer";
export * from "./types";

View file

@ -8,11 +8,14 @@ export class MockHelper {
createServer(
room: RoomInfo,
id: string = "test-server",
name: string = "server-client"
player?: LocalClient
) {
const serverPeer = new MockPeer(this.mocks, id, {});
const serverPlayer = new LocalClient({ name: name });
return new PeerServer(room, serverPlayer, serverPeer as Peer);
return new PeerServer(
room,
player ? player : new LocalClient({ name: "server-player" }),
serverPeer as Peer
);
}
createClient(id: string = "test-client", name: string = "client-peer") {

View file

@ -1,5 +1,5 @@
import { MockHelper } from "@/testing";
import { NetworkMessage } from "@/network";
import { NetworkMessage, LocalClient } from "@/network";
import { EventHook } from "@/testing/EventHook";
const sampleRoom = () => ({
@ -47,6 +47,17 @@ describe("network/PeerServer", () => {
await hook.expect("client2-data", 1000, messageKind("player-joined"));
await hook.expect("client1-data", 1000, messageKind("player-joined"));
});
test("Local server clients receives client events", async () => {
const mox = new MockHelper();
const hook = new EventHook();
const local = new LocalClient({ name: "server-player" });
const server = mox.createServer(sampleRoom(), "test-server", local);
const client = mox.createClient();
hook.hookEmitter(local, "player-joined", "local-joined");
client.connect("test-server");
await hook.expect("local-joined", 1000);
});
});
describe("network/PeerClient", () => {