Skip to content

Commit 2f302c2

Browse files
authored
[Feat] Add patching speed check (#1148)
* add patching speed check * run yarn * update hp patcher * run yarn * add todo, update patching speed * update hp patcher * add ld patching speeds flag * fix compare manifests call, improve error handling
1 parent 543463a commit 2f302c2

File tree

5 files changed

+147
-13
lines changed

5 files changed

+147
-13
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@
370370
"@hyperplay/extension-provider": "^0.0.8",
371371
"@hyperplay/mock-backend": "^0.0.1",
372372
"@hyperplay/overlay": "^0.0.7",
373-
"@hyperplay/patcher": "^0.0.17",
373+
"@hyperplay/patcher": "^0.0.18",
374374
"@hyperplay/providers": "^0.0.6",
375375
"@hyperplay/proxy-server": "^0.0.11"
376376
},

src/backend/metrics/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,21 @@ export interface PatchingFailed {
226226
sensitiveProperties?: never
227227
}
228228

229+
export interface PatchingTooSlow {
230+
event: 'Patching Too Slow'
231+
properties: {
232+
game_name: string
233+
game_title: string
234+
platform: ReturnType<typeof getPlatformName>
235+
platform_arch: InstallPlatform
236+
old_game_version: string
237+
new_game_version: string
238+
est_time_to_patch_sec: string
239+
est_time_to_install_sec: string
240+
}
241+
sensitiveProperties?: never
242+
}
243+
229244
export interface GameUninstallRequested {
230245
event: 'Game Uninstall Requested'
231246
properties: {
@@ -442,5 +457,6 @@ export type PossibleMetricPayloads =
442457
| PatchingStarted
443458
| PatchingSuccess
444459
| PatchingFailed
460+
| PatchingTooSlow
445461

446462
export type PossibleMetricEventNames = PossibleMetricPayloads['event']

src/backend/storeManagers/hyperplay/games.ts

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ import { trackEvent } from 'backend/metrics/metrics'
100100
import { getFlag } from 'backend/flags/flags'
101101
import { ipfsGateway } from 'backend/vite_constants'
102102
import { GlobalConfig } from 'backend/config'
103+
import { PatchingError } from './types'
103104

104105
interface ProgressDownloadingItem {
105106
DownloadItem: DownloadItem
@@ -1653,6 +1654,93 @@ export async function downloadGameIpdtManifest(
16531654
}
16541655
}
16551656

1657+
async function checkIfPatchingIsFaster(
1658+
oldManifestPath: string,
1659+
newManifestPath: string,
1660+
gameInfo: GameInfo
1661+
) {
1662+
// read manifests
1663+
const oldManifestJson = JSON.parse(readFileSync(oldManifestPath).toString())
1664+
const newManifestJson = JSON.parse(readFileSync(newManifestPath).toString())
1665+
1666+
// compare manifests
1667+
1668+
const { compareManifests } = await import('@hyperplay/patcher')
1669+
const { estimatedPatchSizeInKB } = compareManifests(
1670+
oldManifestJson.files,
1671+
newManifestJson.files
1672+
)
1673+
1674+
// calc break point % where patching is faster
1675+
if (
1676+
gameInfo?.install?.platform &&
1677+
gameInfo.channels &&
1678+
gameInfo?.install?.channelName &&
1679+
Object.hasOwn(gameInfo.channels, gameInfo.install.channelName)
1680+
) {
1681+
const channelName = gameInfo.install.channelName
1682+
const [releaseMeta] = getReleaseMeta(gameInfo, channelName)
1683+
const platform = handleArchAndPlatform(
1684+
gameInfo.install.platform,
1685+
releaseMeta
1686+
)
1687+
const downloadSize = parseInt(
1688+
releaseMeta.platforms[platform]?.downloadSize ?? '0'
1689+
)
1690+
const installSize = parseInt(
1691+
releaseMeta.platforms[platform]?.installSize ?? '0'
1692+
)
1693+
// @TODO: get these speed values from local checks of download/write speed
1694+
const patchingSpeeds = getFlag('patching-speeds', {
1695+
downloadSpeedInKBPerSecond: 25600,
1696+
extractionSpeedInKBPerSecond: 51200,
1697+
patchingSpeedEstimateInKBPerSecond: 5120
1698+
}) as {
1699+
downloadSpeedInKBPerSecond: number
1700+
extractionSpeedInKBPerSecond: number
1701+
patchingSpeedEstimateInKBPerSecond: number
1702+
}
1703+
const downloadSpeedInKBPerSecond = patchingSpeeds.downloadSpeedInKBPerSecond
1704+
const extractionSpeedInKBPerSecond =
1705+
patchingSpeeds.extractionSpeedInKBPerSecond
1706+
const estTimeToInstallFullGameInSec =
1707+
(downloadSize / 1024) * downloadSpeedInKBPerSecond +
1708+
(installSize / 1024) * extractionSpeedInKBPerSecond
1709+
1710+
// @TODO: get this value from local check of patching speed
1711+
const patchingSpeedEstimateInKBPerSecond =
1712+
patchingSpeeds.patchingSpeedEstimateInKBPerSecond
1713+
const estTimeToPatchGameInSec =
1714+
estimatedPatchSizeInKB / patchingSpeedEstimateInKBPerSecond
1715+
1716+
if (estTimeToPatchGameInSec > estTimeToInstallFullGameInSec) {
1717+
const abortMessage = `Downloading full game instead of patching. \n
1718+
Estimated time to install full game: ${estTimeToInstallFullGameInSec} seconds. \n
1719+
Estimated time to patch: ${estTimeToPatchGameInSec}
1720+
`
1721+
logInfo(abortMessage, LogPrefix.HyperPlay)
1722+
const patchingError = new PatchingError(
1723+
abortMessage,
1724+
'slower-than-install',
1725+
{
1726+
event: 'Patching Too Slow',
1727+
properties: {
1728+
game_name: gameInfo.app_name,
1729+
game_title: gameInfo.title,
1730+
platform: getPlatformName(platform),
1731+
platform_arch: platform,
1732+
est_time_to_install_sec: estTimeToInstallFullGameInSec.toString(),
1733+
est_time_to_patch_sec: estTimeToPatchGameInSec.toString(),
1734+
old_game_version: gameInfo.install.version ?? 'unknown',
1735+
new_game_version: gameInfo.version ?? 'unknown'
1736+
}
1737+
}
1738+
)
1739+
throw patchingError
1740+
}
1741+
}
1742+
}
1743+
16561744
async function applyPatching(
16571745
gameInfo: GameInfo,
16581746
newVersion: string,
@@ -1717,6 +1805,9 @@ async function applyPatching(
17171805
const previousManifest = await getManifest(appName, platform, version)
17181806
const currentManifest = await getManifest(appName, platform, newVersion)
17191807

1808+
// check if it is faster to patch or install and throw if install is faster
1809+
await checkIfPatchingIsFaster(previousManifest, currentManifest, gameInfo)
1810+
17201811
logInfo(
17211812
`Patching ${gameInfo.title} from ${version} to ${newVersion}`,
17221813
LogPrefix.HyperPlay
@@ -1859,6 +1950,16 @@ async function applyPatching(
18591950

18601951
return { status: 'done' }
18611952
} catch (error) {
1953+
if (error instanceof PatchingError) {
1954+
if (error.reason === 'slower-than-install') {
1955+
if (error.eventToTrack) {
1956+
trackEvent(error.eventToTrack)
1957+
}
1958+
// this will not track any error events or call captureException in the calling code. it will try to install
1959+
return { status: 'error' }
1960+
}
1961+
}
1962+
18621963
logError(`Error while patching ${error}`, LogPrefix.HyperPlay)
18631964

18641965
trackEvent({
@@ -1883,7 +1984,12 @@ async function applyPatching(
18831984
newVersion
18841985
}
18851986
})
1886-
rmSync(datastoreDir, { recursive: true })
1987+
1988+
// errors can be thrown before datastore dir created. rmSync on nonexistent dir blocks indefinitely
1989+
if (existsSync(datastoreDir)) {
1990+
rmSync(datastoreDir, { recursive: true })
1991+
}
1992+
18871993
return { status: 'error', error: `Error while patching ${error}` }
18881994
}
18891995
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { PossibleMetricPayloads } from 'backend/metrics/types'
2+
3+
export type PatchingErrorReason = 'slower-than-install'
4+
5+
export class PatchingError extends Error {
6+
reason: PatchingErrorReason
7+
eventToTrack?: PossibleMetricPayloads
8+
9+
constructor(
10+
message: string,
11+
reason: PatchingErrorReason,
12+
eventToTrack?: PossibleMetricPayloads
13+
) {
14+
super(message) // Pass the message to the base Error class
15+
this.reason = reason
16+
this.eventToTrack = eventToTrack
17+
this.name = 'PatchingError' // Set a custom error name
18+
}
19+
}

yarn.lock

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,10 +1293,10 @@
12931293
"@hyperplay/utils" "^0.3.3"
12941294
mobx "^6.12.3"
12951295

1296-
"@hyperplay/patcher@^0.0.17":
1297-
version "0.0.17"
1298-
resolved "https://registry.yarnpkg.com/@hyperplay/patcher/-/patcher-0.0.17.tgz#0b62dffbb622712c592d271e49b34c70448489c8"
1299-
integrity sha512-29DWtEGlCgxw22u0/k5QtZhSLn/MmHdXCsilCe9w/cbaWguT69oYSSE/E2omhaPIntF3w2/Lt0pxoSpY1Qly1g==
1296+
"@hyperplay/patcher@^0.0.18":
1297+
version "0.0.18"
1298+
resolved "https://registry.yarnpkg.com/@hyperplay/patcher/-/patcher-0.0.18.tgz#a0db981fede4d3a72be293fdefb24c454da54fe7"
1299+
integrity sha512-aCt29RsEuODjk5OA2/Lh9cAKIdfbivwlWcfOIUdF4susB0mpiRQVUVLdBzpSkXsiJ0jDId989jUdEfO1+wh3uw==
13001300
dependencies:
13011301
"@valist/sdk" "^2.10.5"
13021302
ethers "^6.13.4"
@@ -1344,13 +1344,6 @@
13441344
dependencies:
13451345
bignumber.js "^9.1.2"
13461346

1347-
"@hyperplay/utils@^0.3.3":
1348-
version "0.3.3"
1349-
resolved "https://registry.yarnpkg.com/@hyperplay/utils/-/utils-0.3.3.tgz#9d594a29012ae5a172138878208d6d4edb6412f2"
1350-
integrity sha512-t8XB7oFWJU4S1ULxVBURt9BhpgaZAq5C131Ch5wShxuMTjxxGEhTAAcppsH+VYd7lwtp4se9myzhc6u21rUNgQ==
1351-
dependencies:
1352-
bignumber.js "^9.1.2"
1353-
13541347
"@hyperplay/utils@^0.3.4":
13551348
version "0.3.4"
13561349
resolved "https://registry.yarnpkg.com/@hyperplay/utils/-/utils-0.3.4.tgz#71d4abe910ec8550c865418118eb1c07df3b399d"

0 commit comments

Comments
 (0)