Skip to content

Commit

Permalink
Loading and mocap fixes (#9485)
Browse files Browse the repository at this point in the history
* loading and mocap fixes

* scene unload

* fix tests
  • Loading branch information
HexaField committed Dec 22, 2023
1 parent ec91819 commit 31df4b7
Show file tree
Hide file tree
Showing 13 changed files with 94 additions and 85 deletions.
29 changes: 14 additions & 15 deletions packages/client-core/src/components/World/EngineHooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,21 @@ import { AppLoadingState, AppLoadingStates } from '@etherealengine/engine/src/co
import multiLogger from '@etherealengine/engine/src/common/functions/logger'
import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { EngineActions, EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState'
import { getComponent, removeComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { getComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { NetworkState, addNetwork } from '@etherealengine/engine/src/networking/NetworkState'
import { Network, NetworkTopics, createNetwork } from '@etherealengine/engine/src/networking/classes/Network'
import { NetworkPeerFunctions } from '@etherealengine/engine/src/networking/functions/NetworkPeerFunctions'
import { spawnLocalAvatarInWorld } from '@etherealengine/engine/src/networking/functions/receiveJoinWorld'
import { PortalComponent, PortalState } from '@etherealengine/engine/src/scene/components/PortalComponent'
import { UUIDComponent } from '@etherealengine/engine/src/scene/components/UUIDComponent'
import { addOutgoingTopicIfNecessary, dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux'
import { addOutgoingTopicIfNecessary, dispatchAction, getMutableState } from '@etherealengine/hyperflux'
import { loadEngineInjection } from '@etherealengine/projects/loadEngineInjection'

import { AvatarState } from '@etherealengine/engine/src/avatar/state/AvatarNetworkState'
import { FollowCameraComponent } from '@etherealengine/engine/src/camera/components/FollowCameraComponent'
import { TargetCameraRotationComponent } from '@etherealengine/engine/src/camera/components/TargetCameraRotationComponent'
import { UndefinedEntity } from '@etherealengine/engine/src/ecs/classes/Entity'
import { removeEntity } from '@etherealengine/engine/src/ecs/functions/EntityFunctions'
import { WorldNetworkAction } from '@etherealengine/engine/src/networking/functions/WorldNetworkAction'
import { LinkState } from '@etherealengine/engine/src/scene/components/LinkComponent'
import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { ComputedTransformComponent } from '@etherealengine/engine/src/transform/components/ComputedTransformComponent'
import { RouterState } from '../../common/services/RouterService'
import { LocationState } from '../../social/services/LocationService'
import { SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientFunctions'
Expand Down Expand Up @@ -128,24 +124,27 @@ export const useLocationSpawnAvatarWithDespawn = () => {

export const despawnSelfAvatar = () => {
const clientEntity = Engine.instance.localClientEntity
console.log('despawnSelfAvatar', clientEntity)
if (!clientEntity) return

const peersCountForUser =
getState(NetworkState).networks[getState(NetworkState).hostIds.world!].users[Engine.instance.userID]?.length
const network = NetworkState.worldNetwork

const peersCountForUser = network?.users?.[Engine.instance.userID]?.length

// if we are the last peer in the world for this user, destroy the object
if (!peersCountForUser || peersCountForUser === 1) {
dispatchAction(WorldNetworkAction.destroyObject({ entityUUID: getComponent(clientEntity, UUIDComponent) }))
}

const cameraEntity = Engine.instance.cameraEntity
if (!cameraEntity) return
/** @todo this logic should be handled by the camera system */
// const cameraEntity = Engine.instance.cameraEntity
// if (!cameraEntity) return

const cameraComputed = getComponent(cameraEntity, ComputedTransformComponent)
removeEntity(cameraComputed.referenceEntity)
removeComponent(cameraEntity, ComputedTransformComponent)
removeComponent(cameraEntity, FollowCameraComponent)
removeComponent(cameraEntity, TargetCameraRotationComponent)
// const cameraComputed = getComponent(cameraEntity, ComputedTransformComponent)
// removeEntity(cameraComputed.referenceEntity)
// removeComponent(cameraEntity, ComputedTransformComponent)
// removeComponent(cameraEntity, FollowCameraComponent)
// removeComponent(cameraEntity, TargetCameraRotationComponent)
}

export const useLinkTeleport = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {
} from '@etherealengine/client-core/src/common/services/MediaInstanceConnectionService'
import { ChannelService, ChannelState } from '@etherealengine/client-core/src/social/services/ChannelService'
import { LocationState } from '@etherealengine/client-core/src/social/services/LocationService'
import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState'
import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState'
import { getMutableState, none, useHookstate } from '@etherealengine/hyperflux'

Expand All @@ -56,7 +55,6 @@ import MessagesMenu from '../user/components/UserMenu/menus/MessagesMenu'
export const WorldInstanceProvisioning = () => {
const locationState = useHookstate(getMutableState(LocationState))
const isUserBanned = locationState.currentLocation.selfUserBanned.value
const engineState = useHookstate(getMutableState(EngineState))

const worldNetwork = NetworkState.worldNetwork
const worldNetworkState = useWorldNetwork()
Expand All @@ -69,8 +67,6 @@ export const WorldInstanceProvisioning = () => {

// Once we have the location, provision the instance server
useEffect(() => {
if (!engineState.sceneLoaded.value || locationInstances.keys.length) return

const currentLocation = locationState.currentLocation.location
const hasJoined = !!worldNetwork

Expand Down Expand Up @@ -106,7 +102,7 @@ export const WorldInstanceProvisioning = () => {
)
}
}
}, [engineState.sceneLoaded, locationState.currentLocation.location, locationInstances.keys])
}, [locationState.currentLocation.location])

// Populate the URL with the room code and instance id
useEffect(() => {
Expand Down
32 changes: 17 additions & 15 deletions packages/client/src/pages/capture/capture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,23 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/

import React from 'react'
import React, { useEffect } from 'react'
import { useParams } from 'react-router-dom'

import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService'
import {
useLoadEngineWithScene,
useOfflineNetwork,
useOnlineNetwork
} from '@etherealengine/client-core/src/components/World/EngineHooks'
import { useLoadLocation, useLoadScene } from '@etherealengine/client-core/src/components/World/LoadLocationScene'
import { useOfflineNetwork, useOnlineNetwork } from '@etherealengine/client-core/src/components/World/EngineHooks'
import { useRemoveEngineCanvas } from '@etherealengine/client-core/src/hooks/useRemoveEngineCanvas'
import { AuthService } from '@etherealengine/client-core/src/user/services/AuthService'
import { PresentationSystemGroup } from '@etherealengine/engine/src/ecs/functions/EngineFunctions'
import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
import { ECSRecordingActions } from '@etherealengine/engine/src/recording/ECSRecordingSystem'
import { defineActionQueue } from '@etherealengine/hyperflux'
import { defineActionQueue, getMutableState, useHookstate } from '@etherealengine/hyperflux'
import CaptureUI from '@etherealengine/ui/src/pages/Capture'

import { LocationService, LocationState } from '@etherealengine/client-core/src/social/services/LocationService'
import '@etherealengine/client-core/src/world/ClientNetworkModule'
import '@etherealengine/engine/src/EngineModule'
import { AppLoadingState, AppLoadingStates } from '@etherealengine/engine/src/common/AppLoadingService'

const ecsRecordingErrorActionQueue = defineActionQueue(ECSRecordingActions.error.matches)

Expand All @@ -57,20 +54,25 @@ const NotifyRecordingErrorSystem = defineSystem({
})

export const CaptureLocation = () => {
const locationState = useHookstate(getMutableState(LocationState))
useRemoveEngineCanvas()

const params = useParams()

const locationName = params?.locationName as string | undefined
const offline = !locationName

useLoadEngineWithScene({ spectate: true })

if (offline) {
useLoadScene({ projectName: 'default-project', sceneName: 'default' })
} else {
useLoadLocation({ locationName: params.locationName! })
}
useEffect(() => {
if (locationName) LocationState.setLocationName(locationName)
getMutableState(AppLoadingState).merge({
state: AppLoadingStates.SUCCESS,
loaded: true
})
}, [])

useEffect(() => {
if (locationState.locationName.value) LocationService.getLocationByName(locationState.locationName.value)
}, [locationState.locationName.value])

if (offline) {
useOfflineNetwork()
Expand Down
1 change: 1 addition & 0 deletions packages/engine/src/common/AppLoadingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const AppLoadingStates = {

type AppLoadingStatesType = (typeof AppLoadingStates)[keyof typeof AppLoadingStates]

/** @deprecated @todo replace with scene loading state directly */
export const AppLoadingState = defineState({
name: 'AppLoadingState',
initial: () => ({
Expand Down
15 changes: 10 additions & 5 deletions packages/engine/src/ecs/functions/EntityTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
getComponent,
getMutableComponent,
getOptionalComponent,
getOptionalMutableComponent,
hasComponent,
removeComponent,
setComponent
Expand Down Expand Up @@ -76,10 +77,14 @@ export const EntityTreeComponent = defineComponent({

// If a previous parentEntity, remove this entity from its children
if (currentParentEntity && currentParentEntity !== json.parentEntity) {
const oldParent = getMutableComponent(currentParentEntity, EntityTreeComponent)
const parentChildIndex = oldParent.children.value.findIndex((child) => child === entity)
const children = oldParent.children.get(NO_PROXY)
oldParent.children.set([...children.slice(0, parentChildIndex), ...children.slice(parentChildIndex + 1)])
if (entityExists(currentParentEntity)) {
const oldParent = getOptionalMutableComponent(currentParentEntity, EntityTreeComponent)
if (oldParent) {
const parentChildIndex = oldParent.children.value.findIndex((child) => child === entity)
const children = oldParent.children.get(NO_PROXY)
oldParent.children.set([...children.slice(0, parentChildIndex), ...children.slice(parentChildIndex + 1)])
}
}
}

// set new data
Expand All @@ -92,7 +97,7 @@ export const EntityTreeComponent = defineComponent({
if (matchesEntityUUID.test(json?.uuid) && !hasComponent(entity, UUIDComponent))
setComponent(entity, UUIDComponent, json.uuid)

if (parentEntity) {
if (parentEntity && entityExists(parentEntity)) {
if (!hasComponent(parentEntity, EntityTreeComponent)) setComponent(parentEntity, EntityTreeComponent)

const parentState = getMutableComponent(parentEntity, EntityTreeComponent)
Expand Down
5 changes: 2 additions & 3 deletions packages/engine/src/interaction/systems/GrabbableSystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,11 @@ export function transferAuthorityOfObjectReceptor(

// since grabbables are all client authoritative, we don't need to recompute this for all users
export function grabbableQueryAll(grabbableEntity: Entity) {
const grabberComponent = getComponent(grabbableEntity, GrabbedComponent)

const grabbedComponent = getComponent(grabbableEntity, GrabbedComponent)
if (!grabbedComponent) return
const attachmentPoint = grabbedComponent.attachmentPoint

const target = getHandTarget(grabberComponent.grabberEntity, attachmentPoint ?? 'right')!
const target = getHandTarget(grabbedComponent.grabberEntity, attachmentPoint ?? 'right')!

const rigidbodyComponent = getComponent(grabbableEntity, RigidBodyComponent)

Expand Down
5 changes: 4 additions & 1 deletion packages/engine/src/mocap/MotionCaptureSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ const execute = () => {
// for now, it is unnecessary to compute anything on the server
if (!isClient) return
const network = NetworkState.worldNetwork
if (!network) return

for (const [peerID, mocapData] of timeSeriesMocapData) {
if (!network?.peers?.[peerID] || timeSeriesMocapLastSeen.get(peerID)! < Date.now() - 1000) {
if (!network.peers[peerID] || timeSeriesMocapLastSeen.get(peerID)! < Date.now() - 1000) {
timeSeriesMocapData.delete(peerID)
timeSeriesMocapLastSeen.delete(peerID)
}
Expand All @@ -126,6 +128,7 @@ const execute = () => {
for (const entity of motionCaptureQuery()) {
const peers = Object.keys(network.peers).find((peerID: PeerID) => timeSeriesMocapData.has(peerID))
const rigComponent = getComponent(entity, AvatarRigComponent)
if (!rigComponent.normalizedRig) continue
const worldHipsParent = rigComponent.normalizedRig.hips.node.parent
if (!peers) {
removeComponent(entity, MotionCaptureRigComponent)
Expand Down
2 changes: 2 additions & 0 deletions packages/engine/src/mocap/poseToInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ let poseHoldTimer = 0

export const evaluatePose = (entity: Entity) => {
const rig = getComponent(entity, AvatarRigComponent).normalizedRig
if (!rig) return

const deltaSeconds = getState(EngineState).deltaSeconds
const pose = getMutableComponent(entity, MotionCapturePoseComponent)
if (!MotionCaptureRigComponent.solvingLowerBody[entity]) return 'none'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export class WorldNetworkAction {
ownerId: matchesUserId,
networkId: matchesNetworkId,
newAuthority: matchesPeerID,
$topic: NetworkTopics.world
$topic: NetworkTopics.world,
$cache: true
})
}
19 changes: 3 additions & 16 deletions packages/engine/src/networking/state/EntityNetworkState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,7 @@ import { NetworkObjectComponent, NetworkObjectOwnedTag } from '../components/Net
import { NetworkPeerFunctions } from '../functions/NetworkPeerFunctions'
import { WorldNetworkAction } from '../functions/WorldNetworkAction'
import { NetworkState } from '../NetworkState'
import {
EntityNetworkState,
receiveRequestAuthorityOverObject,
receiveTransferAuthorityOfObject
} from './EntityNetworkState'
import { EntityNetworkState, receiveRequestAuthorityOverObject } from './EntityNetworkState'

describe('EntityNetworkState', () => {
beforeEach(async () => {
Expand Down Expand Up @@ -277,10 +273,6 @@ describe('EntityNetworkState', () => {
assert.equal(getComponent(networkObjectEntitiesBefore[0], NetworkObjectComponent).authorityPeerID, peerID)
assert.equal(hasComponent(networkObjectEntitiesBefore[0], NetworkObjectOwnedTag), true)

const transferAuthorityOfObjectQueue = ActionFunctions.defineActionQueue(
WorldNetworkAction.transferAuthorityOfObject.matches
)

receiveRequestAuthorityOverObject(
WorldNetworkAction.requestAuthorityOverObject({
$from: userId,
Expand All @@ -293,7 +285,7 @@ describe('EntityNetworkState', () => {

ActionFunctions.applyIncomingActions()

for (const action of transferAuthorityOfObjectQueue()) receiveTransferAuthorityOfObject(action)
await act(() => receiveActions(EntityNetworkState))

const networkObjectEntitiesAfter = networkObjectQuery()
const networkObjectOwnedEntitiesAfter = networkObjectOwnedQuery()
Expand Down Expand Up @@ -351,10 +343,6 @@ describe('EntityNetworkState', () => {
assert.equal(getComponent(networkObjectEntitiesBefore[0], NetworkObjectComponent).authorityPeerID, peerID)
assert.equal(hasComponent(networkObjectEntitiesBefore[0], NetworkObjectOwnedTag), false)

const transferAuthorityOfObjectQueue = ActionFunctions.defineActionQueue(
WorldNetworkAction.transferAuthorityOfObject.matches
)

receiveRequestAuthorityOverObject(
WorldNetworkAction.requestAuthorityOverObject({
$from: userId, // from user
Expand All @@ -366,9 +354,8 @@ describe('EntityNetworkState', () => {
)

applyIncomingActions()
await act(() => receiveActions(EntityNetworkState))

for (const action of transferAuthorityOfObjectQueue()) receiveTransferAuthorityOfObject(action)
await act(() => receiveActions(EntityNetworkState))

const networkObjectEntitiesAfter = networkObjectQuery()
const networkObjectOwnedEntitiesAfter = networkObjectOwnedQuery()
Expand Down
31 changes: 13 additions & 18 deletions packages/engine/src/networking/state/EntityNetworkState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export const EntityNetworkState = defineState({
})
}
],

[
WorldNetworkAction.destroyObject,
(state, action: typeof WorldNetworkAction.destroyObject.matches._TYPE) => {
Expand All @@ -106,10 +105,23 @@ export const EntityNetworkState = defineState({
if (!entity) return
removeEntity(entity)
}
],
[
WorldNetworkAction.transferAuthorityOfObject,
(state, action: typeof WorldNetworkAction.transferAuthorityOfObject.matches._TYPE) => {
const entity = NetworkObjectComponent.getNetworkObject(action.ownerId, action.networkId)
if (!entity) return
getMutableComponent(entity, NetworkObjectComponent).authorityPeerID.set(action.newAuthority)
}
]
]
})

/**
* Only the transfer needs to be event sourced
* @param action
* @returns
*/
export const receiveRequestAuthorityOverObject = (
action: typeof WorldNetworkAction.requestAuthorityOverObject.matches._TYPE
) => {
Expand All @@ -135,29 +147,12 @@ export const receiveRequestAuthorityOverObject = (
)
}

export const receiveTransferAuthorityOfObject = (
action: typeof WorldNetworkAction.transferAuthorityOfObject.matches._TYPE
) => {
// Authority request can only be processed by owner
if (action.$from !== action.ownerId) return

const entity = NetworkObjectComponent.getNetworkObject(action.ownerId, action.networkId)
if (!entity)
return console.log(
`Warning - tried to get entity belonging to ${action.ownerId} with ID ${action.networkId}, but it doesn't exist`
)

getMutableComponent(entity, NetworkObjectComponent).authorityPeerID.set(action.newAuthority)
}

const requestAuthorityOverObjectQueue = defineActionQueue(WorldNetworkAction.requestAuthorityOverObject.matches)
const transferAuthorityOfObjectQueue = defineActionQueue(WorldNetworkAction.transferAuthorityOfObject.matches)

const execute = () => {
receiveActions(EntityNetworkState)

for (const action of requestAuthorityOverObjectQueue()) receiveRequestAuthorityOverObject(action)
for (const action of transferAuthorityOfObjectQueue()) receiveTransferAuthorityOfObject(action)
}

export const EntityNetworkStateSystem = defineSystem({
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/src/recording/ECSRecordingSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ export const onStartPlayback = async (action: ReturnType<typeof ECSRecordingActi
}
}

if (!UUIDComponent.getEntityByUUID(entityID)) {
if (!UUIDComponent.getEntityByUUID(entityID) && isClone) {
dispatchAction(
AvatarNetworkAction.spawn({
$from: entityID,
Expand Down

0 comments on commit 31df4b7

Please sign in to comment.