Skip to content

Commit

Permalink
feat(functions-load-balancing): update game move logic
Browse files Browse the repository at this point in the history
  • Loading branch information
albertodigioacchino committed Feb 4, 2021
1 parent fd73bc3 commit 72da50c
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 32 deletions.
32 changes: 31 additions & 1 deletion packages/functions/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
},
"main": "lib/index.js",
"dependencies": {
"async-retry": "^1.3.1",
"firebase-admin": "^9.2.0",
"firebase-functions": "^3.11.0",
"@pipeline/common": "^0.2.0",
"express": "^4.17.1",
"lodash": "^4.17.20"
},
"devDependencies": {
"@types/async-retry": "^1.4.2",
"@types/axios": "^0.14.0",
"@types/chai": "^4.2.14",
"@types/chai-as-promised": "^7.1.3",
Expand All @@ -46,4 +48,4 @@
},
"private": true,
"version": "0.2.0"
}
}
4 changes: 4 additions & 0 deletions packages/functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'selectBestRTDBI
exports.selectBestRTDBInstance = require('./load-balancing/selectBestRTDBInstance').selectBestRTDBInstance;
}

if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'scheduledMoveGamesJob') {
exports.scheduledMoveGamesJob = require('./load-balancing/scheduledMoveGamesJob').scheduledMoveGamesJob;
}

21 changes: 17 additions & 4 deletions packages/functions/src/load-balancing/onOnlineGameStatusCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as admin from "firebase-admin";
import {FirebaseCollection, RTDBInstance, RTDBPaths} from "@pipeline/common";
import exportFunctionsOnAllRTDBInstances from "../utils/exportFunctionsOnAllRTDBInstances";
import FieldValue = admin.firestore.FieldValue;
import * as retry from "async-retry";

const db = admin.firestore();
const logger = functions.logger;
Expand All @@ -20,10 +21,22 @@ export async function handler(snapshot: functions.database.DataSnapshot, context
// const docInstanceId = parseRTDBInstanceId(snapshot.instance);

logger.log(`User ${userId} just created connection for game ${gameId} in ${rtdbId} snapshotInstance ${snapshot.instance}`);
await db.collection(FirebaseCollection.RTDBInstances).doc(rtdbId)
.update({
connectionsCount: FieldValue.increment(1) as any,
} as Partial<RTDBInstance>);

try {
await retry(async () => {
await db.collection(FirebaseCollection.RTDBInstances).doc(rtdbId)
.update({
connectionsCount: FieldValue.increment(1) as any,
} as Partial<RTDBInstance>);
}, {
retries: 3,
});
} catch (e) {
console.error('Error updating connections count');
}

await db.collection(FirebaseCollection.Games).doc(gameId).update({lastPlayerDisconnectedAt: null})
logger.log(`Game ${gameId} lastPlayerDisconnectedAt updated`);
}


Expand Down
24 changes: 17 additions & 7 deletions packages/functions/src/load-balancing/onOnlineGameStatusDelete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
import {FirebaseCollection, RTDBInstance, RTDBPaths} from "@pipeline/common";
import FieldValue = admin.firestore.FieldValue;
import {handleLockedCards, handleMoveGame} from "./utils";
import {handleLockedCards, handleUpdateLastPlayerDisconnectedAtGameField} from "./utils";
import exportFunctionsOnAllRTDBInstances from "../utils/exportFunctionsOnAllRTDBInstances";
import {getDatabase} from "../utils/rtdb";

import * as retry from "async-retry";
const db = admin.firestore();
const logger = functions.logger;

