Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Add strong entity typings, add missing types to ECS queries #7892

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
ComponentType,
getComponent,
getMutableComponent,
getOrAddComponent,
hasComponent,
useComponent
} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
Expand Down
9 changes: 2 additions & 7 deletions packages/editor/src/components/properties/Util.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { Entity } from '@etherealengine/engine/src/ecs/classes/Entity'
import { SceneState } from '@etherealengine/engine/src/ecs/classes/Scene'
import {
Component,
ComponentType,
SerializedComponentType
} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { Component, SerializedComponentType } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { EntityOrObjectUUID, getEntityNodeArrayFromEntities } from '@etherealengine/engine/src/ecs/functions/EntityTree'
import { iterateEntityNode } from '@etherealengine/engine/src/ecs/functions/EntityTree'
import { UUIDComponent } from '@etherealengine/engine/src/scene/components/UUIDComponent'
Expand Down Expand Up @@ -58,7 +53,7 @@ export const updateProperties = <C extends Component>(
export function traverseScene<T>(
callback: (node: Entity) => T,
predicate: (node: Entity) => boolean = () => true,
snubChildren: boolean = false
snubChildren = false
): T[] {
const result: T[] = []
iterateEntityNode(getState(SceneState).sceneEntity, (node) => result.push(callback(node)), predicate, snubChildren)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { Event, Object3D } from 'three'

import { ComponentJson } from '@etherealengine/common/src/interfaces/SceneInterface'

import {
ComponentMap,
getComponent,
getMutableComponent,
getOptionalComponent,
hasComponent
} from '../../../../ecs/functions/ComponentFunctions'
import { ColliderComponent } from '../../../../scene/components/ColliderComponent'
Expand All @@ -27,16 +24,18 @@ export class EEECSExporterExtension extends ExporterExtension implements GLTFExp
const data = new Array<[string, any]>()
for (const field of gltfLoaded) {
switch (field) {
case 'entity':
const name = getComponent(entity, NameComponent)
case 'entity': {
const name = getOptionalComponent(entity, NameComponent)
data.push(['xrengine.entity', name])
break
default:
const component = ComponentMap.get(field)!
}
default: {
const component = ComponentMap.get(field)! as any
const compData = component.toJSON(entity, getMutableComponent(entity, component))
for (const [field, value] of Object.entries(compData)) {
data.push([`xrengine.${component.name}.${field}`, value])
}
}
}
}
nodeDef.extensions = nodeDef.extensions ?? {}
Expand Down
6 changes: 4 additions & 2 deletions packages/engine/src/avatar/AvatarAnimationSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
DistanceFromCameraComponent,
FrustumCullCameraComponent
} from '../transform/components/DistanceComponents'
import { LocalTransformComponent, TransformComponent } from '../transform/components/TransformComponent'
import { updateGroupChildren } from '../transform/systems/TransformSystem'
import { XRLeftHandComponent, XRRightHandComponent } from '../xr/XRComponents'
import { getCameraMode, isMobileXRHeadset, ReferenceSpace, XRState } from '../xr/XRState'
Expand Down Expand Up @@ -103,7 +104,7 @@ const avatarAnimationQuery = defineQuery([AnimationComponent, AvatarAnimationCom

const minimumFrustumCullDistanceSqr = 5 * 5 // 5 units

const filterPriorityEntities = (entity: Entity) =>
const filterPriorityEntities = (entity: Entity<any>) =>
Engine.instance.priorityAvatarEntities.has(entity) || entity === Engine.instance.localClientEntity

const filterFrustumCulledEntities = (entity: Entity) =>
Expand Down Expand Up @@ -273,6 +274,7 @@ const execute = () => {
const { rig } = getComponent(entity, AvatarRigComponent)

const ik = getComponent(entity, AvatarLeftArmIKComponent)
ik.target.updateMatrixWorld(true)

// If data is zeroed out, assume there is no input and do not run IK
if (!ik.target.position.equals(V_000)) {
Expand Down Expand Up @@ -328,7 +330,7 @@ const execute = () => {
for (const entity of loopAnimationEntities) updateGroupChildren(entity)

for (const entity of Engine.instance.priorityAvatarEntities) {
const avatarRig = getComponent(entity, AvatarRigComponent)
const avatarRig = getComponent(entity as Entity<[typeof AvatarRigComponent]>, AvatarRigComponent)
if (avatarRig) {
avatarRig.rig.Hips.updateWorldMatrix(true, true)
avatarRig.helper?.updateMatrixWorld(true)
Expand Down
6 changes: 3 additions & 3 deletions packages/engine/src/avatar/AvatarControllerSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { AvatarHeadDecapComponent } from './components/AvatarIKComponents'
import { respawnAvatar } from './functions/respawnAvatar'
import { AvatarInputSettingsReceptor } from './state/AvatarInputSettingsState'

const localControllerQuery = defineQuery([AvatarControllerComponent, LocalInputTagComponent])
const localControllerQuery = defineQuery([AvatarControllerComponent, LocalInputTagComponent, UUIDComponent])
const controllerQuery = defineQuery([AvatarControllerComponent])
const sessionChangedActions = defineActionQueue(XRAction.sessionChanged.matches)

Expand Down Expand Up @@ -70,8 +70,9 @@ const execute = () => {

const controlledEntity = Engine.instance.localClientEntity

if (hasComponent(controlledEntity, AvatarControllerComponent)) {
if (hasComponent(controlledEntity, AvatarControllerComponent) && hasComponent(controlledEntity, RigidBodyComponent)) {
const controller = getComponent(controlledEntity, AvatarControllerComponent)
const rigidbody = getComponent(controlledEntity, RigidBodyComponent)

if (controller.movementEnabled) {
/** Support multiple peers controlling the same avatar by detecting movement and overriding network authority.
Expand All @@ -95,7 +96,6 @@ const execute = () => {
}
}

const rigidbody = getComponent(controlledEntity, RigidBodyComponent)
if (rigidbody.position.y < -10) respawnAvatar(controlledEntity)
}
}
Expand Down
11 changes: 6 additions & 5 deletions packages/engine/src/avatar/functions/moveAvatar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getMutableState } from '@etherealengine/hyperflux'

import { destroyEngine, Engine } from '../../ecs/classes/Engine'
import { EngineState } from '../../ecs/classes/EngineState'
import { Entity } from '../../ecs/classes/Entity'
import { getComponent } from '../../ecs/functions/ComponentFunctions'
import { createEngine } from '../../initializeEngine'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
Expand Down Expand Up @@ -46,8 +47,8 @@ describe('moveAvatar function tests', () => {

const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)

const velocity = getComponent(entity, RigidBodyComponent).linearVelocity
const avatar = getComponent(entity, AvatarControllerComponent)
const velocity = getComponent(entity as Entity<any>, RigidBodyComponent).linearVelocity
const avatar = getComponent(entity as Entity<any>, AvatarControllerComponent)

avatar.gamepadWorldMovement.setZ(-1)

Expand Down Expand Up @@ -79,7 +80,7 @@ describe('moveAvatar function tests', () => {

const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)

const velocity = getComponent(entity, RigidBodyComponent).linearVelocity
const velocity = getComponent(entity as Entity<any>, RigidBodyComponent).linearVelocity

// velocity starts at 0
strictEqual(velocity.x, 0)
Expand Down Expand Up @@ -114,7 +115,7 @@ describe('moveAvatar function tests', () => {

const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)

const velocity = getComponent(entity, RigidBodyComponent).linearVelocity
const velocity = getComponent(entity as Entity<any>, RigidBodyComponent).linearVelocity

// velocity starts at 0
strictEqual(velocity.x, 0)
Expand Down Expand Up @@ -146,7 +147,7 @@ describe('moveAvatar function tests', () => {

const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)

const velocity = getComponent(entity, RigidBodyComponent).linearVelocity
const velocity = getComponent(entity as Entity<any>, RigidBodyComponent).linearVelocity

// velocity starts at 0
strictEqual(velocity.x, 0)
Expand Down
36 changes: 22 additions & 14 deletions packages/engine/src/ecs/classes/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import { NetworkId } from '@etherealengine/common/src/interfaces/NetworkId'
import { ComponentJson } from '@etherealengine/common/src/interfaces/SceneInterface'

import { GLTFLoader } from '../../assets/loaders/gltf/GLTFLoader'
import { AvatarAnimationComponent } from '../../avatar/components/AvatarAnimationComponent'
import { AvatarComponent } from '../../avatar/components/AvatarComponent'
import { AvatarControllerComponent } from '../../avatar/components/AvatarControllerComponent'
import { CameraComponent } from '../../camera/components/CameraComponent'
import { SceneLoaderType } from '../../common/constants/PrefabFunctionType'
import { nowMilliseconds } from '../../common/functions/nowMilliseconds'
Expand All @@ -30,6 +32,7 @@ import { ButtonInputStateType } from '../../input/InputState'
import { NetworkObjectComponent } from '../../networking/components/NetworkObjectComponent'
import { NetworkState } from '../../networking/NetworkState'
import { PhysicsWorld } from '../../physics/classes/Physics'
import { RigidBodyComponent } from '../../physics/components/RigidBodyComponent'
import { addObjectToGroup } from '../../scene/components/GroupComponent'
import { NameComponent } from '../../scene/components/NameComponent'
import { PortalComponent } from '../../scene/components/PortalComponent'
Expand All @@ -40,7 +43,6 @@ import { setTransformComponent, TransformComponent } from '../../transform/compo
import { Widget } from '../../xrui/Widgets'
import {
Component,
ComponentType,
defineQuery,
EntityRemovedComponent,
getComponent,
Expand Down Expand Up @@ -206,7 +208,7 @@ export class Engine {
/**
* The xr origin reference space entity
*/
originEntity: Entity = UndefinedEntity
originEntity: Entity<[typeof TransformComponent]>

/**
* The xr origin group
Expand All @@ -216,7 +218,7 @@ export class Engine {
/**
* The camera entity
*/
cameraEntity: Entity = UndefinedEntity
cameraEntity: Entity<[typeof TransformComponent, typeof CameraComponent]>

/**
* Reference to the three.js camera object.
Expand Down Expand Up @@ -249,13 +251,13 @@ export class Engine {

buttons = {} as Readonly<ButtonInputStateType>

reactiveQueryStates = new Set<{ query: Query; result: State<Entity[]>; components: QueryComponents }>()
reactiveQueryStates = new Set<{ query: Query<any>; result: State<Entity[]>; components: QueryComponents }>()

#entityQuery = defineQuery([Not(EntityRemovedComponent)])
entityQuery = () => this.#entityQuery() as Entity[]

// @todo move to EngineState
activePortal = null as ComponentType<typeof PortalComponent> | null
activePortal = null as typeof PortalComponent._TYPE | null

systemGroups = {} as {
input: SystemUUID
Expand Down Expand Up @@ -307,7 +309,15 @@ export class Engine {
getUserAvatarEntity(userId: UserId) {
return this.getOwnedNetworkObjectsWithComponent(userId, AvatarComponent).find((eid) => {
return getComponent(eid, AvatarComponent).primary
})!
})! as Entity<
[
typeof NetworkObjectComponent,
typeof AvatarComponent,
typeof AvatarControllerComponent,
typeof AvatarAnimationComponent,
typeof RigidBodyComponent
]
>
}

/**
Expand All @@ -316,12 +326,10 @@ export class Engine {
* @param component
* @returns
*/
getOwnedNetworkObjectWithComponent<T, S extends bitecs.ISchema>(userId: UserId, component: Component<T, S>) {
return (
this.getOwnedNetworkObjects(userId).find((eid) => {
return hasComponent(eid, component)
}) || UndefinedEntity
)
getOwnedNetworkObjectWithComponent<C extends Component>(userId: UserId, component: C) {
return (this.getOwnedNetworkObjects(userId).find((eid) => {
return hasComponent(eid, component)
}) || UndefinedEntity) as Entity<[C, typeof NetworkObjectComponent]>
}

/**
Expand All @@ -330,10 +338,10 @@ export class Engine {
* @param component
* @returns
*/
getOwnedNetworkObjectsWithComponent<T, S extends bitecs.ISchema>(userId: UserId, component: Component<T, S>) {
getOwnedNetworkObjectsWithComponent<C extends Component>(userId: UserId, component: C) {
return this.getOwnedNetworkObjects(userId).filter((eid) => {
return hasComponent(eid, component)
})
}) as any as Entity<[typeof NetworkObjectComponent, C]>[]
}

/** ID of last network created. */
Expand Down
15 changes: 14 additions & 1 deletion packages/engine/src/ecs/classes/Entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { OpaqueType } from '@etherealengine/common/src/interfaces/OpaqueType'

export type Entity = OpaqueType<'entity'> & number
type FilterComponents<T> = T //extends {isComponent:true} ? T : never

/**
* Entity or Entity<[]> means an entity that can NOT be used with any component without type errors
* Entity<[Component1, Component2]> means an entity that is typed to ONLY have Component1 and Component2
*/
export type Entity<C extends readonly any[] = []> = OpaqueType<'entity'> &
number & {
__components: {
[key in FilterComponents<C>[number]['name']]: true
} & {
[key: string]: true
}
}

export const UndefinedEntity = 0 as Entity
5 changes: 3 additions & 2 deletions packages/engine/src/ecs/classes/Scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Color, Texture } from 'three'
import { SceneData } from '@etherealengine/common/src/interfaces/SceneInterface'
import { defineState } from '@etherealengine/hyperflux'

import { UndefinedEntity } from './Entity'
import { EntityTreeNode } from '../functions/EntityTree'
import { Entity, UndefinedEntity } from './Entity'

/** @todo support multiple scenes */

Expand All @@ -16,7 +17,7 @@ export const SceneState = defineState({
name: 'SceneState',
initial: () => ({
sceneData: null as SceneData | null,
sceneEntity: UndefinedEntity,
sceneEntity: UndefinedEntity as Entity<EntityTreeNode>,
background: null as null | Color | Texture,
sceneMetadataRegistry: {} as Record<string, SceneMetadata<any>>
})
Expand Down
Loading