Skip to content

Commit

Permalink
Fixes a client-side bug with users' avatars missing resources.
Browse files Browse the repository at this point in the history
Sometimes, a user-patched message is getting sent to the client
where the nested avatar object does not have modelResource or
thumbnailResource. This was causing errors due to assuming
modelResource would always exist.

Workaround is to not assume that, and to attempt to fetch the
avatar manually if it is missing modelResource. It will then
spawn the avatar from this, and only if this failed to get the
necessary information is an error thrown about the avatar
missing its model.
  • Loading branch information
barankyle committed May 4, 2023
1 parent 1bab790 commit 77831b8
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 14 deletions.
28 changes: 21 additions & 7 deletions packages/client-core/src/components/World/EngineHooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { NotificationService } from '../../common/services/NotificationService'
import { useRouter } from '../../common/services/RouterService'
import { useLocationState } from '../../social/services/LocationService'
import { SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientFunctions'
import { AvatarService } from '../../user/services/AvatarService'
import { startClientSystems } from '../../world/startClientSystems'

const logger = multiLogger.child({ component: 'client-core:world' })
Expand All @@ -57,6 +58,24 @@ export const useLoadEngine = () => {
}, [])
}

const fetchMissingAvatar = async (user, avatarSpawnPose) => {
const avatar = await AvatarService.getAvatar(user.avatar.id.value)
if (avatar && (avatar.modelResource?.LOD0_url || (avatar.modelResource as any)?.src))
spawnLocalAvatarInWorld({
avatarSpawnPose,
avatarDetail: {
avatarURL: avatar.modelResource?.LOD0_url || (avatar.modelResource as any)?.src,
thumbnailURL: avatar.thumbnailResource?.LOD0_url || (avatar.thumbnailResource as any)?.src
},
name: user.name.value
})
else
NotificationService.dispatchNotify(
'Your avatar is missing its model. Please change your avatar from the user menu.',
{ variant: 'error' }
)
}

export const useLocationSpawnAvatar = (spectate = false) => {
const sceneLoaded = useHookstate(getMutableState(EngineState).sceneLoaded)
const authState = useAuthState()
Expand Down Expand Up @@ -89,7 +108,7 @@ export const useLocationSpawnAvatar = (spectate = false) => {
? getSpawnPoint(spawnPoint, Engine.instance.userId)
: getRandomSpawnPoint(Engine.instance.userId)

if (avatarDetails.modelResource?.LOD0_url || (avatarDetails.modelResource as any).src)
if (avatarDetails.modelResource?.LOD0_url || (avatarDetails.modelResource as any)?.src)
spawnLocalAvatarInWorld({
avatarSpawnPose,
avatarDetail: {
Expand All @@ -98,12 +117,7 @@ export const useLocationSpawnAvatar = (spectate = false) => {
},
name: user.name.value
})
else {
NotificationService.dispatchNotify(
'Your avatar is missing its model. Please change your avatar from the user menu.',
{ variant: 'error' }
)
}
else fetchMissingAvatar(user, avatarSpawnPose)
}, [sceneLoaded, authState.user, authState.user?.avatar, spectateParam])
}

Expand Down
9 changes: 9 additions & 0 deletions packages/client-core/src/user/services/AvatarService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInt
import { StaticResourceInterface } from '@etherealengine/common/src/interfaces/StaticResourceInterface'
import { UserId } from '@etherealengine/common/src/interfaces/UserId'
import { matches, Validator } from '@etherealengine/engine/src/common/functions/MatchesUtils'
import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { WorldNetworkAction } from '@etherealengine/engine/src/networking/functions/WorldNetworkAction'
import { defineAction, defineState, dispatchAction, getMutableState, useState } from '@etherealengine/hyperflux'

Expand Down Expand Up @@ -191,6 +192,14 @@ export const AvatarService = {
isPublic
}
}).promise as Promise<StaticResourceInterface[]>
},

async getAvatar(id: string) {
try {
return Engine.instance.api.service('avatar').get(id)
} catch (err) {
return null
}
}
}

Expand Down
147 changes: 140 additions & 7 deletions packages/server-core/src/user/user/user.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import addAssociations from '@etherealengine/server-core/src/hooks/add-associati
import addScopeToUser from '../../hooks/add-scope-to-user'
import authenticate from '../../hooks/authenticate'
import verifyScope from '../../hooks/verify-scope'
import logger from '../../ServerLogger'