Expand All @@ -27,18 +27,28 @@ async function handler(snapshot: functions.database.DataSnapshot, context: funct
// const docInstanceId = parseRTDBInstanceId(snapshot.instance);

logger.log(`User ${userId} just closed all connections for game ${gameId} in instance ${rtdbId}`);
await db.collection(FirebaseCollection.RTDBInstances).doc(rtdbId)
.update({
connectionsCount: FieldValue.increment(-1) as any,
} as Partial<RTDBInstance>);

try {
await retry(async () => {
await db.collection(FirebaseCollection.RTDBInstances).doc(rtdbId)
.update({
connectionsCount: FieldValue.increment(-1) as any,
} as Partial<RTDBInstance>);
}, {
retries: 3,
});
} catch (e) {
console.error('Error updating connections count');
}


const rtdb = getDatabase(
rtdbUrl
);

await handleLockedCards(gameId, rtdb, userId);
logger.log('Locked cards handled');
await handleMoveGame(gameId, db, rtdb);
await handleUpdateLastPlayerDisconnectedAtGameField(gameId, db, rtdb);
logger.log('Locked cards handled');
}

Expand Down
26 changes: 18 additions & 8 deletions packages/functions/src/load-balancing/onOnlineGameStatusUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@ import * as admin from "firebase-admin";
import {FirebaseCollection, RTDBInstance, RTDBPaths} from "@pipeline/common";
import exportFunctionsOnAllRTDBInstances from "../utils/exportFunctionsOnAllRTDBInstances";
import FieldValue = admin.firestore.FieldValue;
import * as retry from "async-retry";

const db = admin.firestore();
const logger = functions.logger;


async function handleUpdateConnectionsCount(rtdbId: string, connectionsDiff: number) {
try {
await retry(async () => {
await db.collection(FirebaseCollection.RTDBInstances).doc(rtdbId)
.update({
connectionsCount: FieldValue.increment(connectionsDiff) as any,
} as Partial<RTDBInstance>);
}, {
retries: 3,
});
} catch (e) {
console.error('Error updating connections count');
}
}

