Skip to content

Commit

Permalink
Merge patch commit
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Apr 30, 2024
2 parents 970726b + f262b49 commit e1b65a4
Show file tree
Hide file tree
Showing 16 changed files with 83 additions and 194 deletions.
2 changes: 0 additions & 2 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@ BROWSER=none
REACT_APP_API_ROOT=https://farmhand.vercel.app/
REACT_APP_NAME=$npm_package_name
REACT_APP_VERSION=$npm_package_version

REACT_APP_ENABLE_KEGS=true
1 change: 0 additions & 1 deletion .env.development.local
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
REACT_APP_API_ROOT=http://localhost:3001/
REACT_APP_ENABLE_KEGS=true
REACT_APP_ENABLE_FOREST=true

# Silence warnings from dev server
Expand Down
2 changes: 0 additions & 2 deletions api-etc/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,3 @@ export const ACCEPTED_ORIGINS = new Set([
'https://www.farmhand.life',
'https://v6p9d9t4.ssl.hwcdn.net', // itch.io's CDN that the game is served from
])

export const MAX_ROOM_SIZE = 25
43 changes: 4 additions & 39 deletions api/get-market-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ const { promisify } = require('util')

require('redis')
require('../src/common/utils')
const { SERVER_ERRORS } = require('../src/common/constants')
const { MAX_ROOM_SIZE } = require('../api-etc/constants')
// End explicit requires for serverless builds