const restrictUserPatch = (context: HookContext) => {
if (context.params.isInternal) return context
Expand Down Expand Up @@ -89,29 +90,161 @@ const addAvatarResources = () => {
return async (context: HookContext): Promise<HookContext> => {
const { app, result } = context

if (result.avatar) {
// if (!result.data) console.log('result.avatar', result, result.avatar, result.avatar?.dataValues, result.dataValues, result.dataValues?.avatar?.dataValues,)

if (result.dataValues?.avatar) {
if (result.dataValues?.avatar.modelResourceId)
try {
result.dataValues.avatar.modelResource = await app
.service('static-resource')
.get(result.dataValues.avatar.modelResourceId)
} catch (err) {
logger.error('error getting avatar model %o', err)
}
if (result.dataValues?.avatar.dataValues?.modelResourceId)
try {
result.dataValues.avatar.dataValues.modelResource = await app
.service('static-resource')
.get(result.dataValues.avatar.dataValues.modelResourceId)
} catch (err) {
logger.error('error getting avatar model %o', err)
}
if (result.dataValues?.avatar.thumbnailResourceId)
try {
result.dataValues.avatar.thumbnailResource = await app
.service('static-resource')
.get(result.dataValues.avatar.thumbnailResourceId)
} catch (err) {
logger.error('error getting avatar model %o', err)
}
if (result.dataValues?.avatar.dataValues?.thumbnailResourceId)
try {
result.dataValues.avatar.dataValues.thumbnailResource = await app
.service('static-resource')
.get(result.dataValues.avatar.dataValues.thumbnailResourceId)
} catch (err) {
logger.error('error getting avatar model %o', err)
}
} else if (result.avatar) {
if (result.avatar.modelResourceId)
try {
result.avatar.modelResource = await app.service('static-resource').get(result.avatar.modelResourceId)
} catch (err) {}
if (result.avatar.dataValues.modelResourceId)
} catch (err) {
logger.error('error getting avatar model %o', err)
}
if (result.avatar.dataValues?.modelResourceId)
try {
result.avatar.dataValues.modelResource = await app
.service('static-resource')
.get(result.avatar.dataValues.modelResourceId)
} catch (err) {}
} catch (err) {
logger.error('error getting avatar model %o', err)
}
if (result.avatar.thumbnailResourceId)
try {
result.avatar.thumbnailResource = await app.service('static-resource').get(result.avatar.thumbnailResourceId)
} catch (err) {}
if (result.avatar.dataValues.thumbnailResourceId)
} catch (err) {
logger.error('error getting avatar model %o', err)
}
if (result.avatar.dataValues?.thumbnailResourceId)
try {
result.avatar.dataValues.thumbnailResource = await app
.service('static-resource')
.get(result.avatar.dataValues.thumbnailResourceId)
} catch (err) {}
} catch (err) {
logger.error('error getting avatar model %o', err)
}
}

// if (result.data) {
// const mappedUsers = result.data.map(user => {
// return new Promise(async (resolve, reject) => {
// if (user.avatar) {
// if (user.avatar.modelResourceId)
// try {
// user.avatar.modelResource = await app.service('static-resource').get(user.avatar.modelResourceId)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// if (user.avatar.dataValues?.modelResourceId)
// try {
// user.avatar.dataValues.modelResource = await app
// .service('static-resource')
// .get(user.avatar.dataValues.modelResourceId)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// if (user.avatar.thumbnailResourceId)
// try {
// user.avatar.thumbnailResource = await app.service('static-resource').get(user.avatar.thumbnailResourceId)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// if (user.avatar.dataValues?.thumbnailResourceId)
// try {
// user.avatar.dataValues.thumbnailResource = await app
// .service('static-resource')
// .get(user.avatar.dataValues.thumbnailResourceId)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// }
// if (user.dataValues.avatar) {
// if (user.dataValues.avatar.modelResourceId)
// try {
// user.dataValues.avatar.modelResource = await app.service('static-resource').get(user.dataValues.avatar.modelResourceId)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// try {
// console.log('POPULATING MUTLI DATAVALUES MODELRESOURCE')
// user.dataValues.avatar.dataValues.modelResource = await app
// .service('static-resource')
// .get(user.dataValues.avatar.dataValues.modelResourceId)
// console.log('DONE POPULATED MUTLI DATAVALUES MODELRESOURCE', user, user.dataValues.avatar.dataValues)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// if (user.dataValues.avatar.thumbnailResourceId)
// try {
// user.dataValues.avatar.thumbnailResource = await app.service('static-resource').get(user.dataValues.avatar.thumbnailResourceId)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// if (user.dataValues.avatar.dataValues?.thumbnailResourceId)
// try {
// user.dataValues.avatar.dataValues.thumbnailResource = await app
// .service('static-resource')
// .get(user.dataValues.avatar.dataValues.thumbnailResourceId)
// resolve(user)
// } catch (err) {
// logger.error('error getting avatar model %o', err)
// reject(err)
// }
// }
// })
// })
// console.log('promises', mappedUsers)
// await Promise.all(mappedUsers)
// result.data = mappedUsers
// }

// if (!result.data) console.log('Returned result.avatar', result, result.avatar, result.avatar?.dataValues, result.dataValues?.avatar?.dataValues)
// if (result.data && result.total > 0) console.log('nested user', result.data[0].avatar.dataValues)
return context
}
}
Expand Down

0 comments on commit 77831b8

Please sign in to comment.