/**
* It triggers when the path /connections/{gameId}/{userId} of that RTDB instance is updated.
*
Expand Down Expand Up @@ -37,17 +53,11 @@ export async function handler(snapshot: functions.Change<functions.database.Data

if (connectionsDiff < 0) {
logger.log(`User ${userId} for game ${gameId} has closed one connection instance ${rtdbId}`);
await db.collection(FirebaseCollection.RTDBInstances).doc(rtdbId)
.update({
connectionsCount: FieldValue.increment(connectionsDiff) as any,
} as Partial<RTDBInstance>);
await handleUpdateConnectionsCount(rtdbId, connectionsDiff);
}
if (connectionsDiff > 0) {
logger.log(`User ${userId} for game ${gameId} has opened one connection`);
await db.collection(FirebaseCollection.RTDBInstances).doc(rtdbId)
.update({
connectionsCount: FieldValue.increment(connectionsDiff) as any,
} as Partial<RTDBInstance>);
await handleUpdateConnectionsCount(rtdbId, connectionsDiff);
}
}

Expand Down
36 changes: 36 additions & 0 deletions packages/functions/src/load-balancing/scheduledMoveGame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
import {FirebaseCollection} from '@pipeline/common';
import {getDatabase} from "../utils/rtdb";
import {Game} from "../models/Game";
import {moveGameFromRTDBToFirestore} from "./utils";
const db = admin.firestore();
const logger = functions.logger;

const DAY_IN_MILLIS = 24 * 60 * 60 * 1000;

const moveGamesJob = async () => {
const lastActiveGamesDate = new Date(Date.now() - DAY_IN_MILLIS);

const oldActiveGamesQuery = db.collection(FirebaseCollection.Games)
.where(`lastPlayerDisconnectedAt`, '<=', lastActiveGamesDate);

const oldActiveGamesSnap = await oldActiveGamesQuery.get();
logger.log(`Found ${oldActiveGamesSnap.docs.length} old active games`);

for (const gameDoc of oldActiveGamesSnap.docs) {
const game = gameDoc.data() as Game;
await moveGameFromRTDBToFirestore(gameDoc.id, db, getDatabase(`https://${game.rtdbInstance!}.firebasedatabase.app`))
}

logger.log('moveGames job ended');
}

export const scheduledMoveGamesJob = functions.region(
'europe-west1'
).runWith({
memory: '2GB',
timeoutSeconds: 540,
}).pubsub.schedule(`every 60 minutes`).onRun(async () => {
await moveGamesJob();
});
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export const selectBestRTDBInstance = functions.region(
game.rtdbInstance = null;
await rtdb.ref(`/${RTDBPaths.Games}/${gameId}`).set({
...game,
movedAt: FieldValue.serverTimestamp(),
});
if (cards) {
await rtdb.ref(`/${RTDBPaths.Cards}/${gameId}`).set({
Expand Down
25 changes: 15 additions & 10 deletions packages/functions/src/load-balancing/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ const logger = functions.logger;
* @param rtdb
*/
const moveGameFromRTDBToFirestore = async (gameId: string, db: FirebaseFirestore.Firestore, rtdb: admin.database.Database) => {
const gameRef = rtdb.ref(`/${RTDBPaths.Games}/${gameId}`);
const gamePath = `/${RTDBPaths.Games}/${gameId}`;
const gameRef = rtdb.ref(gamePath);
const gameSnap = await gameRef.get();
const game = gameSnap.val() as RTDBGame;
const cardRef = rtdb.ref(`/${RTDBPaths.Cards}/${gameId}`);
const cardsSnap = await cardRef.get();
const cardsPath = `/${RTDBPaths.Cards}/${gameId}`;
const cardsRef = rtdb.ref(cardsPath);
const cardsSnap = await cardsRef.get();
let newCards = null;
if (cardsSnap.exists()) {
const cards = cardsSnap.val() as {[key: string]: CardState};
Expand All @@ -29,18 +31,21 @@ const moveGameFromRTDBToFirestore = async (gameId: string, db: FirebaseFirestore
return acc;
}, {} as {[key: string]: CardState});
}
await db.collection(FirebaseCollection.Games).doc(gameId).update({...game, rtdbInstance: null, cards: newCards, movedAt: FieldValue.serverTimestamp()} as Game);
await gameRef.set(null);
await cardRef.set(null);
await db.collection(FirebaseCollection.Games).doc(gameId).update({...game, rtdbInstance: null,
cards: newCards, lastPlayerDisconnectedAt: null, movedAt: FieldValue.serverTimestamp()} as Game);
await rtdb.ref().update({
[gamePath]: null,
[cardsPath]: null,
});
}

async function handleMoveGame(gameId: string, db: FirebaseFirestore.Firestore, rtdb: admin.database.Database) {
async function handleUpdateLastPlayerDisconnectedAtGameField(gameId: string, db: FirebaseFirestore.Firestore, rtdb: admin.database.Database) {
const snap = await rtdb.ref(`/${RTDBPaths.Connections}/${gameId}`).get();
const onlineCount = snap.exists() ? snap.numChildren() : 0;
logger.log(`Online user for game ${gameId}: ${onlineCount}`);
if (onlineCount === 0) {
await moveGameFromRTDBToFirestore(gameId, db, rtdb);
logger.log(`Game ${gameId} moved from RTDB to Firestore`);
await db.collection(FirebaseCollection.Games).doc(gameId).update({lastPlayerDisconnectedAt: FieldValue.serverTimestamp()})
logger.log(`Game ${gameId} lastPlayerDisconnectedAt updated`);
}

}
Expand All @@ -62,4 +67,4 @@ async function handleLockedCards(gameId: string, rtdb: admin.database.Database,
}
}

export {moveGameFromRTDBToFirestore, handleMoveGame, handleLockedCards};
export {moveGameFromRTDBToFirestore, handleUpdateLastPlayerDisconnectedAtGameField, handleLockedCards};

0 comments on commit 72da50c

Please sign in to comment.