diff --git a/packages/client-core/src/components/InstanceChat/index.tsx b/packages/client-core/src/components/InstanceChat/index.tsx index f6facc62d7..cd935b2613 100755 --- a/packages/client-core/src/components/InstanceChat/index.tsx +++ b/packages/client-core/src/components/InstanceChat/index.tsx @@ -32,7 +32,6 @@ import { AuthState } from '@etherealengine/client-core/src/user/services/AuthSer import { AudioEffectPlayer } from '@etherealengine/engine/src/audio/systems/MediaSystem' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState' -import { WorldNetworkAction } from '@etherealengine/engine/src/networking/functions/WorldNetworkAction' import { WorldState } from '@etherealengine/engine/src/networking/interfaces/WorldState' import { dispatchAction, getMutableState, useHookstate } from '@etherealengine/hyperflux' import Avatar from '@etherealengine/ui/src/primitives/mui/Avatar' @@ -47,6 +46,7 @@ import { Close as CloseIcon, Message as MessageIcon } from '@mui/icons-material' import Fab from '@mui/material/Fab' import { AppAction } from '../../common/services/AppService' +import { AvatarUIActions, AvatarUIState } from '../../systems/state/AvatarUIState' import { getUserAvatarThumbnail } from '../../user/functions/useUserAvatarThumbnail' import { useShelfStyles } from '../Shelves/useShelfStyles' import defaultStyles from './index.module.scss' @@ -89,7 +89,7 @@ export const useChatHooks = ({ chatWindowOpen, setUnreadMessages, messageRefInpu const composingMessage = useHookstate('') const cursorPosition = useHookstate(0) const user = useHookstate(getMutableState(AuthState).user) - const usersTyping = useHookstate(getMutableState(EngineState)).usersTyping[user?.id.value].value + const usersTyping = useHookstate(getMutableState(AvatarUIState)).usersTyping[user?.id.value].value const isMultiline = useHookstate(false) useEffect(() => { @@ -102,7 +102,7 @@ export const useChatHooks = ({ chatWindowOpen, setUnreadMessages, messageRefInpu if (!composingMessage.value || !usersTyping) return const delayDebounce = setTimeout(() => { dispatchAction( - WorldNetworkAction.setUserTyping({ + AvatarUIActions.setUserTyping({ typing: false }) ) @@ -132,7 +132,7 @@ export const useChatHooks = ({ chatWindowOpen, setUnreadMessages, messageRefInpu if (message.length > composingMessage.value.length) { if (!usersTyping) { dispatchAction( - WorldNetworkAction.setUserTyping({ + AvatarUIActions.setUserTyping({ typing: true }) ) @@ -141,7 +141,7 @@ export const useChatHooks = ({ chatWindowOpen, setUnreadMessages, messageRefInpu if (message.length == 0 || message.length < composingMessage.value.length) { if (usersTyping) { dispatchAction( - WorldNetworkAction.setUserTyping({ + AvatarUIActions.setUserTyping({ typing: false }) ) @@ -156,7 +156,7 @@ export const useChatHooks = ({ chatWindowOpen, setUnreadMessages, messageRefInpu if (composingMessage?.value?.length && instanceId) { if (usersTyping) { dispatchAction( - WorldNetworkAction.setUserTyping({ + AvatarUIActions.setUserTyping({ typing: false }) ) diff --git a/packages/client-core/src/media/webcam/WebcamInput.ts b/packages/client-core/src/media/webcam/WebcamInput.ts index 867531a655..439cad30a0 100755 --- a/packages/client-core/src/media/webcam/WebcamInput.ts +++ b/packages/client-core/src/media/webcam/WebcamInput.ts @@ -292,6 +292,7 @@ const execute = () => { for (const entity of webcamQuery()) setAvatarExpression(entity) } +/** @todo - this system currently is not used and has been replaced by the /capture route */ export const WebcamInputSystem = defineSystem({ uuid: 'ee.client.WebcamInputSystem', execute diff --git a/packages/client-core/src/systems/AvatarUISystem.tsx b/packages/client-core/src/systems/AvatarUISystem.tsx index 8d8fd85882..f79316eec7 100644 --- a/packages/client-core/src/systems/AvatarUISystem.tsx +++ b/packages/client-core/src/systems/AvatarUISystem.tsx @@ -57,6 +57,7 @@ import { getMutableState, getState, none } from '@etherealengine/hyperflux' import AvatarContextMenu from '../user/components/UserMenu/menus/AvatarContextMenu' import { PopupMenuState } from '../user/components/UserMenu/PopupMenuService' +import { AvatarUIStateSystem } from './state/AvatarUIState' import { createAvatarDetailView } from './ui/AvatarDetailView' import { AvatarUIContextMenuState } from './ui/UserMenuView' @@ -290,5 +291,6 @@ const reactor = () => { export const AvatarUISystem = defineSystem({ uuid: 'ee.client.AvatarUISystem', execute, - reactor + reactor, + subSystems: [AvatarUIStateSystem] }) diff --git a/packages/engine/src/avatar/components/SpawnPoseComponent.ts b/packages/client-core/src/systems/state/AvatarUIState.ts old mode 100755 new mode 100644 similarity index 53% rename from packages/engine/src/avatar/components/SpawnPoseComponent.ts rename to packages/client-core/src/systems/state/AvatarUIState.ts index 7518439949..3041691aa2 --- a/packages/engine/src/avatar/components/SpawnPoseComponent.ts +++ b/packages/client-core/src/systems/state/AvatarUIState.ts @@ -23,24 +23,39 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { Quaternion, Vector3 } from 'three' - -import { defineComponent } from '../../ecs/functions/ComponentFunctions' - -export const SpawnPoseComponent = defineComponent({ - name: 'SpawnPoseComponent', - - onInit: (entity) => { - return { - position: new Vector3(), - rotation: new Quaternion() - } +import { matches } from '@etherealengine/engine/src/common/functions/MatchesUtils' +import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions' +import { NetworkTopics } from '@etherealengine/engine/src/networking/classes/Network' +import { defineAction, defineState, none, receiveActions } from '@etherealengine/hyperflux' + +export class AvatarUIActions { + static setUserTyping = defineAction({ + type: 'ee.client.avatar.USER_IS_TYPING', + typing: matches.boolean, + $topic: NetworkTopics.world + }) +} + +export const AvatarUIState = defineState({ + name: 'AvatarUIState', + + initial: { + usersTyping: {} as { [key: string]: true } }, - onSet: (entity, component, json) => { - if (!json) return + receptors: [ + [ + AvatarUIActions.setUserTyping, + (state, action) => { + state.usersTyping[action.$from].set(action.typing ? true : none) + } + ] + ] +}) - if (json.position) component.position.set(json.position) - if (json.rotation) component.rotation.set(json.rotation) +export const AvatarUIStateSystem = defineSystem({ + uuid: 'ee.engine.avatar.AvatarUIStateSystem', + execute: () => { + receiveActions(AvatarUIState) } }) diff --git a/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx b/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx index 83b2f628da..e9466df57f 100644 --- a/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx +++ b/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx @@ -36,9 +36,9 @@ import { createXRUI } from '@etherealengine/engine/src/xrui/functions/createXRUI import { useXRUIState } from '@etherealengine/engine/src/xrui/functions/useXRUIState' import { createState, getMutableState, useHookstate } from '@etherealengine/hyperflux' +import { AvatarUIState } from '../../state/AvatarUIState' import styleString from './index.scss?inline' -/** @deprecated */ export function createAvatarDetailView(id: string) { const videoPreviewMesh = new Mesh(new CircleGeometry(0.25, 32), new MeshBasicMaterial()) const ui = createXRUI( @@ -55,16 +55,16 @@ export function createAvatarDetailView(id: string) { interface AvatarDetailState { id: string } -/** @deprecated */ + const AvatarDetailView = () => { const { t } = useTranslation() const detailState = useXRUIState() - const user = Array.from(Engine.instance.worldNetworkState.peers?.get({ noproxy: true }).values()).find( - (peer) => peer.userId === detailState.id.value - ) + const user = Engine.instance.worldNetworkState?.peers + ? Array.from(Engine.instance.worldNetwork.peers.values()).find((peer) => peer.userId === detailState.id.value) + : undefined const worldState = useHookstate(getMutableState(WorldState)).get({ noproxy: true }) - const engineState = useHookstate(getMutableState(EngineState)) - const usersTyping = engineState.usersTyping[detailState.id.value].value + const usersTypingState = useHookstate(getMutableState(AvatarUIState).usersTyping) + const usersTyping = usersTypingState[detailState.id.value]?.value const username = worldState?.userNames && user ? worldState.userNames[user.userId] : 'A user' return ( diff --git a/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx index b6126075ad..cd1f3c75bc 100755 --- a/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx +++ b/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx @@ -35,6 +35,8 @@ import Text from '@etherealengine/client-core/src/common/components/Text' import { AuthService, AuthState } from '@etherealengine/client-core/src/user/services/AuthService' import { defaultThemeModes, defaultThemeSettings } from '@etherealengine/common/src/constants/DefaultThemeSettings' import capitalizeFirstLetter from '@etherealengine/common/src/utils/capitalizeFirstLetter' +import InputGroup from '@etherealengine/editor/src/components/inputs/InputGroup' +import SelectInput from '@etherealengine/editor/src/components/inputs/SelectInput' import { AudioSettingAction, AudioState } from '@etherealengine/engine/src/audio/AudioState' import { AvatarAxesControlScheme, @@ -58,6 +60,29 @@ import { UserMenus } from '../../../UserUISystem' import styles from '../index.module.scss' import { PopupMenuServices } from '../PopupMenuService' +const ShadowMapResolutionOptions = [ + { + label: '256px', + value: 256 + }, + { + label: '512px', + value: 512 + }, + { + label: '1024px', + value: 1024 + }, + { + label: '2048px', + value: 2048 + }, + { + label: '4096px (not recommended)', + value: 4096 + } +] + const chromeDesktop = !isMobile && /chrome/i.test(navigator.userAgent) type Props = { @@ -458,6 +483,17 @@ const SettingMenu = ({ isPopover }: Props): JSX.Element => { onChange={handleQualityLevelChange} /> + + rendererState.shadowMapResolution.set(resolution)} + /> + + { }) /** Fixed */ - startSystems([IncomingNetworkSystem, WorldNetworkActionSystem, EquippableSystem, AvatarSimulationSystemGroup], { + startSystems([IncomingNetworkSystem, EntityNetworkStateSystem, EquippableSystem, AvatarSimulationSystemGroup], { with: SimulationSystemGroup }) diff --git a/packages/client-core/src/world/useDefaultLocationSystems.ts b/packages/client-core/src/world/useDefaultLocationSystems.ts index 6c4f8bf8ba..db1791bd7f 100644 --- a/packages/client-core/src/world/useDefaultLocationSystems.ts +++ b/packages/client-core/src/world/useDefaultLocationSystems.ts @@ -29,7 +29,6 @@ import { InputSystemGroup, PresentationSystemGroup } from '@etherealengine/engin import { startSystems } from '@etherealengine/engine/src/ecs/functions/SystemFunctions' import { TransformSystem } from '@etherealengine/engine/src/transform/systems/TransformSystem' -import { WebcamInputSystem } from '../media/webcam/WebcamInput' import { ClientNetworkingSystem } from '../networking/ClientNetworkingSystem' import { AvatarUISystem } from '../systems/AvatarUISystem' import { LoadingUISystem } from '../systems/LoadingUISystem' @@ -40,8 +39,6 @@ import { UserUISystem } from '../user/UserUISystem' export const useDefaultLocationSystems = (online: boolean) => { useEffect(() => { - startSystems([WebcamInputSystem], { with: InputSystemGroup }) - startSystems([LoadingUISystem, AvatarUISystem, WidgetUISystem], { before: TransformSystem }) const postPresentationSystems = [UserUISystem, FilteredUsersSystem, WarningUISystem] diff --git a/packages/client-core/tests/mocha.env.js b/packages/client-core/tests/mocha.env.js index bf22c479d1..287cdd40e5 100644 --- a/packages/client-core/tests/mocha.env.js +++ b/packages/client-core/tests/mocha.env.js @@ -26,6 +26,7 @@ Ethereal Engine. All Rights Reserved. process.env.APP_ENV = 'test' +process.env.NODE_ENV = 'test' process.env.TS_NODE_FILES = true process.env.TS_NODE_PROJECT = 'tsconfig.json' process.env.TS_NODE_COMPILER_OPTIONS = '{\"module\": \"commonjs\" }' diff --git a/packages/client/tests/mocha.env.js b/packages/client/tests/mocha.env.js index d37cea8231..5dc69c9c06 100644 --- a/packages/client/tests/mocha.env.js +++ b/packages/client/tests/mocha.env.js @@ -26,6 +26,7 @@ Ethereal Engine. All Rights Reserved. process.env.APP_ENV = 'test' +process.env.NODE_ENV = 'test' process.env.TS_NODE_FILES = true process.env.TS_NODE_PROJECT = 'tsconfig.json' process.env.TS_NODE_COMPILER_OPTIONS = '{\"module\": \"commonjs\" }' diff --git a/packages/editor/src/components/properties/LightShadowProperties.tsx b/packages/editor/src/components/properties/LightShadowProperties.tsx index 45d4e4ad31..f3db3f3ff8 100755 --- a/packages/editor/src/components/properties/LightShadowProperties.tsx +++ b/packages/editor/src/components/properties/LightShadowProperties.tsx @@ -32,36 +32,9 @@ import { Component, useComponent } from '@etherealengine/engine/src/ecs/function import BooleanInput from '../inputs/BooleanInput' import InputGroup from '../inputs/InputGroup' import NumericInputGroup from '../inputs/NumericInputGroup' -import SelectInput from '../inputs/SelectInput' -import { updateProperties, updateProperty } from './Util' +import { updateProperty } from './Util' -/** - * Array containing options for shadow resolution - */ -const ShadowMapResolutionOptions = [ - { - label: '256px', - value: 256 - }, - { - label: '512px', - value: 512 - }, - { - label: '1024px', - value: 1024 - }, - { - label: '2048px', - value: 2048 - }, - { - label: '4096px (not recommended)', - value: 4096 - } -] - -//creating properties for LightShadowProperties component +/**creating properties for LightShadowProperties component */ type LightShadowPropertiesProps = { entity: Entity comp: Component @@ -70,16 +43,10 @@ type LightShadowPropertiesProps = { /** * OnChangeShadowMapResolution used to customize properties of LightShadowProperties * Used with LightNodeEditors. - * - * @type {[class component]} */ export const LightShadowProperties = (props: LightShadowPropertiesProps) => { const { t } = useTranslation() - const changeShadowMapResolution = (resolution) => { - updateProperties(props.comp, { shadowMapResolution: resolution }) - } - const lightComponent = useComponent(props.entity, props.comp).value as any return ( @@ -87,14 +54,6 @@ export const LightShadowProperties = (props: LightShadowPropertiesProps) => { - - - { beforeEach(async () => { createEngine() await Physics.load() + Engine.instance.store.defaultDispatchDelay = () => 0 Engine.instance.physicsWorld = Physics.createWorld() Engine.instance.userId = 'userId' as UserId Engine.instance.peerID = 'peerID' as PeerID @@ -61,20 +69,21 @@ describe('moveAvatar function tests', () => { const engineState = getMutableState(EngineState) engineState.simulationTimestep.set(1000 / 60) - const spawnAvatar = AvatarNetworkAction.spawn({ - $from: Engine.instance.userId, - position: new Vector3(), - rotation: new Quaternion(), - entityUUID: Engine.instance.userId as string as EntityUUID - }) + dispatchAction( + AvatarNetworkAction.spawn({ + $from: Engine.instance.userId, + position: new Vector3(), + rotation: new Quaternion(), + entityUUID: Engine.instance.userId as string as EntityUUID + }) + ) - WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar as any) + applyIncomingActions() + receiveActions(EntityNetworkState) spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) const entity = Engine.instance.getUserAvatarEntity(Engine.instance.userId) - const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000) - const velocity = getComponent(entity, RigidBodyComponent).linearVelocity const avatar = getComponent(entity, AvatarControllerComponent) @@ -94,20 +103,21 @@ describe('moveAvatar function tests', () => { const engineState = getMutableState(EngineState) engineState.simulationTimestep.set(1000 / 60) - const spawnAvatar = AvatarNetworkAction.spawn({ - $from: Engine.instance.userId, - position: new Vector3(), - rotation: new Quaternion(), - entityUUID: Engine.instance.userId as string as EntityUUID - }) + dispatchAction( + AvatarNetworkAction.spawn({ + $from: Engine.instance.userId, + position: new Vector3(), + rotation: new Quaternion(), + entityUUID: Engine.instance.userId as string as EntityUUID + }) + ) - WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar as any) + applyIncomingActions() + receiveActions(EntityNetworkState) spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) const entity = Engine.instance.getUserAvatarEntity(Engine.instance.userId) - const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000) - const velocity = getComponent(entity, RigidBodyComponent).linearVelocity // velocity starts at 0 @@ -129,20 +139,21 @@ describe('moveAvatar function tests', () => { /* mock */ Engine.instance.physicsWorld.timestep = 1 / 2 - const spawnAvatar = AvatarNetworkAction.spawn({ - $from: Engine.instance.userId, - position: new Vector3(), - rotation: new Quaternion(), - entityUUID: Engine.instance.userId as string as EntityUUID - }) + dispatchAction( + AvatarNetworkAction.spawn({ + $from: Engine.instance.userId, + position: new Vector3(), + rotation: new Quaternion(), + entityUUID: Engine.instance.userId as string as EntityUUID + }) + ) - WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar as any) + applyIncomingActions() + receiveActions(EntityNetworkState) spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) const entity = Engine.instance.getUserAvatarEntity(Engine.instance.userId) - const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000) - const velocity = getComponent(entity, RigidBodyComponent).linearVelocity // velocity starts at 0 @@ -161,20 +172,21 @@ describe('moveAvatar function tests', () => { const engineState = getMutableState(EngineState) engineState.simulationTimestep.set(1000 / 60) - const spawnAvatar = AvatarNetworkAction.spawn({ - $from: Engine.instance.userId, - position: new Vector3(), - rotation: new Quaternion(), - entityUUID: Engine.instance.userId as string as EntityUUID - }) + dispatchAction( + AvatarNetworkAction.spawn({ + $from: Engine.instance.userId, + position: new Vector3(), + rotation: new Quaternion(), + entityUUID: Engine.instance.userId as string as EntityUUID + }) + ) - WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar as any) + applyIncomingActions() + receiveActions(EntityNetworkState) spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) const entity = Engine.instance.getUserAvatarEntity(Engine.instance.userId) - const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000) - const velocity = getComponent(entity, RigidBodyComponent).linearVelocity // velocity starts at 0 diff --git a/packages/engine/src/avatar/functions/moveAvatar.ts b/packages/engine/src/avatar/functions/moveAvatar.ts index 3a154a65fc..b1c15fb206 100755 --- a/packages/engine/src/avatar/functions/moveAvatar.ts +++ b/packages/engine/src/avatar/functions/moveAvatar.ts @@ -37,17 +37,18 @@ import { Engine } from '../../ecs/classes/Engine' import { EngineState } from '../../ecs/classes/EngineState' import { Entity } from '../../ecs/classes/Entity' import { ComponentType, getComponent, hasComponent } from '../../ecs/functions/ComponentFunctions' +import { EntityNetworkState } from '../../networking/state/EntityNetworkState' import { Physics } from '../../physics/classes/Physics' import { RigidBodyComponent } from '../../physics/components/RigidBodyComponent' import { CollisionGroups } from '../../physics/enums/CollisionGroups' import { SceneQueryType } from '../../physics/types/PhysicsTypes' +import { UUIDComponent } from '../../scene/components/UUIDComponent' import { TransformComponent } from '../../transform/components/TransformComponent' import { computeAndUpdateWorldOrigin, updateWorldOrigin } from '../../transform/updateWorldOrigin' import { getCameraMode, hasMovementControls, ReferenceSpace, XRState } from '../../xr/XRState' import { AvatarComponent } from '../components/AvatarComponent' import { AvatarControllerComponent } from '../components/AvatarControllerComponent' import { AvatarHeadDecapComponent } from '../components/AvatarIKComponents' -import { SpawnPoseComponent } from '../components/SpawnPoseComponent' import { AvatarMovementSettingsState } from '../state/AvatarMovementSettingsState' import { AutopilotMarker, clearWalkPoint, scaleFluctuate } from './autopilotFunctions' @@ -440,7 +441,9 @@ const _slerpBodyTowardsVelocity = (entity: Entity, alpha: number) => { let prevVector = prevVectors.get(entity)! if (!prevVector) { - prevVector = new Vector3(0, 0, 1).applyQuaternion(getComponent(entity, SpawnPoseComponent).rotation) + prevVector = new Vector3(0, 0, 1).applyQuaternion( + getState(EntityNetworkState)[getComponent(entity, UUIDComponent)].spawnRotation + ) prevVectors.set(entity, prevVector) } diff --git a/packages/engine/src/avatar/functions/respawnAvatar.ts b/packages/engine/src/avatar/functions/respawnAvatar.ts index 27e8378380..b6f306e409 100644 --- a/packages/engine/src/avatar/functions/respawnAvatar.ts +++ b/packages/engine/src/avatar/functions/respawnAvatar.ts @@ -23,15 +23,18 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { getState } from '@etherealengine/hyperflux' + import { Entity } from '../../ecs/classes/Entity' import { getComponent } from '../../ecs/functions/ComponentFunctions' +import { EntityNetworkState } from '../../networking/state/EntityNetworkState' +import { UUIDComponent } from '../../scene/components/UUIDComponent' import { AvatarControllerComponent } from '../components/AvatarControllerComponent' -import { SpawnPoseComponent } from '../components/SpawnPoseComponent' import { teleportAvatar } from './moveAvatar' export const respawnAvatar = (entity: Entity) => { - const { position } = getComponent(entity, SpawnPoseComponent) + const { spawnPosition } = getState(EntityNetworkState)[getComponent(entity, UUIDComponent)] const controller = getComponent(entity, AvatarControllerComponent) controller.verticalVelocity = 0 - teleportAvatar(entity, position) + teleportAvatar(entity, spawnPosition) } diff --git a/packages/engine/src/avatar/functions/spawnAvatarReceptor.test.ts b/packages/engine/src/avatar/functions/spawnAvatarReceptor.test.ts index 5a24f59433..764a94bace 100644 --- a/packages/engine/src/avatar/functions/spawnAvatarReceptor.test.ts +++ b/packages/engine/src/avatar/functions/spawnAvatarReceptor.test.ts @@ -29,13 +29,14 @@ import { Quaternion, Vector3 } from 'three' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { UserId } from '@etherealengine/common/src/interfaces/UserId' +import { applyIncomingActions, dispatchAction, receiveActions } from '@etherealengine/hyperflux' import { destroyEngine, Engine } from '../../ecs/classes/Engine' import { hasComponent } from '../../ecs/functions/ComponentFunctions' import { createEngine } from '../../initializeEngine' import { LocalInputTagComponent } from '../../input/components/LocalInputTagComponent' import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction' -import { WorldNetworkActionReceptor } from '../../networking/functions/WorldNetworkActionReceptor' +import { EntityNetworkState } from '../../networking/state/EntityNetworkState' import { Physics } from '../../physics/classes/Physics' import { RigidBodyComponent, @@ -46,7 +47,6 @@ import { TransformComponent } from '../../transform/components/TransformComponen import { AvatarAnimationComponent } from '../components/AvatarAnimationComponent' import { AvatarComponent } from '../components/AvatarComponent' import { AvatarControllerComponent } from '../components/AvatarControllerComponent' -import { SpawnPoseComponent } from '../components/SpawnPoseComponent' import { AvatarNetworkAction } from '../state/AvatarNetworkState' import { spawnAvatarReceptor } from './spawnAvatarReceptor' @@ -54,6 +54,7 @@ describe('spawnAvatarReceptor', () => { beforeEach(async () => { createEngine() await Physics.load() + Engine.instance.store.defaultDispatchDelay = () => 0 Engine.instance.physicsWorld = Physics.createWorld() Engine.instance.userId = 'user' as UserId Engine.instance.peerID = 'peerID' as PeerID @@ -65,13 +66,18 @@ describe('spawnAvatarReceptor', () => { it('check the create avatar function', () => { // mock entity to apply incoming unreliable updates to - const action = AvatarNetworkAction.spawn({ - $from: Engine.instance.userId, - position: new Vector3(), - rotation: new Quaternion(), - entityUUID: Engine.instance.userId as string as EntityUUID - }) - WorldNetworkActionReceptor.receiveSpawnObject(action as any) + dispatchAction( + AvatarNetworkAction.spawn({ + $from: Engine.instance.userId, + position: new Vector3(), + rotation: new Quaternion(), + entityUUID: Engine.instance.userId as string as EntityUUID + }) + ) + + applyIncomingActions() + receiveActions(EntityNetworkState) + spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) const entity = Engine.instance.getUserAvatarEntity(Engine.instance.userId) @@ -80,7 +86,6 @@ describe('spawnAvatarReceptor', () => { assert(hasComponent(entity, AvatarComponent)) assert(hasComponent(entity, NameComponent)) assert(hasComponent(entity, AvatarAnimationComponent)) - assert(hasComponent(entity, SpawnPoseComponent)) assert(hasComponent(entity, AvatarControllerComponent)) assert(hasComponent(entity, LocalInputTagComponent)) assert(hasComponent(entity, RigidBodyComponent)) diff --git a/packages/engine/src/avatar/functions/spawnAvatarReceptor.ts b/packages/engine/src/avatar/functions/spawnAvatarReceptor.ts index 6a1df35916..bf1fbb4655 100644 --- a/packages/engine/src/avatar/functions/spawnAvatarReceptor.ts +++ b/packages/engine/src/avatar/functions/spawnAvatarReceptor.ts @@ -65,7 +65,6 @@ import { AnimationComponent } from '../components/AnimationComponent' import { AvatarAnimationComponent } from '../components/AvatarAnimationComponent' import { AvatarComponent } from '../components/AvatarComponent' import { AvatarControllerComponent } from '../components/AvatarControllerComponent' -import { SpawnPoseComponent } from '../components/SpawnPoseComponent' export const avatarRadius = 0.25 export const defaultAvatarHeight = 1.8 @@ -121,11 +120,6 @@ export const spawnAvatarReceptor = (entityUUID: EntityUUID) => { locomotion: new Vector3() }) - addComponent(entity, SpawnPoseComponent, { - position: new Vector3().copy(transform.position), - rotation: new Quaternion().copy(transform.rotation) - }) - if (ownerID === Engine.instance.userId) { createAvatarController(entity) addComponent(entity, LocalInputTagComponent, true) diff --git a/packages/engine/src/avatar/state/AvatarNetworkState.tsx b/packages/engine/src/avatar/state/AvatarNetworkState.tsx index c9c979aa85..3bc1add016 100644 --- a/packages/engine/src/avatar/state/AvatarNetworkState.tsx +++ b/packages/engine/src/avatar/state/AvatarNetworkState.tsx @@ -107,20 +107,6 @@ export const AvatarState = defineState({ WorldNetworkAction.destroyObject, (state, action: typeof WorldNetworkAction.destroyObject.matches._TYPE) => { state[action.entityUUID].set(none) - /** @todo - This is a hack until all actions use event sourcing */ - - const cachedActionsToRemove = Engine.instance.store.actions.cached.filter( - (a) => - (AvatarNetworkAction.setAvatarID.matches.test(a) || - AvatarNetworkAction.setAnimationState.matches.test(a)) && - a.entityUUID === action.entityUUID - ) - - if (cachedActionsToRemove) { - cachedActionsToRemove.forEach((a) => - Engine.instance.store.actions.cached.splice(Engine.instance.store.actions.cached.indexOf(a), 1) - ) - } } ] ] diff --git a/packages/engine/src/avatar/systems/AvatarInputSystem.ts b/packages/engine/src/avatar/systems/AvatarInputSystem.ts index 8953b75e0c..f5bbb7ebad 100755 --- a/packages/engine/src/avatar/systems/AvatarInputSystem.ts +++ b/packages/engine/src/avatar/systems/AvatarInputSystem.ts @@ -148,14 +148,6 @@ const onInteract = (handedness: XRHandedness = 'none') => { ) } -const onKeyO = () => { - dispatchAction( - WorldNetworkAction.spawnDebugPhysicsObject({ - config: boxDynamicConfig - }) - ) -} - const onKeyP = () => { getMutableState(RendererState).debugEnable.set(!getMutableState(RendererState).debugEnable.value) } @@ -279,7 +271,6 @@ const execute = () => { const gamepadJump = standardGamepad && buttons[StandardGamepadButton.ButtonA]?.down if (isDev) { - if (buttons.KeyO?.down) onKeyO() if (buttons.KeyP?.down) onKeyP() } diff --git a/packages/engine/src/ecs/classes/EngineState.ts b/packages/engine/src/ecs/classes/EngineState.ts index 910b6b72d1..f37a147d99 100644 --- a/packages/engine/src/ecs/classes/EngineState.ts +++ b/packages/engine/src/ecs/classes/EngineState.ts @@ -53,7 +53,6 @@ export const EngineState = defineState({ leaveWorld: false, socketInstance: false, spectating: false, - usersTyping: {} as { [key: string]: true }, avatarLoadingEffect: true, /** * An empty share link will default to the current URL, plus any modifiers (such as spectate mode) diff --git a/packages/engine/src/ecs/functions/EntityTree.ts b/packages/engine/src/ecs/functions/EntityTree.ts index 671e2dbd7a..3ab1abd1b6 100644 --- a/packages/engine/src/ecs/functions/EntityTree.ts +++ b/packages/engine/src/ecs/functions/EntityTree.ts @@ -219,8 +219,9 @@ export function addEntityNodeChild(entity: Entity, parentEntity: Entity, uuid?: if (Engine.instance.worldNetwork?.isHosting) { const uuid = getComponent(entity, UUIDComponent) dispatchAction( - WorldNetworkAction.registerSceneObject({ - objectUuid: uuid + WorldNetworkAction.spawnObject({ + entityUUID: uuid, + prefab: '' }) ) } diff --git a/packages/engine/src/interaction/systems/EquippableSystem.test.ts b/packages/engine/src/interaction/systems/EquippableSystem.test.ts index b9b74ced3c..1203dc184c 100644 --- a/packages/engine/src/interaction/systems/EquippableSystem.test.ts +++ b/packages/engine/src/interaction/systems/EquippableSystem.test.ts @@ -29,6 +29,7 @@ import { Quaternion, Vector3 } from 'three' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import { NetworkId } from '@etherealengine/common/src/interfaces/NetworkId' import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' +import { applyIncomingActions, dispatchAction, receiveActions } from '@etherealengine/hyperflux' import { getHandTarget } from '../../avatar/components/AvatarIKComponents' import { spawnAvatarReceptor } from '../../avatar/functions/spawnAvatarReceptor' @@ -44,8 +45,7 @@ import { import { createEntity } from '../../ecs/functions/EntityFunctions' import { createEngine } from '../../initializeEngine' import { NetworkObjectComponent } from '../../networking/components/NetworkObjectComponent' -import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction' -import { WorldNetworkActionReceptor } from '../../networking/functions/WorldNetworkActionReceptor' +import { EntityNetworkState } from '../../networking/state/EntityNetworkState' import { Physics } from '../../physics/classes/Physics' import { setTransformComponent, TransformComponent } from '../../transform/components/TransformComponent' import { EquippedComponent } from '../components/EquippedComponent' @@ -58,6 +58,7 @@ describe.skip('EquippableSystem Integration Tests', () => { beforeEach(async () => { createEngine() await Physics.load() + Engine.instance.store.defaultDispatchDelay = () => 0 Engine.instance.physicsWorld = Physics.createWorld() }) @@ -76,15 +77,17 @@ describe.skip('EquippableSystem Integration Tests', () => { }) const networkObject = getComponent(player, NetworkObjectComponent) - const spawnAvatar = AvatarNetworkAction.spawn({ - $from: Engine.instance.userId, - networkId: networkObject.networkId, - position: new Vector3(-0.48624888685311896, 0, -0.12087574159728942), - rotation: new Quaternion(), - entityUUID: Engine.instance.userId as string as EntityUUID - }) - - WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar as any) + dispatchAction( + AvatarNetworkAction.spawn({ + $from: Engine.instance.userId, + networkId: networkObject.networkId, + position: new Vector3(-0.48624888685311896, 0, -0.12087574159728942), + rotation: new Quaternion(), + entityUUID: Engine.instance.userId as string as EntityUUID + }) + ) + applyIncomingActions() + receiveActions(EntityNetworkState) spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) diff --git a/packages/engine/src/interaction/systems/EquippableSystem.ts b/packages/engine/src/interaction/systems/EquippableSystem.ts index 598c7dce3c..37590fdbe8 100644 --- a/packages/engine/src/interaction/systems/EquippableSystem.ts +++ b/packages/engine/src/interaction/systems/EquippableSystem.ts @@ -100,6 +100,7 @@ export function setEquippedObjectReceptor(action: ReturnType ) { diff --git a/packages/engine/src/networking/components/NetworkObjectComponent.ts b/packages/engine/src/networking/components/NetworkObjectComponent.ts index 53c159dd8e..95e05021ae 100755 --- a/packages/engine/src/networking/components/NetworkObjectComponent.ts +++ b/packages/engine/src/networking/components/NetworkObjectComponent.ts @@ -24,12 +24,15 @@ Ethereal Engine. All Rights Reserved. */ import { Types } from 'bitecs' +import { useEffect } from 'react' import { NetworkId } from '@etherealengine/common/src/interfaces/NetworkId' import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { UserId } from '@etherealengine/common/src/interfaces/UserId' -import { defineComponent } from '../../ecs/functions/ComponentFunctions' +import { Engine } from '../../ecs/classes/Engine' +import { defineComponent, removeComponent, setComponent, useComponent } from '../../ecs/functions/ComponentFunctions' +import { useEntityContext } from '../../ecs/functions/EntityFunctions' export const NetworkObjectComponent = defineComponent({ name: 'NetworkObjectComponent', @@ -64,6 +67,24 @@ export const NetworkObjectComponent = defineComponent({ component.networkId.set(json.networkId) NetworkObjectComponent.networkId[entity] = json.networkId } + }, + + reactor: function () { + const entity = useEntityContext() + const networkObject = useComponent(entity, NetworkObjectComponent) + + useEffect(() => { + if (networkObject.authorityPeerID.value === Engine.instance.peerID) + setComponent(entity, NetworkObjectAuthorityTag) + else removeComponent(entity, NetworkObjectAuthorityTag) + }, [networkObject.authorityPeerID]) + + useEffect(() => { + if (networkObject.ownerId.value === Engine.instance.userId) setComponent(entity, NetworkObjectOwnedTag) + else removeComponent(entity, NetworkObjectOwnedTag) + }, [networkObject.ownerId]) + + return null } }) diff --git a/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts b/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts index 4c3d82e746..54aa43b81e 100644 --- a/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts +++ b/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts @@ -29,7 +29,7 @@ import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import { NetworkId } from '@etherealengine/common/src/interfaces/NetworkId' import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { UserId } from '@etherealengine/common/src/interfaces/UserId' -import { applyIncomingActions, getMutableState } from '@etherealengine/hyperflux' +import { applyIncomingActions, getMutableState, receiveActions } from '@etherealengine/hyperflux' import { createMockNetwork } from '../../../tests/util/createMockNetwork' import { destroyEngine, Engine } from '../../ecs/classes/Engine' @@ -42,13 +42,14 @@ import { Network } from '../classes/Network' import { NetworkObjectComponent } from '../components/NetworkObjectComponent' import { WorldState } from '../interfaces/WorldState' import { NetworkState } from '../NetworkState' -import { WorldNetworkActionSystem } from '../systems/WorldNetworkActionSystem' +import { EntityNetworkState } from '../state/EntityNetworkState' import { NetworkPeerFunctions } from './NetworkPeerFunctions' describe('NetworkPeerFunctions', () => { beforeEach(() => { createEngine() createMockNetwork() + Engine.instance.store.defaultDispatchDelay = () => 0 }) afterEach(() => { @@ -163,7 +164,7 @@ describe('NetworkPeerFunctions', () => { NetworkPeerFunctions.destroyPeer(network, peerID) applyIncomingActions() - SystemDefinitions.get(WorldNetworkActionSystem)!.execute() + receiveActions(EntityNetworkState) assert(!Engine.instance.getNetworkObject(userId, networkId)) }) diff --git a/packages/engine/src/networking/functions/WorldNetworkAction.ts b/packages/engine/src/networking/functions/WorldNetworkAction.ts index 8f8ff408d3..6b2c4497b5 100644 --- a/packages/engine/src/networking/functions/WorldNetworkAction.ts +++ b/packages/engine/src/networking/functions/WorldNetworkAction.ts @@ -39,20 +39,6 @@ import { Engine } from '../../ecs/classes/Engine' import { NetworkTopics } from '../classes/Network' export class WorldNetworkAction { - static spawnDebugPhysicsObject = defineAction({ - type: 'ee.engine.world.SPAWN_DEBUG_PHYSICS_OBJECT', - config: matches.any.optional(), - $topic: NetworkTopics.world - }) - - static registerSceneObject = defineAction({ - type: 'ee.engine.world.REGISTER_SCENE_OBJECT', - networkId: matchesWithDefault(matchesNetworkId, () => Engine.instance.createNetworkId()), - objectUuid: matchesEntityUUID, - $cache: true, - $topic: NetworkTopics.world - }) - static spawnObject = defineAction({ type: 'ee.engine.world.SPAWN_OBJECT', prefab: matches.string, @@ -73,13 +59,7 @@ export class WorldNetworkAction { static destroyObject = defineAction({ type: 'ee.engine.world.DESTROY_OBJECT', entityUUID: matchesEntityUUID, - $topic: NetworkTopics.world - }) - - static interact = defineAction({ - type: 'ee.engine.world.INTERACT', - object: { ownerId: matchesUserId, networkId: matchesNetworkId }, - parity: matches.literals('left', 'right', 'none'), + $cache: true, $topic: NetworkTopics.world }) @@ -110,10 +90,4 @@ export class WorldNetworkAction { newAuthority: matchesPeerID, $topic: NetworkTopics.world }) - - static setUserTyping = defineAction({ - type: 'ee.engine.world.USER_IS_TYPING', - typing: matches.boolean, - $topic: NetworkTopics.world - }) } diff --git a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.ts b/packages/engine/src/networking/functions/WorldNetworkActionReceptor.ts deleted file mode 100644 index 8227c5e92e..0000000000 --- a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { none } from '@hookstate/core' -import { Quaternion, Vector3 } from 'three' - -import { dispatchAction, getMutableState } from '@etherealengine/hyperflux' - -import { Engine } from '../../ecs/classes/Engine' -import { EngineState } from '../../ecs/classes/EngineState' -import { - getMutableComponent, - hasComponent, - removeComponent, - setComponent -} from '../../ecs/functions/ComponentFunctions' -import { createEntity, removeEntity } from '../../ecs/functions/EntityFunctions' -import { generatePhysicsObject } from '../../physics/functions/physicsObjectDebugFunctions' -import { UUIDComponent } from '../../scene/components/UUIDComponent' -import { TransformComponent } from '../../transform/components/TransformComponent' -import { - NetworkObjectAuthorityTag, - NetworkObjectComponent, - NetworkObjectOwnedTag -} from '../components/NetworkObjectComponent' -import { WorldNetworkAction } from './WorldNetworkAction' - -const receiveSpawnObject = (action: typeof WorldNetworkAction.spawnObject.matches._TYPE) => { - if (UUIDComponent.entitiesByUUID[action.entityUUID]) return - - console.log('Received spawn object', action) - - const entity = createEntity() - setComponent(entity, UUIDComponent, action.entityUUID) - - setComponent(entity, NetworkObjectComponent, { - ownerId: action.$from, - authorityPeerID: action.$peer, - networkId: action.networkId - }) - - const isAuthoritativePeer = action.$peer === Engine.instance.peerID - - if (isAuthoritativePeer) { - dispatchAction( - WorldNetworkAction.transferAuthorityOfObject({ - ownerId: action.$from, - networkId: action.networkId, - newAuthority: Engine.instance.peerID - }) - ) - } - - const isOwnedByMe = action.$from === Engine.instance.userId - if (isOwnedByMe) { - setComponent(entity, NetworkObjectOwnedTag) - } - - const position = new Vector3() - const rotation = new Quaternion() - - if (action.position) position.copy(action.position) - if (action.rotation) rotation.copy(action.rotation) - - setComponent(entity, TransformComponent, { position, rotation }) -} - -const receiveRegisterSceneObject = (action: typeof WorldNetworkAction.registerSceneObject.matches._TYPE) => { - const entity = UUIDComponent.entitiesByUUID[action.objectUuid] - - if (!entity) return console.warn('[WorldNetworkAction] Tried to register a scene entity that does not exist', action) - - setComponent(entity, NetworkObjectComponent, { - ownerId: action.$from, - authorityPeerID: action.$peer ?? Engine.instance.peerID, - networkId: action.networkId - }) - - const isOwnedByMe = action.$from === Engine.instance.userId - if (isOwnedByMe) { - setComponent(entity, NetworkObjectOwnedTag) - setComponent(entity, NetworkObjectAuthorityTag) - } else { - if (hasComponent(entity, NetworkObjectOwnedTag)) removeComponent(entity, NetworkObjectOwnedTag) - if (hasComponent(entity, NetworkObjectAuthorityTag)) removeComponent(entity, NetworkObjectAuthorityTag) - } -} - -const receiveSpawnDebugPhysicsObject = (action: typeof WorldNetworkAction.spawnDebugPhysicsObject.matches._TYPE) => { - generatePhysicsObject(action.config, action.config.spawnPosition, true, action.config.spawnScale) -} - -const receiveDestroyObject = (action: ReturnType) => { - const entity = UUIDComponent.entitiesByUUID[action.entityUUID] - if (!entity) return - removeEntity(entity) - const idx = Engine.instance.store.actions.cached.findIndex( - (a) => WorldNetworkAction.spawnObject.matches.test(a) && a.entityUUID === action.entityUUID - ) - if (idx !== -1) Engine.instance.store.actions.cached.splice(idx, 1) -} - -const receiveRequestAuthorityOverObject = ( - action: typeof WorldNetworkAction.requestAuthorityOverObject.matches._TYPE -) => { - console.log('receiveRequestAuthorityOverObject', action, Engine.instance.userId) - // Authority request can only be processed by owner - if (Engine.instance.userId !== action.ownerId) return - - const ownerId = action.ownerId - const entity = Engine.instance.getNetworkObject(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` - ) - - /** - * Custom logic for disallowing transfer goes here - */ - - dispatchAction( - WorldNetworkAction.transferAuthorityOfObject({ - ownerId: action.ownerId, - networkId: action.networkId, - newAuthority: action.newAuthority - }) - ) -} - -const receiveTransferAuthorityOfObject = ( - action: typeof WorldNetworkAction.transferAuthorityOfObject.matches._TYPE -) => { - console.log('receiveTransferAuthorityOfObject', action) - // Authority request can only be processed by owner - if (action.$from !== action.ownerId) return - - const ownerId = action.ownerId - const entity = Engine.instance.getNetworkObject(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) - - if (Engine.instance.peerID === action.newAuthority) { - if (hasComponent(entity, NetworkObjectAuthorityTag)) - return console.warn(`Warning - User ${Engine.instance.userId} already has authority over entity ${entity}.`) - - setComponent(entity, NetworkObjectAuthorityTag) - } else { - if (hasComponent(entity, NetworkObjectAuthorityTag)) removeComponent(entity, NetworkObjectAuthorityTag) - } -} - -const receiveSetUserTyping = (action: typeof WorldNetworkAction.setUserTyping.matches._TYPE) => { - getMutableState(EngineState).usersTyping[action.$from].set(action.typing ? true : none) -} - -export const WorldNetworkActionReceptor = { - receiveSpawnObject, - receiveRegisterSceneObject, - receiveSpawnDebugPhysicsObject, - receiveDestroyObject, - receiveRequestAuthorityOverObject, - receiveTransferAuthorityOfObject, - receiveSetUserTyping -} diff --git a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.test.ts b/packages/engine/src/networking/state/EntityNetworkState.test.ts similarity index 86% rename from packages/engine/src/networking/functions/WorldNetworkActionReceptor.test.ts rename to packages/engine/src/networking/state/EntityNetworkState.test.ts index ec33dfd930..70f1b9a1c8 100644 --- a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.test.ts +++ b/packages/engine/src/networking/state/EntityNetworkState.test.ts @@ -23,6 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { act } from '@testing-library/react' import assert from 'assert' import { Quaternion, Vector3 } from 'three' @@ -30,32 +31,40 @@ import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import { NetworkId } from '@etherealengine/common/src/interfaces/NetworkId' import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { UserId } from '@etherealengine/common/src/interfaces/UserId' +import { ReactorRoot, receiveActions } from '@etherealengine/hyperflux' import * as ActionFunctions from '@etherealengine/hyperflux/functions/ActionFunctions' +import { applyIncomingActions, dispatchAction } from '@etherealengine/hyperflux/functions/ActionFunctions' import { createMockNetwork } from '../../../tests/util/createMockNetwork' -import { AvatarComponent } from '../../avatar/components/AvatarComponent' import { spawnAvatarReceptor } from '../../avatar/functions/spawnAvatarReceptor' import { AvatarNetworkAction } from '../../avatar/state/AvatarNetworkState' import { destroyEngine, Engine } from '../../ecs/classes/Engine' import { defineQuery, getComponent, hasComponent } from '../../ecs/functions/ComponentFunctions' -import { SystemDefinitions } from '../../ecs/functions/SystemFunctions' +import { SimulationSystemGroup } from '../../ecs/functions/EngineFunctions' +import { startSystem, SystemDefinitions } from '../../ecs/functions/SystemFunctions' import { createEngine } from '../../initializeEngine' import { Physics } from '../../physics/classes/Physics' +import { UUIDComponent } from '../../scene/components/UUIDComponent' import { Network, NetworkTopics } from '../classes/Network' import { NetworkObjectComponent } from '../components/NetworkObjectComponent' import { NetworkObjectOwnedTag } from '../components/NetworkObjectComponent' -import { WorldNetworkActionSystem } from '../systems/WorldNetworkActionSystem' -import { NetworkPeerFunctions } from './NetworkPeerFunctions' -import { WorldNetworkAction } from './WorldNetworkAction' -import { WorldNetworkActionReceptor } from './WorldNetworkActionReceptor' - -describe('WorldNetworkActionReceptors', () => { +import { NetworkPeerFunctions } from '../functions/NetworkPeerFunctions' +import { WorldNetworkAction } from '../functions/WorldNetworkAction' +import { + EntityNetworkState, + EntityNetworkStateSystem, + receiveRequestAuthorityOverObject, + receiveTransferAuthorityOfObject +} from './EntityNetworkState' + +describe('EntityNetworkState', () => { beforeEach(async () => { createEngine() createMockNetwork() await Physics.load() Engine.instance.physicsWorld = Physics.createWorld() Engine.instance.store.defaultDispatchDelay = () => 0 + startSystem(EntityNetworkStateSystem, { with: SimulationSystemGroup }) }) afterEach(() => { @@ -79,7 +88,7 @@ describe('WorldNetworkActionReceptors', () => { const objNetId = 3 as NetworkId const objPrefab = 'generic prefab' - WorldNetworkActionReceptor.receiveSpawnObject( + dispatchAction( WorldNetworkAction.spawnObject({ $from: Engine.instance.worldNetwork.hostId, // from host prefab: objPrefab, // generic prefab @@ -90,6 +99,9 @@ describe('WorldNetworkActionReceptors', () => { }) ) + applyIncomingActions() + receiveActions(EntityNetworkState) + const networkObjectQuery = defineQuery([NetworkObjectComponent]) const networkObjectOwnedQuery = defineQuery([NetworkObjectOwnedTag]) @@ -104,7 +116,7 @@ describe('WorldNetworkActionReceptors', () => { assert.equal(hasComponent(networkObjectEntities[0], NetworkObjectOwnedTag), false) }) - it('should spawn object owned by user', () => { + it('should spawn object owned by user', async () => { const userId = 'user id' as UserId const hostId = 'host' as UserId const peerID = 'peer id' as PeerID @@ -122,7 +134,7 @@ describe('WorldNetworkActionReceptors', () => { const objNetId = 3 as NetworkId const objPrefab = 'generic prefab' - WorldNetworkActionReceptor.receiveSpawnObject( + dispatchAction( WorldNetworkAction.spawnObject({ $from: userId, // from user prefab: objPrefab, // generic prefab @@ -131,6 +143,9 @@ describe('WorldNetworkActionReceptors', () => { entityUUID: Engine.instance.peerID as any as EntityUUID }) ) + applyIncomingActions() + + await act(() => receiveActions(EntityNetworkState)) const networkObjectQuery = defineQuery([NetworkObjectComponent]) const networkObjectOwnedQuery = defineQuery([NetworkObjectOwnedTag]) @@ -165,7 +180,7 @@ describe('WorldNetworkActionReceptors', () => { const objNetId = 3 as NetworkId const objPrefab = 'avatar' - WorldNetworkActionReceptor.receiveSpawnObject( + dispatchAction( WorldNetworkAction.spawnObject({ $from: userId2, // from other user prefab: objPrefab, // generic prefab @@ -175,8 +190,8 @@ describe('WorldNetworkActionReceptors', () => { entityUUID: peerID3 as any as EntityUUID }) ) - - SystemDefinitions.get(WorldNetworkActionSystem)!.execute() + applyIncomingActions() + await act(() => receiveActions(EntityNetworkState)) const networkObjectQuery = defineQuery([NetworkObjectComponent]) const networkObjectOwnedQuery = defineQuery([NetworkObjectOwnedTag]) @@ -202,15 +217,19 @@ describe('WorldNetworkActionReceptors', () => { NetworkPeerFunctions.createPeer(network, peerID, 1, userId, 1, 'user name') - const action = AvatarNetworkAction.spawn({ - networkId: 42 as NetworkId, - $peer: peerID, - entityUUID: Engine.instance.userId as string as EntityUUID - }) - WorldNetworkActionReceptor.receiveSpawnObject(action as any) - spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) + dispatchAction( + AvatarNetworkAction.spawn({ + networkId: 42 as NetworkId, + $peer: peerID, + entityUUID: Engine.instance.userId as string as EntityUUID + }) + ) + applyIncomingActions() + await act(() => receiveActions(EntityNetworkState)) - const entity = Engine.instance.getOwnedNetworkObjectWithComponent(userId, AvatarComponent) + const entity = UUIDComponent.entitiesByUUID[Engine.instance.userId as any as EntityUUID] + + spawnAvatarReceptor(Engine.instance.userId as string as EntityUUID) assert.equal(getComponent(entity, NetworkObjectComponent).networkId, 42) assert.equal(getComponent(entity, NetworkObjectComponent).authorityPeerID, peerID) @@ -239,7 +258,7 @@ describe('WorldNetworkActionReceptors', () => { const objNetId = 3 as NetworkId const objPrefab = 'generic prefab' - WorldNetworkActionReceptor.receiveSpawnObject( + dispatchAction( WorldNetworkAction.spawnObject({ $from: userId, prefab: objPrefab, @@ -249,6 +268,8 @@ describe('WorldNetworkActionReceptors', () => { entityUUID: Engine.instance.peerID as any as EntityUUID }) ) + applyIncomingActions() + await act(() => receiveActions(EntityNetworkState)) const networkObjectQuery = defineQuery([NetworkObjectComponent]) const networkObjectOwnedQuery = defineQuery([NetworkObjectOwnedTag]) @@ -267,7 +288,7 @@ describe('WorldNetworkActionReceptors', () => { WorldNetworkAction.transferAuthorityOfObject.matches ) - WorldNetworkActionReceptor.receiveRequestAuthorityOverObject( + receiveRequestAuthorityOverObject( WorldNetworkAction.requestAuthorityOverObject({ $from: userId, ownerId: userId, @@ -279,8 +300,7 @@ describe('WorldNetworkActionReceptors', () => { ActionFunctions.applyIncomingActions() - for (const action of transferAuthorityOfObjectQueue()) - WorldNetworkActionReceptor.receiveTransferAuthorityOfObject(action) + for (const action of transferAuthorityOfObjectQueue()) receiveTransferAuthorityOfObject(action) const networkObjectEntitiesAfter = networkObjectQuery() const networkObjectOwnedEntitiesAfter = networkObjectOwnedQuery() @@ -312,7 +332,7 @@ describe('WorldNetworkActionReceptors', () => { const objNetId = 3 as NetworkId const objPrefab = 'generic prefab' - WorldNetworkActionReceptor.receiveSpawnObject( + dispatchAction( WorldNetworkAction.spawnObject({ $from: hostUserId, // from host prefab: objPrefab, // generic prefab @@ -323,6 +343,9 @@ describe('WorldNetworkActionReceptors', () => { }) ) + applyIncomingActions() + await act(() => receiveActions(EntityNetworkState)) + const networkObjectQuery = defineQuery([NetworkObjectComponent]) const networkObjectOwnedQuery = defineQuery([NetworkObjectOwnedTag]) @@ -340,7 +363,7 @@ describe('WorldNetworkActionReceptors', () => { WorldNetworkAction.transferAuthorityOfObject.matches ) - WorldNetworkActionReceptor.receiveRequestAuthorityOverObject( + receiveRequestAuthorityOverObject( WorldNetworkAction.requestAuthorityOverObject({ $from: userId, // from user ownerId: hostUserId, @@ -350,10 +373,10 @@ describe('WorldNetworkActionReceptors', () => { }) ) - ActionFunctions.applyIncomingActions() + applyIncomingActions() + await act(() => receiveActions(EntityNetworkState)) - for (const action of transferAuthorityOfObjectQueue()) - WorldNetworkActionReceptor.receiveTransferAuthorityOfObject(action) + for (const action of transferAuthorityOfObjectQueue()) receiveTransferAuthorityOfObject(action) const networkObjectEntitiesAfter = networkObjectQuery() const networkObjectOwnedEntitiesAfter = networkObjectOwnedQuery() diff --git a/packages/engine/src/networking/state/EntityNetworkState.tsx b/packages/engine/src/networking/state/EntityNetworkState.tsx new file mode 100644 index 0000000000..47d4bda90e --- /dev/null +++ b/packages/engine/src/networking/state/EntityNetworkState.tsx @@ -0,0 +1,147 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Quaternion, Vector3 } from 'three' + +import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' +import { NetworkId } from '@etherealengine/common/src/interfaces/NetworkId' +import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' +import { UserId } from '@etherealengine/common/src/interfaces/UserId' +import { defineActionQueue, defineState, dispatchAction, none, receiveActions } from '@etherealengine/hyperflux' + +import { Engine } from '../../ecs/classes/Engine' +import { getMutableComponent, setComponent } from '../../ecs/functions/ComponentFunctions' +import { createEntity, removeEntity } from '../../ecs/functions/EntityFunctions' +import { defineSystem } from '../../ecs/functions/SystemFunctions' +import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction' +import { UUIDComponent } from '../../scene/components/UUIDComponent' +import { TransformComponent } from '../../transform/components/TransformComponent' +import { NetworkObjectComponent } from '../components/NetworkObjectComponent' + +export const EntityNetworkState = defineState({ + name: 'ee.engine.avatar.EntityNetworkState', + + initial: {} as Record< + EntityUUID, + { + ownerId: UserId + networkId: NetworkId + peerId: PeerID + prefab: string + spawnPosition: Vector3 + spawnRotation: Quaternion + } + >, + + receptors: [ + [ + WorldNetworkAction.spawnObject, + (state, action: typeof WorldNetworkAction.spawnObject.matches._TYPE) => { + const entity = UUIDComponent.entitiesByUUID[action.entityUUID] ?? createEntity() + setComponent(entity, UUIDComponent, action.entityUUID) + setComponent(entity, NetworkObjectComponent, { + ownerId: action.$from, + authorityPeerID: action.$peer, + networkId: action.networkId + }) + setComponent(entity, TransformComponent, { position: action.position!, rotation: action.rotation! }) + + state[action.entityUUID].merge({ + ownerId: action.$from, + networkId: action.networkId, + peerId: action.$peer, + prefab: action.prefab, + spawnPosition: action.position ?? new Vector3(), + spawnRotation: action.rotation ?? new Quaternion() + }) + } + ], + + [ + WorldNetworkAction.destroyObject, + (state, action: typeof WorldNetworkAction.destroyObject.matches._TYPE) => { + state[action.entityUUID].set(none) + const entity = UUIDComponent.entitiesByUUID[action.entityUUID] + if (!entity) return + removeEntity(entity) + } + ] + ] +}) + +export const receiveRequestAuthorityOverObject = ( + action: typeof WorldNetworkAction.requestAuthorityOverObject.matches._TYPE +) => { + // Authority request can only be processed by owner + if (Engine.instance.userId !== action.ownerId) return + + const ownerId = action.ownerId + const entity = Engine.instance.getNetworkObject(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` + ) + + /** + * Custom logic for disallowing transfer goes here + */ + dispatchAction( + WorldNetworkAction.transferAuthorityOfObject({ + ownerId: action.ownerId, + networkId: action.networkId, + newAuthority: action.newAuthority + }) + ) +} + +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 = Engine.instance.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({ + uuid: 'ee.engine.avatar.EntityNetworkStateSystem', + execute +}) diff --git a/packages/engine/src/networking/systems/WorldNetworkActionSystem.ts b/packages/engine/src/networking/systems/WorldNetworkActionSystem.ts deleted file mode 100644 index 2fdcdb9845..0000000000 --- a/packages/engine/src/networking/systems/WorldNetworkActionSystem.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { useEffect } from 'react' - -import { defineActionQueue, removeActionQueue } from '@etherealengine/hyperflux' - -import { defineSystem } from '../../ecs/functions/SystemFunctions' -import { WorldNetworkAction } from '../functions/WorldNetworkAction' -import { WorldNetworkActionReceptor } from '../functions/WorldNetworkActionReceptor' - -const spawnObjectQueue = defineActionQueue(WorldNetworkAction.spawnObject.matches) -const registerSceneObjectQueue = defineActionQueue(WorldNetworkAction.registerSceneObject.matches) -const spawnDebugPhysicsObjectQueue = defineActionQueue(WorldNetworkAction.spawnDebugPhysicsObject.matches) -const destroyObjectQueue = defineActionQueue(WorldNetworkAction.destroyObject.matches) -const requestAuthorityOverObjectQueue = defineActionQueue(WorldNetworkAction.requestAuthorityOverObject.matches) -const transferAuthorityOfObjectQueue = defineActionQueue(WorldNetworkAction.transferAuthorityOfObject.matches) -const setUserTypingQueue = defineActionQueue(WorldNetworkAction.setUserTyping.matches) - -const execute = () => { - for (const action of spawnObjectQueue()) { - console.log(action) - WorldNetworkActionReceptor.receiveSpawnObject(action) - } - for (const action of registerSceneObjectQueue()) WorldNetworkActionReceptor.receiveRegisterSceneObject(action) - for (const action of spawnDebugPhysicsObjectQueue()) WorldNetworkActionReceptor.receiveSpawnDebugPhysicsObject(action) - for (const action of destroyObjectQueue()) WorldNetworkActionReceptor.receiveDestroyObject(action) - for (const action of requestAuthorityOverObjectQueue()) - WorldNetworkActionReceptor.receiveRequestAuthorityOverObject(action) - for (const action of transferAuthorityOfObjectQueue()) - WorldNetworkActionReceptor.receiveTransferAuthorityOfObject(action) - for (const action of setUserTypingQueue()) WorldNetworkActionReceptor.receiveSetUserTyping(action) -} - -export const WorldNetworkActionSystem = defineSystem({ - uuid: 'ee.engine.WorldNetworkActionSystem', - execute -}) diff --git a/packages/engine/src/renderer/RendererState.ts b/packages/engine/src/renderer/RendererState.ts index ca2a4ad940..342d6fdaad 100644 --- a/packages/engine/src/renderer/RendererState.ts +++ b/packages/engine/src/renderer/RendererState.ts @@ -43,7 +43,8 @@ export const RendererState = defineState({ nodeHelperVisibility: false, gridVisibility: false, gridHeight: 0, - forceBasicMaterials: false + forceBasicMaterials: false, + shadowMapResolution: 256 }), onCreate: (store, state) => { syncStateWithLocalStorage(RendererState, [ diff --git a/packages/engine/src/renderer/functions/RenderSettingsFunction.ts b/packages/engine/src/renderer/functions/RenderSettingsFunction.ts index 1c42e1813e..6af55db179 100644 --- a/packages/engine/src/renderer/functions/RenderSettingsFunction.ts +++ b/packages/engine/src/renderer/functions/RenderSettingsFunction.ts @@ -29,6 +29,7 @@ import { getMutableState, getState, useHookstate } from '@etherealengine/hyperfl import { iOS } from '../../common/functions/isMobile' import { Engine } from '../../ecs/classes/Engine' +import { EngineState } from '../../ecs/classes/EngineState' import { RendererState } from '../../renderer/RendererState' import { isMobileXRHeadset } from '../../xr/XRState' import { RenderModes } from '../constants/RenderModes' @@ -36,14 +37,21 @@ import { EngineRenderer, RenderSettingsState } from '../WebGLRendererSystem' export const getShadowsEnabled = () => { const rendererState = getState(RendererState) - return !isMobileXRHeadset && !iOS && rendererState.useShadows && rendererState.renderMode === RenderModes.SHADOW + const isEditor = getState(EngineState).isEditor + return ( + !isMobileXRHeadset && + !iOS && + rendererState.useShadows && + (isEditor ? rendererState.renderMode === RenderModes.SHADOW : true) + ) } export const useShadowsEnabled = () => { const rendererState = getMutableState(RendererState) const useShadows = useHookstate(rendererState.useShadows).value const renderMode = useHookstate(rendererState.renderMode).value - return !isMobileXRHeadset && !iOS && useShadows && renderMode === RenderModes.SHADOW + const isEditor = useHookstate(getMutableState(EngineState).isEditor).value + return !isMobileXRHeadset && !iOS && useShadows && (isEditor ? renderMode === RenderModes.SHADOW : true) } export const updateShadowMap = () => { diff --git a/packages/engine/src/scene/components/DirectionalLightComponent.ts b/packages/engine/src/scene/components/DirectionalLightComponent.ts index 6e458f517a..116d2b0bd8 100644 --- a/packages/engine/src/scene/components/DirectionalLightComponent.ts +++ b/packages/engine/src/scene/components/DirectionalLightComponent.ts @@ -52,7 +52,6 @@ export const DirectionalLightComponent = defineComponent({ color: new Color(), intensity: 1, castShadow: false, - shadowMapResolution: 512, shadowBias: -0.00001, shadowRadius: 1, cameraFar: 2000, @@ -69,9 +68,6 @@ export const DirectionalLightComponent = defineComponent({ if (matches.number.test(json.cameraFar)) component.cameraFar.set(json.cameraFar) if (matches.boolean.test(json.castShadow)) component.castShadow.set(json.castShadow) /** backwards compat */ - if (matches.array.test(json.shadowMapResolution)) - component.shadowMapResolution.set((json.shadowMapResolution as any)[0]) - if (matches.number.test(json.shadowMapResolution)) component.shadowMapResolution.set(json.shadowMapResolution) if (matches.number.test(json.shadowBias)) component.shadowBias.set(json.shadowBias) if (matches.number.test(json.shadowRadius)) component.shadowRadius.set(json.shadowRadius) if (matches.number.test(json.useInCSM)) component.useInCSM.set(json.useInCSM) @@ -86,7 +82,6 @@ export const DirectionalLightComponent = defineComponent({ component.light.value.shadow.camera.far = component.cameraFar.value component.light.value.shadow.bias = component.shadowBias.value component.light.value.shadow.radius = component.shadowRadius.value - component.light.value.shadow.mapSize.set(component.shadowMapResolution.value, component.shadowMapResolution.value) }, toJSON: (entity, component) => { @@ -95,7 +90,6 @@ export const DirectionalLightComponent = defineComponent({ intensity: component.intensity.value, cameraFar: component.cameraFar.value, castShadow: component.castShadow.value, - shadowMapResolution: component.shadowMapResolution.value, shadowBias: component.shadowBias.value, shadowRadius: component.shadowRadius.value, useInCSM: component.useInCSM.value @@ -109,7 +103,8 @@ export const DirectionalLightComponent = defineComponent({ reactor: function () { const entity = useEntityContext() - const debugEnabled = useHookstate(getMutableState(RendererState).nodeHelperVisibility) + const renderState = useHookstate(getMutableState(RendererState)) + const debugEnabled = renderState.nodeHelperVisibility const light = useComponent(entity, DirectionalLightComponent) useEffect(() => { @@ -137,14 +132,17 @@ export const DirectionalLightComponent = defineComponent({ }, [light.shadowRadius]) useEffect(() => { - if (light.light.value.shadow.mapSize.x !== light.shadowMapResolution.value) { - light.light.value.shadow.mapSize.set(light.shadowMapResolution.value, light.shadowMapResolution.value) + if (light.light.value.shadow.mapSize.x !== renderState.shadowMapResolution.value) { + light.light.value.shadow.mapSize.set( + renderState.shadowMapResolution.value, + renderState.shadowMapResolution.value + ) light.light.value.shadow.map?.dispose() light.light.value.shadow.map = null as any light.light.value.shadow.camera.updateProjectionMatrix() light.light.value.shadow.needsUpdate = true } - }, [light.shadowMapResolution]) + }, [renderState.shadowMapResolution]) useEffect(() => { if (debugEnabled.value && !light.helper.value) { diff --git a/packages/engine/src/scene/components/PointLightComponent.ts b/packages/engine/src/scene/components/PointLightComponent.ts index c14d2b2d6d..97556772bb 100644 --- a/packages/engine/src/scene/components/PointLightComponent.ts +++ b/packages/engine/src/scene/components/PointLightComponent.ts @@ -50,7 +50,6 @@ export const PointLightComponent = defineComponent({ range: 0, decay: 2, castShadow: false, - shadowMapResolution: 256, shadowBias: 0.5, shadowRadius: 1, light, @@ -67,9 +66,6 @@ export const PointLightComponent = defineComponent({ if (matches.number.test(json.decay)) component.decay.set(json.decay) if (matches.boolean.test(json.castShadow)) component.castShadow.set(json.castShadow) /** backwards compat */ - if (matches.array.test(json.shadowMapResolution)) - component.shadowMapResolution.set((json.shadowMapResolution as any)[0]) - if (matches.number.test(json.shadowMapResolution)) component.shadowMapResolution.set(json.shadowMapResolution) if (matches.number.test(json.shadowBias)) component.shadowBias.set(json.shadowBias) if (matches.number.test(json.shadowRadius)) component.shadowRadius.set(json.shadowRadius) }, @@ -81,7 +77,6 @@ export const PointLightComponent = defineComponent({ range: component.range.value, decay: component.decay.value, castShadow: component.castShadow.value, - shadowMapResolution: component.shadowMapResolution.value, shadowBias: component.shadowBias.value, shadowRadius: component.shadowRadius.value } @@ -94,7 +89,8 @@ export const PointLightComponent = defineComponent({ reactor: function () { const entity = useEntityContext() - const debugEnabled = useHookstate(getMutableState(RendererState).nodeHelperVisibility) + const renderState = useHookstate(getMutableState(RendererState)) + const debugEnabled = renderState.nodeHelperVisibility const light = useComponent(entity, PointLightComponent) useEffect(() => { @@ -126,14 +122,17 @@ export const PointLightComponent = defineComponent({ }, [light.shadowRadius]) useEffect(() => { - if (light.light.value.shadow.mapSize.x !== light.shadowMapResolution.value) { - light.light.value.shadow.mapSize.set(light.shadowMapResolution.value, light.shadowMapResolution.value) + if (light.light.value.shadow.mapSize.x !== renderState.shadowMapResolution.value) { + light.light.value.shadow.mapSize.set( + renderState.shadowMapResolution.value, + renderState.shadowMapResolution.value + ) light.light.value.shadow.map?.dispose() light.light.value.shadow.map = null as any light.light.value.shadow.camera.updateProjectionMatrix() light.light.value.shadow.needsUpdate = true } - }, [light.shadowMapResolution]) + }, [renderState.shadowMapResolution]) useEffect(() => { if (debugEnabled.value && !light.helper.value) { diff --git a/packages/engine/src/scene/components/SpotLightComponent.ts b/packages/engine/src/scene/components/SpotLightComponent.ts index b62c931f5e..8f24c9fcdb 100644 --- a/packages/engine/src/scene/components/SpotLightComponent.ts +++ b/packages/engine/src/scene/components/SpotLightComponent.ts @@ -55,7 +55,6 @@ export const SpotLightComponent = defineComponent({ angle: Math.PI / 3, penumbra: 1, castShadow: false, - shadowMapResolution: 256, shadowBias: 0.00001, shadowRadius: 1, light, @@ -76,9 +75,6 @@ export const SpotLightComponent = defineComponent({ if (matches.number.test(json.penumbra)) component.angle.set(json.penumbra) if (matches.boolean.test(json.castShadow)) component.castShadow.set(json.castShadow) /** backwards compat */ - if (matches.array.test(json.shadowMapResolution)) - component.shadowMapResolution.set((json.shadowMapResolution as any)[0]) - if (matches.number.test(json.shadowMapResolution)) component.shadowMapResolution.set(json.shadowMapResolution) if (matches.number.test(json.shadowBias)) component.shadowBias.set(json.shadowBias) if (matches.number.test(json.shadowRadius)) component.shadowRadius.set(json.shadowRadius) }, @@ -92,7 +88,6 @@ export const SpotLightComponent = defineComponent({ angle: component.angle.value, penumbra: component.penumbra.value, castShadow: component.castShadow.value, - shadowMapResolution: component.shadowMapResolution.value, shadowBias: component.shadowBias.value, shadowRadius: component.shadowRadius.value } @@ -105,7 +100,8 @@ export const SpotLightComponent = defineComponent({ reactor: function () { const entity = useEntityContext() - const debugEnabled = useHookstate(getMutableState(RendererState).nodeHelperVisibility) + const renderState = useHookstate(getMutableState(RendererState)) + const debugEnabled = renderState.nodeHelperVisibility const light = useComponent(entity, SpotLightComponent) useEffect(() => { @@ -147,14 +143,17 @@ export const SpotLightComponent = defineComponent({ }, [light.castShadow]) useEffect(() => { - if (light.light.value.shadow.mapSize.x !== light.shadowMapResolution.value) { - light.light.value.shadow.mapSize.set(light.shadowMapResolution.value, light.shadowMapResolution.value) + if (light.light.value.shadow.mapSize.x !== renderState.shadowMapResolution.value) { + light.light.value.shadow.mapSize.set( + renderState.shadowMapResolution.value, + renderState.shadowMapResolution.value + ) light.light.value.shadow.map?.dispose() light.light.value.shadow.map = null as any light.light.value.shadow.camera.updateProjectionMatrix() light.light.value.shadow.needsUpdate = true } - }, [light.shadowMapResolution]) + }, [renderState.shadowMapResolution]) useEffect(() => { if (debugEnabled.value && !light.helper.value) { diff --git a/packages/engine/src/scene/functions/loaders/PortalFunctions.ts b/packages/engine/src/scene/functions/loaders/PortalFunctions.ts index 6d8faaa9be..0aa2601286 100644 --- a/packages/engine/src/scene/functions/loaders/PortalFunctions.ts +++ b/packages/engine/src/scene/functions/loaders/PortalFunctions.ts @@ -27,7 +27,6 @@ import { dispatchAction, getState } from '@etherealengine/hyperflux' import { AvatarStates } from '../../../avatar/animation/Util' import { AvatarControllerComponent } from '../../../avatar/components/AvatarControllerComponent' -import { SpawnPoseComponent } from '../../../avatar/components/SpawnPoseComponent' import { teleportAvatar } from '../../../avatar/functions/moveAvatar' import { switchCameraMode } from '../../../avatar/functions/switchCameraMode' import { AvatarNetworkAction } from '../../../avatar/state/AvatarNetworkState' @@ -36,6 +35,7 @@ import { Engine } from '../../../ecs/classes/Engine' import { EngineActions, EngineState } from '../../../ecs/classes/EngineState' import { Entity } from '../../../ecs/classes/Entity' import { getComponent } from '../../../ecs/functions/ComponentFunctions' +import { EntityNetworkState } from '../../../networking/state/EntityNetworkState' import { PortalComponent } from '../../components/PortalComponent' import { UUIDComponent } from '../../components/UUIDComponent' @@ -52,7 +52,9 @@ export const setAvatarToLocationTeleportingState = () => { export const revertAvatarToMovingStateFromTeleport = () => { const localClientEntity = Engine.instance.localClientEntity - getComponent(localClientEntity, SpawnPoseComponent).position.copy(Engine.instance.activePortal!.remoteSpawnPosition) + getState(EntityNetworkState)[getComponent(localClientEntity, UUIDComponent)].spawnPosition.copy( + Engine.instance.activePortal!.remoteSpawnPosition + ) getComponent(localClientEntity, AvatarControllerComponent).movementEnabled = true // teleport player to where the portal spawn position is diff --git a/packages/engine/tests/mocha.env.js b/packages/engine/tests/mocha.env.js index a1f41cc03e..2e15c4954c 100644 --- a/packages/engine/tests/mocha.env.js +++ b/packages/engine/tests/mocha.env.js @@ -26,6 +26,7 @@ Ethereal Engine. All Rights Reserved. process.env.APP_ENV = 'test' +process.env.NODE_ENV = 'test' process.env.TS_NODE_FILES = true process.env.TS_NODE_PROJECT = 'tsconfig.json' process.env.TS_NODE_COMPILER_OPTIONS = '{\"module\": \"commonjs\" }' diff --git a/packages/hyperflux/functions/ReactorFunctions.tsx b/packages/hyperflux/functions/ReactorFunctions.tsx index 8579494f45..02d84a2673 100644 --- a/packages/hyperflux/functions/ReactorFunctions.tsx +++ b/packages/hyperflux/functions/ReactorFunctions.tsx @@ -32,6 +32,7 @@ import { isDev } from '@etherealengine/common/src/config' import { HyperFlux } from './StoreFunctions' const ReactorReconciler = Reconciler({ + warnsIfNotActing: true, getPublicInstance: (instance) => instance, getRootHostContext: () => null, getChildHostContext: (parentHostContext) => parentHostContext, @@ -79,7 +80,7 @@ export interface ReactorRoot { isRunning: boolean promise: Promise cleanupFunctions: Set<() => void> - run: () => Promise + run: (force?: boolean) => Promise stop: () => Promise } diff --git a/packages/hyperflux/tests/mocha.env.js b/packages/hyperflux/tests/mocha.env.js index d37cea8231..5dc69c9c06 100644 --- a/packages/hyperflux/tests/mocha.env.js +++ b/packages/hyperflux/tests/mocha.env.js @@ -26,6 +26,7 @@ Ethereal Engine. All Rights Reserved. process.env.APP_ENV = 'test' +process.env.NODE_ENV = 'test' process.env.TS_NODE_FILES = true process.env.TS_NODE_PROJECT = 'tsconfig.json' process.env.TS_NODE_COMPILER_OPTIONS = '{\"module\": \"commonjs\" }' diff --git a/packages/instanceserver/src/NetworkFunctions.ts b/packages/instanceserver/src/NetworkFunctions.ts index 9b07f0fa3a..a8af4137dc 100755 --- a/packages/instanceserver/src/NetworkFunctions.ts +++ b/packages/instanceserver/src/NetworkFunctions.ts @@ -27,14 +27,13 @@ import _ from 'lodash' import { DataConsumer, DataProducer } from 'mediasoup/node/lib/types' import { Spark } from 'primus' +import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import { Instance } from '@etherealengine/common/src/interfaces/Instance' import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { UserInterface } from '@etherealengine/common/src/interfaces/User' import { UserId } from '@etherealengine/common/src/interfaces/UserId' -import { SpawnPoseComponent } from '@etherealengine/engine/src/avatar/components/SpawnPoseComponent' import { respawnAvatar } from '@etherealengine/engine/src/avatar/functions/respawnAvatar' import checkPositionIsValid from '@etherealengine/engine/src/common/functions/checkPositionIsValid' -import { performance } from '@etherealengine/engine/src/common/functions/performance' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { getComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' import { DataChannelType } from '@etherealengine/engine/src/networking/classes/Network' @@ -43,6 +42,7 @@ import { NetworkPeerFunctions } from '@etherealengine/engine/src/networking/func import { JoinWorldRequestData } from '@etherealengine/engine/src/networking/functions/receiveJoinWorld' import { WorldState } from '@etherealengine/engine/src/networking/interfaces/WorldState' import { updateNetwork } from '@etherealengine/engine/src/networking/NetworkState' +import { EntityNetworkState } from '@etherealengine/engine/src/networking/state/EntityNetworkState' import { updatePeers } from '@etherealengine/engine/src/networking/systems/OutgoingActionSystem' import { GroupComponent } from '@etherealengine/engine/src/scene/components/GroupComponent' import { TransformComponent } from '@etherealengine/engine/src/transform/components/TransformComponent' @@ -451,9 +451,9 @@ const getUserSpawnFromInvite = async ( const validSpawnablePosition = checkPositionIsValid(inviterUserObject3d.position, false) if (validSpawnablePosition) { - const spawnPoseComponent = getComponent(selfAvatarEntity, SpawnPoseComponent) - spawnPoseComponent?.position.copy(inviterUserObject3d.position) - spawnPoseComponent?.rotation.copy(inviterUserTransform.rotation) + const spawnPose = getState(EntityNetworkState)[user.id as any as EntityUUID] + spawnPose.spawnPosition.copy(inviterUserObject3d.position) + spawnPose.spawnRotation.copy(inviterUserTransform.rotation) respawnAvatar(selfAvatarEntity) } } else { diff --git a/packages/instanceserver/src/startServerSystems.ts b/packages/instanceserver/src/startServerSystems.ts index 09a87979a8..ef1bca0b4b 100644 --- a/packages/instanceserver/src/startServerSystems.ts +++ b/packages/instanceserver/src/startServerSystems.ts @@ -38,9 +38,9 @@ import { startSystems } from '@etherealengine/engine/src/ecs/functions/SystemFun import { EquippableSystem } from '@etherealengine/engine/src/interaction/systems/EquippableSystem' import { InteractiveSystem } from '@etherealengine/engine/src/interaction/systems/InteractiveSystem' import { MotionCaptureSystem } from '@etherealengine/engine/src/mocap/MotionCaptureSystem' +import { EntityNetworkStateSystem } from '@etherealengine/engine/src/networking/state/EntityNetworkState' import { IncomingNetworkSystem } from '@etherealengine/engine/src/networking/systems/IncomingNetworkSystem' import { OutgoingNetworkSystem } from '@etherealengine/engine/src/networking/systems/OutgoingNetworkSystem' -import { WorldNetworkActionSystem } from '@etherealengine/engine/src/networking/systems/WorldNetworkActionSystem' import { PhysicsSystem } from '@etherealengine/engine/src/physics/systems/PhysicsSystem' import { SceneSystemLoadGroup, SceneSystemUpdateGroup } from '@etherealengine/engine/src/scene/SceneClientModule' @@ -49,7 +49,7 @@ import { ServerRecordingSystem } from './ServerRecordingSystem' export const startMediaServerSystems = () => { /** Fixed */ - startSystems([WorldNetworkActionSystem], { with: SimulationSystemGroup }) + startSystems([EntityNetworkStateSystem], { with: SimulationSystemGroup }) /** Post Render */ startSystems([ServerRecordingSystem], { @@ -65,7 +65,7 @@ export const startWorldServerSystems = () => { startSystems( [ IncomingNetworkSystem, - WorldNetworkActionSystem, + EntityNetworkStateSystem, ServerHostNetworkSystem, EquippableSystem, AvatarSimulationSystemGroup diff --git a/packages/instanceserver/tests/mocha.env.js b/packages/instanceserver/tests/mocha.env.js index d37cea8231..5dc69c9c06 100644 --- a/packages/instanceserver/tests/mocha.env.js +++ b/packages/instanceserver/tests/mocha.env.js @@ -26,6 +26,7 @@ Ethereal Engine. All Rights Reserved. process.env.APP_ENV = 'test' +process.env.NODE_ENV = 'test' process.env.TS_NODE_FILES = true process.env.TS_NODE_PROJECT = 'tsconfig.json' process.env.TS_NODE_COMPILER_OPTIONS = '{\"module\": \"commonjs\" }' diff --git a/packages/projects/default-project/tests/mocha.env.js b/packages/projects/default-project/tests/mocha.env.js index 979a84f459..4e3a29e254 100644 --- a/packages/projects/default-project/tests/mocha.env.js +++ b/packages/projects/default-project/tests/mocha.env.js @@ -26,6 +26,7 @@ Ethereal Engine. All Rights Reserved. process.env.APP_ENV = 'test' +process.env.NODE_ENV = 'test' process.env.TS_NODE_FILES = true process.env.TS_NODE_PROJECT = 'tsconfig.json' process.env.TS_NODE_COMPILER_OPTIONS = '{\"module\": \"commonjs\" }' diff --git a/packages/server-core/src/media/storageprovider/s3.storage.ts b/packages/server-core/src/media/storageprovider/s3.storage.ts index d9571409ba..772546f9ac 100755 --- a/packages/server-core/src/media/storageprovider/s3.storage.ts +++ b/packages/server-core/src/media/storageprovider/s3.storage.ts @@ -341,15 +341,22 @@ export class S3Provider implements StorageProviderInterface { let routeRegex = '' for (let route of routes) if (route !== '/') - routeRegex += - route === '/location' || - route === '/auth' || - route === '/admin' || - route === '/editor' || - route === '/studio' || - route === '/capture' - ? `^${route}/|` - : `^${route}|` + switch (route) { + case '/admin': + case '/editor': + case '/studio': + routeRegex += `^${route}$$|` // String.replace will convert this to a single $ + routeRegex += `^${route}/|` + break + case '/location': + case '/auth': + case '/capture': + routeRegex += `^${route}/|` + break + default: + routeRegex += `^${route}$$|` // String.replace will convert this to a single $ + break + } if (routes.length > 0) routeRegex = routeRegex.slice(0, routeRegex.length - 1) let publicRegex = '' fs.readdirSync(path.join(appRootPath.path, 'packages', 'client', 'dist'), { withFileTypes: true }).forEach(