Compare commits

..

2 commits

Author SHA1 Message Date
69f9cb6843
Sync player list and player ID with vuex
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2019-10-07 12:23:17 +02:00
6b34fe3dfa
Wait for connection and properly clean up players 2019-10-07 12:22:40 +02:00
8 changed files with 83 additions and 15 deletions

View file

@ -23,6 +23,7 @@ export abstract class Client extends EventEmitter {
case "room-info": case "room-info":
this.roomInfo = data.room; this.roomInfo = data.room;
this.players = data.players; this.players = data.players;
this.emit("handshake");
break; break;
// Someone changed name (or was forced to) // Someone changed name (or was forced to)
case "rename": case "rename":

View file

@ -34,6 +34,12 @@ function nextName(name: string): string {
return name.substr(0, i) + (Number(name.slice(i)) + 1); return name.substr(0, i) + (Number(name.slice(i)) + 1);
} }
function connectionOpen(conn: DataConnection): Promise<void> {
return new Promise(resolve => {
conn.on("open", () => resolve());
});
}
export class PeerServer extends EventEmitter { export class PeerServer extends EventEmitter {
protected peer: Peer; protected peer: Peer;
private room: Room; private room: Room;
@ -64,17 +70,21 @@ export class PeerServer extends EventEmitter {
// Setup peer // Setup peer
this.peer = customPeer ? customPeer : new Peer(); this.peer = customPeer ? customPeer : new Peer();
this.peer.on("open", function(id) { this.peer.on("open", id => {
console.info("Peer ID assigned: %s", id); console.info("Peer ID assigned: %s", id);
this.emit("open", id);
}); });
this.peer.on("connection", conn => { this.peer.on("connection", conn => {
this._connection(conn); this._connection(conn);
}); });
} }
private _connection(conn: DataConnection) { private async _connection(conn: DataConnection) {
const metadata = conn.metadata as PeerMetadata; const metadata = conn.metadata as PeerMetadata;
// Wait for connection to be open
await connectionOpen(conn);
let player: NetworkPlayer = { let player: NetworkPlayer = {
kind: "remote", kind: "remote",
name: metadata.name, name: metadata.name,
@ -140,6 +150,12 @@ export class PeerServer extends EventEmitter {
private addPlayer(player: NetworkPlayer) { private addPlayer(player: NetworkPlayer) {
const playerName = player.name; const playerName = player.name;
// Hacky: Give player list before this player was added, so join
// message doesn't mess things up later
const players = Object.keys(this.room.players);
// Add player to room
this.room.players[playerName] = player; this.room.players[playerName] = player;
// Start listening for new messages // Start listening for new messages
@ -148,6 +164,10 @@ export class PeerServer extends EventEmitter {
this._received.bind(this, this.room.players[playerName]) this._received.bind(this, this.room.players[playerName])
); );
player.conn.on("error", err => {
throw err;
});
// Send the player info about the room // Send the player info about the room
this.send<RoomInfoMessage>(player, { this.send<RoomInfoMessage>(player, {
kind: "room-info", kind: "room-info",
@ -155,7 +175,7 @@ export class PeerServer extends EventEmitter {
...this.room.info, ...this.room.info,
password: "" password: ""
}, },
players: Object.keys(this.room.players) players
}); });
// Notify other players // Notify other players
@ -172,6 +192,9 @@ export class PeerServer extends EventEmitter {
// Close connection with player // Close connection with player
player.conn.close(); player.conn.close();
// Remove player from player list
delete this.room.players[player.name];
// Notify other players // Notify other players
this.broadcast<LeaveMessage>({ this.broadcast<LeaveMessage>({
kind: "player-left", kind: "player-left",

View file

@ -1,4 +1,4 @@
import { ActionTree } from "vuex"; import { ActionTree, Commit } from "vuex";
import { AppState } from "../types"; import { AppState } from "../types";
import { NetworkState, StartServerOptions, ConnectOptions } from "./types"; import { NetworkState, StartServerOptions, ConnectOptions } from "./types";
import { import {
@ -10,10 +10,23 @@ import {
ChatMessage ChatMessage
} from "@/network"; } from "@/network";
function bindClientEvents(commit: Commit, client: Client) {
client.on("handshake", () => {
commit("playerListChanged", client.players);
});
client.on("player-joined", () => commit("playerListChanged", client.players));
client.on("player-left", () => commit("playerListChanged", client.players));
client.on("rename", () => commit("playerListChanged", client.players));
}
const actions: ActionTree<NetworkState, AppState> = { const actions: ActionTree<NetworkState, AppState> = {
startServer({ commit }, options: StartServerOptions) { startServer({ commit }, options: StartServerOptions) {
const local = new LocalClient(options.playerInfo); const local = new LocalClient(options.playerInfo);
const server = new PeerServer(options.roomInfo, local, options._customPeer); const server = new PeerServer(options.roomInfo, local, options._customPeer);
server.once("open", id => {
commit("serverAssignedID", id);
});
bindClientEvents(commit, local);
commit("becomeServer", { local, server }); commit("becomeServer", { local, server });
}, },
@ -29,6 +42,7 @@ const actions: ActionTree<NetworkState, AppState> = {
client.on("error", err => { client.on("error", err => {
commit("connectionError", err); commit("connectionError", err);
}); });
bindClientEvents(commit, client);
client.connect(options.serverID); client.connect(options.serverID);
}, },

View file

@ -15,13 +15,7 @@ const getters: GetterTree<NetworkState, AppState> = {
}, },
sessionID(state): string | null { sessionID(state): string | null {
switch (state.peerType) {
case "server":
return state.server.id;
case "client":
return state.serverID; return state.serverID;
}
return null;
}, },
client(state): Client | null { client(state): Client | null {
@ -46,6 +40,10 @@ const getters: GetterTree<NetworkState, AppState> = {
return true; return true;
} }
return false; return false;
},
players(state): string[] {
return state.players;
} }
}; };

View file

@ -15,6 +15,7 @@ export const state: NetworkState = {
server: null, server: null,
local: null, local: null,
serverID: null, serverID: null,
players: [],
chatLog: [] chatLog: []
}; };

View file

@ -32,6 +32,14 @@ const mutations: MutationTree<NetworkState> = {
receivedChatMessage(state, message: ChatMessage) { receivedChatMessage(state, message: ChatMessage) {
state.chatLog.push(message); state.chatLog.push(message);
},
serverAssignedID(state, id: string) {
state.serverID = id;
},
playerListChanged(state, players: string[]) {
state.players = players;
} }
}; };

View file

@ -16,6 +16,8 @@ export type ConnectionStatus =
export interface SharedNetworkState { export interface SharedNetworkState {
chatLog: ChatMessage[]; chatLog: ChatMessage[];
serverID: string | null;
players: string[];
} }
export interface NoNetworkState extends SharedNetworkState { export interface NoNetworkState extends SharedNetworkState {
@ -25,7 +27,6 @@ export interface NoNetworkState extends SharedNetworkState {
peer: null; peer: null;
server: null; server: null;
local: null; local: null;
serverID: null;
} }
export interface ClientNetworkState extends SharedNetworkState { export interface ClientNetworkState extends SharedNetworkState {
@ -33,7 +34,6 @@ export interface ClientNetworkState extends SharedNetworkState {
connectionStatus: ConnectionStatus; connectionStatus: ConnectionStatus;
connectionError?: Error; connectionError?: Error;
peer: PeerClient; peer: PeerClient;
serverID: string;
} }
export interface ServerNetworkState extends SharedNetworkState { export interface ServerNetworkState extends SharedNetworkState {

View file

@ -80,7 +80,15 @@
</section> </section>
<section class="room" v-else> <section class="room" v-else>
<section class="info"> <section class="info">
Session ID: {{ sessionID }} Session ID: <span class="selectable">{{ sessionID }}</span>
</section>
<section class="players">
Players:
<ul>
<li class="selectable" v-for="player in players" :key="player">
{{ player }}
</li>
</ul>
</section> </section>
</section> </section>
</section> </section>
@ -160,10 +168,21 @@
} }
} }
.room {
padding: 10px 20px;
margin: 10px;
border: 1px solid rgba($white, 20%);
border-radius: 10px;
}
.only-mobile { .only-mobile {
display: none; display: none;
} }
.selectable {
user-select: all;
}
@media (max-width: 500px) { @media (max-width: 500px) {
.only-full { .only-full {
display: none; display: none;
@ -189,6 +208,7 @@ import { Component, Vue } from "vue-property-decorator";
import TopNav from "@/components/Navigation/TopNav.vue"; import TopNav from "@/components/Navigation/TopNav.vue";
import { StartServerOptions, ConnectOptions } from "@/store/network/types"; import { StartServerOptions, ConnectOptions } from "@/store/network/types";
import { Action, Getter } from "vuex-class"; import { Action, Getter } from "vuex-class";
import { Client } from "@/network";
@Component({ @Component({
components: { components: {
@ -211,9 +231,12 @@ export default class Lobby extends Vue {
@Getter("inRoom", { namespace: "network" }) @Getter("inRoom", { namespace: "network" })
private inRoom!: boolean; private inRoom!: boolean;
@Getter("sessionID", { namespace: "network"} ) @Getter("sessionID", { namespace: "network" })
private sessionID!: string | null; private sessionID!: string | null;
@Getter("players", { namespace: "network" })
private players!: string[];
private data() { private data() {
return { return {
playerName: playerName: