Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Add patching speed check #1148

Merged
merged 12 commits into from
Nov 16, 2024
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@
"@hyperplay/extension-provider": "^0.0.8",
"@hyperplay/mock-backend": "^0.0.1",
"@hyperplay/overlay": "^0.0.7",
"@hyperplay/patcher": "^0.0.17",
"@hyperplay/patcher": "^0.0.18",
"@hyperplay/providers": "^0.0.6",
"@hyperplay/proxy-server": "^0.0.11"
},
Expand Down
16 changes: 16 additions & 0 deletions src/backend/metrics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,21 @@ export interface PatchingFailed {
sensitiveProperties?: never
}

export interface PatchingTooSlow {
event: 'Patching Too Slow'
properties: {
game_name: string
game_title: string
platform: ReturnType<typeof getPlatformName>
platform_arch: InstallPlatform
old_game_version: string
new_game_version: string
est_time_to_patch_sec: string
est_time_to_install_sec: string
}
sensitiveProperties?: never
}

export interface GameUninstallRequested {
event: 'Game Uninstall Requested'
properties: {
Expand Down Expand Up @@ -442,5 +457,6 @@ export type PossibleMetricPayloads =
| PatchingStarted
| PatchingSuccess
| PatchingFailed
| PatchingTooSlow

export type PossibleMetricEventNames = PossibleMetricPayloads['event']
108 changes: 107 additions & 1 deletion src/backend/storeManagers/hyperplay/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import { trackEvent } from 'backend/metrics/metrics'
import { getFlag } from 'backend/flags/flags'
import { ipfsGateway } from 'backend/vite_constants'
import { GlobalConfig } from 'backend/config'
import { PatchingError } from './types'

interface ProgressDownloadingItem {
DownloadItem: DownloadItem
Expand Down Expand Up @@ -1653,6 +1654,93 @@ export async function downloadGameIpdtManifest(
}
}

async function checkIfPatchingIsFaster(
oldManifestPath: string,
newManifestPath: string,
gameInfo: GameInfo
) {
// read manifests
const oldManifestJson = JSON.parse(readFileSync(oldManifestPath).toString())
const newManifestJson = JSON.parse(readFileSync(newManifestPath).toString())

// compare manifests

const { compareManifests } = await import('@hyperplay/patcher')
const { estimatedPatchSizeInKB } = compareManifests(
oldManifestJson.files,
newManifestJson.files
)

// calc break point % where patching is faster
if (
gameInfo?.install?.platform &&
gameInfo.channels &&
gameInfo?.install?.channelName &&
Object.hasOwn(gameInfo.channels, gameInfo.install.channelName)
) {
const channelName = gameInfo.install.channelName
const [releaseMeta] = getReleaseMeta(gameInfo, channelName)
const platform = handleArchAndPlatform(
gameInfo.install.platform,
releaseMeta
)
const downloadSize = parseInt(
releaseMeta.platforms[platform]?.downloadSize ?? '0'
)
const installSize = parseInt(
releaseMeta.platforms[platform]?.installSize ?? '0'
)
// @TODO: get these speed values from local checks of download/write speed
const patchingSpeeds = getFlag('patching-speeds', {
downloadSpeedInKBPerSecond: 25600,
extractionSpeedInKBPerSecond: 51200,
patchingSpeedEstimateInKBPerSecond: 5120
}) as {
downloadSpeedInKBPerSecond: number
extractionSpeedInKBPerSecond: number
patchingSpeedEstimateInKBPerSecond: number
}
const downloadSpeedInKBPerSecond = patchingSpeeds.downloadSpeedInKBPerSecond
const extractionSpeedInKBPerSecond =
patchingSpeeds.extractionSpeedInKBPerSecond
const estTimeToInstallFullGameInSec =
(downloadSize / 1024) * downloadSpeedInKBPerSecond +
(installSize / 1024) * extractionSpeedInKBPerSecond

// @TODO: get this value from local check of patching speed
const patchingSpeedEstimateInKBPerSecond =
patchingSpeeds.patchingSpeedEstimateInKBPerSecond
const estTimeToPatchGameInSec =
estimatedPatchSizeInKB / patchingSpeedEstimateInKBPerSecond

if (estTimeToPatchGameInSec > estTimeToInstallFullGameInSec) {
const abortMessage = `Downloading full game instead of patching. \n
Estimated time to install full game: ${estTimeToInstallFullGameInSec} seconds. \n
Estimated time to patch: ${estTimeToPatchGameInSec}
`
logInfo(abortMessage, LogPrefix.HyperPlay)
const patchingError = new PatchingError(
abortMessage,
'slower-than-install',
{
event: 'Patching Too Slow',
properties: {
game_name: gameInfo.app_name,
game_title: gameInfo.title,
platform: getPlatformName(platform),
platform_arch: platform,
est_time_to_install_sec: estTimeToInstallFullGameInSec.toString(),
est_time_to_patch_sec: estTimeToPatchGameInSec.toString(),
old_game_version: gameInfo.install.version ?? 'unknown',
new_game_version: gameInfo.version ?? 'unknown'
}
}
)
throw patchingError
}
}
}

async function applyPatching(
gameInfo: GameInfo,
newVersion: string,
Expand Down Expand Up @@ -1717,6 +1805,9 @@ async function applyPatching(
const previousManifest = await getManifest(appName, platform, version)
const currentManifest = await getManifest(appName, platform, newVersion)

// check if it is faster to patch or install and throw if install is faster
await checkIfPatchingIsFaster(previousManifest, currentManifest, gameInfo)

logInfo(
`Patching ${gameInfo.title} from ${version} to ${newVersion}`,
LogPrefix.HyperPlay
Expand Down Expand Up @@ -1859,6 +1950,16 @@ async function applyPatching(

return { status: 'done' }
} catch (error) {
if (error instanceof PatchingError) {
if (error.reason === 'slower-than-install') {
if (error.eventToTrack) {
trackEvent(error.eventToTrack)
}
// this will not track any error events or call captureException in the calling code. it will try to install
return { status: 'error' }
}
}

logError(`Error while patching ${error}`, LogPrefix.HyperPlay)

trackEvent({
Expand All @@ -1883,7 +1984,12 @@ async function applyPatching(
newVersion
}
})
rmSync(datastoreDir, { recursive: true })

// errors can be thrown before datastore dir created. rmSync on nonexistent dir blocks indefinitely
if (existsSync(datastoreDir)) {
rmSync(datastoreDir, { recursive: true })
}

return { status: 'error', error: `Error while patching ${error}` }
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/backend/storeManagers/hyperplay/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PossibleMetricPayloads } from 'backend/metrics/types'

export type PatchingErrorReason = 'slower-than-install'

export class PatchingError extends Error {
reason: PatchingErrorReason
eventToTrack?: PossibleMetricPayloads

constructor(
message: string,
reason: PatchingErrorReason,
eventToTrack?: PossibleMetricPayloads
) {
super(message) // Pass the message to the base Error class
this.reason = reason
this.eventToTrack = eventToTrack
this.name = 'PatchingError' // Set a custom error name
}
}
15 changes: 4 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1293,10 +1293,10 @@
"@hyperplay/utils" "^0.3.3"
mobx "^6.12.3"

"@hyperplay/patcher@^0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@hyperplay/patcher/-/patcher-0.0.17.tgz#0b62dffbb622712c592d271e49b34c70448489c8"
integrity sha512-29DWtEGlCgxw22u0/k5QtZhSLn/MmHdXCsilCe9w/cbaWguT69oYSSE/E2omhaPIntF3w2/Lt0pxoSpY1Qly1g==
"@hyperplay/patcher@^0.0.18":
version "0.0.18"
resolved "https://registry.yarnpkg.com/@hyperplay/patcher/-/patcher-0.0.18.tgz#a0db981fede4d3a72be293fdefb24c454da54fe7"
integrity sha512-aCt29RsEuODjk5OA2/Lh9cAKIdfbivwlWcfOIUdF4susB0mpiRQVUVLdBzpSkXsiJ0jDId989jUdEfO1+wh3uw==
dependencies:
"@valist/sdk" "^2.10.5"
ethers "^6.13.4"
Expand Down Expand Up @@ -1344,13 +1344,6 @@
dependencies:
bignumber.js "^9.1.2"

"@hyperplay/utils@^0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@hyperplay/utils/-/utils-0.3.3.tgz#9d594a29012ae5a172138878208d6d4edb6412f2"
integrity sha512-t8XB7oFWJU4S1ULxVBURt9BhpgaZAq5C131Ch5wShxuMTjxxGEhTAAcppsH+VYd7lwtp4se9myzhc6u21rUNgQ==
dependencies:
bignumber.js "^9.1.2"

"@hyperplay/utils@^0.3.4":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@hyperplay/utils/-/utils-0.3.4.tgz#71d4abe910ec8550c865418118eb1c07df3b399d"
Expand Down