const {
Expand All @@ -15,55 +13,22 @@ const {
getRoomData,
getRoomName,
} = require('../api-etc/utils')
const { HEARTBEAT_INTERVAL_PERIOD } = require('../src/common/constants')

const client = getRedisClient()

const get = promisify(client.get).bind(client)
const set = promisify(client.set).bind(client)

module.exports = allowCors(async (req, res) => {
const { farmId = null } = req.query
const roomKey = getRoomName(req)

const roomData = await getRoomData(roomKey, get, set)
const { activePlayers, valueAdjustments } = roomData
const { valueAdjustments } = roomData

const now = Date.now()

if (farmId) {
activePlayers[farmId] = now
}

let numberOfActivePlayers = 0

const activePlayerIds = Object.keys(activePlayers)

// Multiply HEARTBEAT_INTERVAL_PERIOD by some amount to account for network
// latency and other transient heartbeat delays
const evictionTimeout = HEARTBEAT_INTERVAL_PERIOD * 2.5

// Clean up stale activePlayers data
activePlayerIds.forEach(activePlayerId => {
const timestamp = activePlayers[activePlayerId]
const delta = now - timestamp

if (delta > evictionTimeout) {
delete activePlayers[activePlayerId]
} else {
numberOfActivePlayers++
}
})

// Note: Eviction logic (above) must happen before potentially bailing out
// here to ensure that rooms can drain appropriately.
if (farmId && numberOfActivePlayers > MAX_ROOM_SIZE) {
return res.status(403).json({ errorCode: SERVER_ERRORS.ROOM_FULL })
}

set(roomKey, JSON.stringify({ ...roomData, activePlayers }))
set(roomKey, JSON.stringify(roomData))

res
.status(200)
.json({ activePlayers: numberOfActivePlayers, valueAdjustments })
// TODO: activePlayers: 1 is for legacy backwards compatibility. Remove it after 10/1/2024.
.json({ valueAdjustments, activePlayers: 1 })
})
20 changes: 10 additions & 10 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jeremyckahn/farmhand",
"version": "1.18.11",
"version": "1.18.12",
"publishConfig": {
"access": "public"
},
Expand Down
6 changes: 0 additions & 6 deletions src/common/constants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
export const MAX_ROOM_NAME_LENGTH = 25

export const HEARTBEAT_INTERVAL_PERIOD = 10 * 1000 // 10 seconds

export const SERVER_ERRORS = {
ROOM_FULL: 'ROOM_FULL',
}
144 changes: 36 additions & 108 deletions src/components/Farmhand/Farmhand.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,19 @@ import {
import {
COW_TRADE_TIMEOUT,
DEFAULT_ROOM,
HEARTBEAT_INTERVAL_PERIOD,
INITIAL_STORAGE_LIMIT,
STAGE_TITLE_MAP,
STANDARD_LOAN_AMOUNT,
Z_INDEX,
STANDARD_VIEW_LIST,
} from '../../constants'
import {
HEARTBEAT_INTERVAL_PERIOD,
SERVER_ERRORS,
} from '../../common/constants'
import {
CONNECTED_TO_ROOM,
LOAN_INCREASED,
POSITIONS_POSTED_NOTIFICATION,
RECIPE_LEARNED,
RECIPES_LEARNED,
ROOM_FULL_NOTIFICATION,
} from '../../templates'
import {
CONNECTING_TO_SERVER,
Expand Down Expand Up @@ -638,7 +634,7 @@ export default class Farmhand extends FarmhandReducers {
await this.initializeNewGame()
}

this.syncToRoom().catch(errorCode => this.handleRoomSyncError(errorCode))
this.syncToRoom()

this.setState({ hasBooted: true })
}
Expand Down Expand Up @@ -672,6 +668,7 @@ export default class Farmhand extends FarmhandReducers {

const decodedRoom = decodeURIComponent(newRoom)

// NOTE: This indicates that the client should attempt to connect to the server
const newIsOnline = path.startsWith('/online')

if (newIsOnline !== this.state.isOnline || decodedRoom !== room) {
Expand All @@ -683,7 +680,9 @@ export default class Farmhand extends FarmhandReducers {
}

if (isOnline !== prevState.isOnline || room !== prevState.room) {
this.syncToRoom().catch(errorCode => this.handleRoomSyncError(errorCode))
if (newIsOnline) {
this.syncToRoom()
}

if (!isOnline && typeof heartbeatTimeoutId === 'number') {
clearTimeout(heartbeatTimeoutId)
Expand Down Expand Up @@ -914,25 +913,17 @@ export default class Farmhand extends FarmhandReducers {

this.state.peerRoom?.leave()

const { activePlayers, errorCode, valueAdjustments } = await getData(
endpoints.getMarketData,
{
farmId: this.state.id,
room: room,
}
)

if (errorCode) {
// Bail out and move control to this try's catch
throw new Error(errorCode)
}
const { valueAdjustments } = await getData(endpoints.getMarketData, {
farmId: this.state.id,
room: room,
})

this.scheduleHeartbeat()

const trackerRedundancy = 4

this.setState({
activePlayers,
activePlayers: 1,
peerRoom: joinRoom(
{
appId: process.env.REACT_APP_NAME,
Expand All @@ -951,20 +942,21 @@ export default class Farmhand extends FarmhandReducers {

this.showNotification(CONNECTED_TO_ROOM`${room}`, 'success')
} catch (e) {
const message = e instanceof Error ? e.message : 'Unexpected error'

// TODO: Add some reasonable fallback behavior in case the server request
// fails. Possibility: Regenerate valueAdjustments and notify the user
// they are offline.

if (SERVER_ERRORS[message]) {
// Bubble up the errorCode to be handled by game logic
throw message
}

this.showNotification(SERVER_ERROR, 'error')

console.error(e)

// NOTE: Syncing failed, so take the user offline
this.setState(() => {
return {
redirect: '/',
cowIdOfferedForTrade: '',
}
})
}

this.setState({
Expand All @@ -973,87 +965,16 @@ export default class Farmhand extends FarmhandReducers {
})
}

handleRoomSyncError(errorCode) {
const { room } = this.state

switch (errorCode) {
case SERVER_ERRORS.ROOM_FULL:
const roomNameChunks = room.split('-')
const roomNumber = parseInt(roomNameChunks.slice(-1)[0]) // May be NaN
const nextRoomNumber = isNaN(roomNumber) ? 2 : roomNumber + 1
const roomBaseName = roomNameChunks
.slice(0, isNaN(roomNumber) ? undefined : -1)
.join('-')
const nextRoom = `${roomBaseName}-${nextRoomNumber}`

this.showNotification(
ROOM_FULL_NOTIFICATION`${room}${nextRoom}`,
'warning'
)

this.setState(() => ({
redirect: `/online/${encodeURIComponent(nextRoom)}`,
}))

break

default:
}
}

scheduleHeartbeat() {
const { heartbeatTimeoutId, id, room } = this.state
const { heartbeatTimeoutId } = this.state
clearTimeout(heartbeatTimeoutId ?? -1)

this.setState(() => ({
heartbeatTimeoutId: setTimeout(async () => {
try {
const { activePlayers, errorCode } = await getData(
endpoints.getMarketData,
{
farmId: id,
room,
}
)

if (errorCode) {
// Bail out and move control to this try's catch
throw new Error(errorCode)
}

// If the player has been previously disconnected due to network
// flakiness (see the catch block below), attempt to rejoin the peer
// room.
const peerRoom =
this.state.peerRoom ||
joinRoom(
{
appId: process.env.REACT_APP_NAME,
trackerUrls,
rtcConfig,
},
room
)

this.setState(({ money }) => ({
activePlayers,
money: moneyTotal(money, activePlayers),
peerRoom,
}))
} catch (e) {
const message = e instanceof Error ? e.message : 'Unexpected error'

if (SERVER_ERRORS[message]) {
// Bubble up the errorCode to be handled by game logic
throw message
}

this.showNotification(SERVER_ERROR, 'error')

this.setState({ peerRoom: null })

console.error(e)
}
this.setState(({ money, activePlayers }) => ({
activePlayers,
money: moneyTotal(money, activePlayers),
}))

this.scheduleHeartbeat()
}, HEARTBEAT_INTERVAL_PERIOD),
Expand Down Expand Up @@ -1132,6 +1053,11 @@ export default class Farmhand extends FarmhandReducers {
inventory
)

const { valueAdjustments } = await postData(endpoints.postDayResults, {
positions,
room,
})

if (Object.keys(positions).length) {
serverMessages.push({
message: POSITIONS_POSTED_NOTIFICATION`${'You'}${positions}`,
Expand All @@ -1141,22 +1067,24 @@ export default class Farmhand extends FarmhandReducers {
broadcastedPositionMessage = POSITIONS_POSTED_NOTIFICATION`${''}${positions}`
}

const { valueAdjustments } = await postData(endpoints.postDayResults, {
positions,
room,
})

nextDayState.valueAdjustments = applyPriceEvents(
valueAdjustments,
nextDayState.priceCrashes,
nextDayState.priceSurges
)
} catch (e) {
// NOTE: This will get reached when there's an issue posting data to the server.
serverMessages.push({
message: SERVER_ERROR,
severity: 'error',
})

// NOTE: Takes the user offline
this.setState({
redirect: '/',
cowIdOfferedForTrade: '',
})

console.error(e)
}

Expand Down
Loading

0 comments on commit e1b65a4

Please sign in to comment.