完善后端联调链路与模拟器多通道支持

This commit is contained in:
2026-04-01 18:48:59 +08:00
parent 94a1f0ba78
commit a70dc8d5d0
51 changed files with 4037 additions and 197 deletions

View File

@@ -12,6 +12,8 @@ import {
type GameLaunchEnvelope,
type MapPageLaunchOptions,
} from '../../utils/gameLaunch'
import { finishSession, startSession, type BackendSessionFinishSummaryPayload } from '../../utils/backendApi'
import { loadBackendBaseUrl } from '../../utils/backendAuth'
import { loadRemoteMapConfig, type RemoteMapConfig } from '../../utils/remoteMapConfig'
import { type H5ExperienceFallbackPayload, type H5ExperienceRequest } from '../../game/experience/h5Experience'
import { type TrackColorPreset } from '../../game/presentation/trackStyleConfig'
@@ -173,6 +175,8 @@ let lastPunchHintHapticAt = 0
let currentSystemSettingsConfig: SystemSettingsConfig | undefined
let currentRemoteMapConfig: RemoteMapConfig | undefined
let systemSettingsLockLifetimeActive = false
let syncedBackendSessionStartId = ''
let syncedBackendSessionFinishId = ''
let lastCenterScaleRulerStablePatch: Pick<
MapPageData,
| 'centerScaleRulerVisible'
@@ -441,6 +445,37 @@ function hasExplicitLaunchOptions(options?: MapPageLaunchOptions | null): boolea
)
}
function getCurrentBackendSessionContext(): { sessionId: string; sessionToken: string } | null {
const business = currentGameLaunchEnvelope.business
if (!business || !business.sessionId || !business.sessionToken) {
return null
}
return {
sessionId: business.sessionId,
sessionToken: business.sessionToken,
}
}
function getBackendSessionContextFromLaunchEnvelope(envelope: GameLaunchEnvelope | null | undefined): { sessionId: string; sessionToken: string } | null {
if (!envelope || !envelope.business || !envelope.business.sessionId || !envelope.business.sessionToken) {
return null
}
return {
sessionId: envelope.business.sessionId,
sessionToken: envelope.business.sessionToken,
}
}
function getCurrentBackendBaseUrl(): string {
const app = getApp<IAppOption>()
if (app.globalData && app.globalData.backendBaseUrl) {
return app.globalData.backendBaseUrl
}
return loadBackendBaseUrl()
}
function buildSideButtonVisibility(mode: SideButtonMode) {
return {
sideButtonMode: mode,
@@ -871,6 +906,8 @@ Page({
onLoad(options: MapPageLaunchOptions) {
clearSessionRecoveryPersistTimer()
syncedBackendSessionStartId = ''
syncedBackendSessionFinishId = ''
currentGameLaunchEnvelope = resolveGameLaunchEnvelope(options)
if (!hasExplicitLaunchOptions(options)) {
const recoverySnapshot = loadSessionRecoverySnapshot()
@@ -991,6 +1028,8 @@ Page({
const nextAnimationLevel = typeof nextPatch.animationLevel === 'string'
? nextPatch.animationLevel
: this.data.animationLevel
let shouldSyncBackendSessionStart = false
let backendSessionFinishStatus: 'finished' | 'failed' | null = null
if (nextAnimationLevel === 'lite') {
clearHudFxTimer('timer')
@@ -1055,6 +1094,7 @@ Page({
nextData.showGameInfoPanel = false
nextData.showSystemSettingsPanel = false
clearGameInfoPanelSyncTimer()
backendSessionFinishStatus = nextPatch.gameSessionStatus === 'finished' ? 'finished' : 'failed'
} else if (
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
&& nextPatch.gameSessionStatus === 'idle'
@@ -1064,6 +1104,11 @@ Page({
shouldSyncRuntimeSystemSettings = true
clearSessionRecoverySnapshot()
clearSessionRecoveryPersistTimer()
} else if (
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
&& nextPatch.gameSessionStatus === 'running'
) {
shouldSyncBackendSessionStart = true
} else if (nextPatch.gameSessionStatus === 'running' || nextPatch.gameSessionStatus === 'idle') {
nextData.showResultScene = false
}
@@ -1077,6 +1122,12 @@ Page({
if (typeof nextPatch.gameSessionStatus === 'string') {
this.syncSessionRecoveryLifecycle(nextPatch.gameSessionStatus)
}
if (shouldSyncBackendSessionStart) {
this.syncBackendSessionStart()
}
if (backendSessionFinishStatus) {
this.syncBackendSessionFinish(backendSessionFinishStatus)
}
if (shouldSyncRuntimeSystemSettings) {
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
}
@@ -1088,6 +1139,12 @@ Page({
if (typeof nextPatch.gameSessionStatus === 'string') {
this.syncSessionRecoveryLifecycle(nextPatch.gameSessionStatus)
}
if (shouldSyncBackendSessionStart) {
this.syncBackendSessionStart()
}
if (backendSessionFinishStatus) {
this.syncBackendSessionFinish(backendSessionFinishStatus)
}
if (shouldSyncRuntimeSystemSettings) {
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
}
@@ -1283,6 +1340,8 @@ Page({
onUnload() {
this.persistSessionRecoverySnapshot()
clearSessionRecoveryPersistTimer()
syncedBackendSessionStartId = ''
syncedBackendSessionFinishId = ''
clearGameInfoPanelSyncTimer()
clearCenterScaleRulerSyncTimer()
clearCenterScaleRulerUpdateTimer()
@@ -1332,6 +1391,114 @@ Page({
return true
},
syncBackendSessionStart() {
const sessionContext = getCurrentBackendSessionContext()
if (!sessionContext || syncedBackendSessionStartId === sessionContext.sessionId) {
return
}
startSession({
baseUrl: getCurrentBackendBaseUrl(),
sessionId: sessionContext.sessionId,
sessionToken: sessionContext.sessionToken,
})
.then(() => {
syncedBackendSessionStartId = sessionContext.sessionId
})
.catch((error) => {
const message = error && error.message ? error.message : '未知错误'
this.setData({
statusText: `session start 上报失败: ${message}`,
})
})
},
syncBackendSessionFinish(statusOverride?: 'finished' | 'failed' | 'cancelled') {
const sessionContext = getCurrentBackendSessionContext()
if (!sessionContext || syncedBackendSessionFinishId === sessionContext.sessionId || !mapEngine) {
return
}
const finishSummary = mapEngine.getSessionFinishSummary(statusOverride)
if (!finishSummary) {
return
}
const summaryPayload: BackendSessionFinishSummaryPayload = {}
if (typeof finishSummary.finalDurationSec === 'number') {
summaryPayload.finalDurationSec = finishSummary.finalDurationSec
}
if (typeof finishSummary.finalScore === 'number') {
summaryPayload.finalScore = finishSummary.finalScore
}
if (typeof finishSummary.completedControls === 'number') {
summaryPayload.completedControls = finishSummary.completedControls
}
if (typeof finishSummary.totalControls === 'number') {
summaryPayload.totalControls = finishSummary.totalControls
}
if (typeof finishSummary.distanceMeters === 'number') {
summaryPayload.distanceMeters = finishSummary.distanceMeters
}
if (typeof finishSummary.averageSpeedKmh === 'number') {
summaryPayload.averageSpeedKmh = finishSummary.averageSpeedKmh
}
finishSession({
baseUrl: getCurrentBackendBaseUrl(),
sessionId: sessionContext.sessionId,
sessionToken: sessionContext.sessionToken,
status: finishSummary.status,
summary: summaryPayload,
})
.then(() => {
syncedBackendSessionFinishId = sessionContext.sessionId
})
.catch((error) => {
const message = error && error.message ? error.message : '未知错误'
this.setData({
statusText: `session finish 上报失败: ${message}`,
})
})
},
reportAbandonedRecoverySnapshot(snapshot: SessionRecoverySnapshot) {
const sessionContext = getBackendSessionContextFromLaunchEnvelope(snapshot.launchEnvelope)
if (!sessionContext) {
clearSessionRecoverySnapshot()
return
}
finishSession({
baseUrl: getCurrentBackendBaseUrl(),
sessionId: sessionContext.sessionId,
sessionToken: sessionContext.sessionToken,
status: 'cancelled',
summary: {},
})
.then(() => {
syncedBackendSessionFinishId = sessionContext.sessionId
clearSessionRecoverySnapshot()
wx.showToast({
title: '已放弃上次对局',
icon: 'none',
duration: 1400,
})
})
.catch((error) => {
clearSessionRecoverySnapshot()
const message = error && error.message ? error.message : '未知错误'
this.setData({
statusText: `放弃恢复已生效,后端取消上报失败: ${message}`,
})
wx.showToast({
title: '已放弃上次对局',
icon: 'none',
duration: 1400,
})
})
},
syncSessionRecoveryLifecycle(status: MapPageData['gameSessionStatus']) {
if (status === 'running') {
this.persistSessionRecoverySnapshot()
@@ -1368,7 +1535,7 @@ Page({
cancelText: '放弃',
success: (result) => {
if (!result.confirm) {
clearSessionRecoverySnapshot()
this.reportAbandonedRecoverySnapshot(snapshot)
return
}
@@ -1385,15 +1552,19 @@ Page({
return
}
this.setData({
showResultScene: false,
showDebugPanel: false,
showGameInfoPanel: false,
showSystemSettingsPanel: false,
})
this.syncSessionRecoveryLifecycle('running')
},
})
this.setData({
showResultScene: false,
showDebugPanel: false,
showGameInfoPanel: false,
showSystemSettingsPanel: false,
})
const sessionContext = getCurrentBackendSessionContext()
if (sessionContext) {
syncedBackendSessionStartId = sessionContext.sessionId
}
this.syncSessionRecoveryLifecycle('running')
},
})
},
compileCurrentRuntimeProfile(lockLifetimeActive = isSystemSettingsLockLifetimeActive()) {
@@ -1537,7 +1708,10 @@ Page({
return
}
const errorMessage = error && error.message ? error.message : '未知错误'
const rawErrorMessage = error && error.message ? error.message : '未知错误'
const errorMessage = rawErrorMessage.indexOf('404') >= 0
? `release manifest 不存在或未发布 (${configLabel})`
: rawErrorMessage
this.setData({
configStatusText: `载入失败: ${errorMessage}`,
statusText: `远程地图配置载入失败: ${errorMessage} (${INTERNAL_BUILD_VERSION})`,
@@ -1939,18 +2113,19 @@ Page({
return
}
wx.showModal({
title: '确认退出',
content: '确认强制结束当前对局并返回开始前状态?',
confirmText: '确认退出',
cancelText: '取消',
success: (result) => {
if (result.confirm && mapEngine) {
systemSettingsLockLifetimeActive = false
mapEngine.handleForceExitGame()
}
},
})
wx.showModal({
title: '确认退出',
content: '确认强制结束当前对局并返回开始前状态?',
confirmText: '确认退出',
cancelText: '取消',
success: (result) => {
if (result.confirm && mapEngine) {
this.syncBackendSessionFinish('cancelled')
systemSettingsLockLifetimeActive = false
mapEngine.handleForceExitGame()
}
},
})
},
handleSkipAction() {