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

Commit

Permalink
Fix networked action dispatch ordering / lifecycle (#5796)
Browse files Browse the repository at this point in the history
* refactor physics debug action

* Fix networked action dispatch ordering and add tests

Co-authored-by: Gheric Speiginer <[email protected]>
  • Loading branch information
HexaField and speigg authored Apr 19, 2022
1 parent 9837917 commit a93ce90
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 52 deletions.
14 changes: 8 additions & 6 deletions packages/engine/src/avatar/AvatarInputSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Quaternion, SkinnedMesh, Vector2, Vector3 } from 'three'
import { isDev } from '@xrengine/common/src/utils/isDev'
import { dispatchAction } from '@xrengine/hyperflux'

// import { boxDynamicConfig } from '@xrengine/projects/default-project/PhysicsSimulationTestSystem'
import { FollowCameraComponent } from '../camera/components/FollowCameraComponent'
import { TargetCameraRotationComponent } from '../camera/components/TargetCameraRotationComponent'
import { CameraMode } from '../camera/types/CameraMode'
Expand Down Expand Up @@ -45,6 +44,8 @@ import {
unequipEntity
} from '../interaction/functions/equippableFunctions'
import { AutoPilotClickRequestComponent } from '../navigation/component/AutoPilotClickRequestComponent'
import { NetworkWorldAction } from '../networking/functions/NetworkWorldAction'
import { boxDynamicConfig } from '../physics/functions/physicsObjectDebugFunctions'
import { accessEngineRendererState, EngineRendererAction } from '../renderer/EngineRendererState'
import { Object3DComponent } from '../scene/components/Object3DComponent'
import { TransformComponent } from '../transform/components/TransformComponent'
Expand Down Expand Up @@ -510,11 +511,12 @@ export const handlePrimaryButton: InputBehaviorType = (entity, inputKey, inputVa
export const handlePhysicsDebugEvent = (entity: Entity, inputKey: InputAlias, inputValue: InputValue): void => {
if (inputValue.lifecycleState !== LifecycleValue.Ended) return
if (inputKey === PhysicsDebugInput.GENERATE_DYNAMIC_DEBUG_CUBE) {
// dispatchAction(
// NetworkWorldAction.spawnDebugPhysicsObject({
// config: boxDynamicConfig // Any custom config can be provided here
// })
// )
dispatchAction(
Engine.currentWorld.store,
NetworkWorldAction.spawnDebugPhysicsObject({
config: boxDynamicConfig // Any custom config can be provided here
})
)
} else if (inputKey === PhysicsDebugInput.TOGGLE_PHYSICS_DEBUG) {
dispatchAction(
Engine.store,
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/src/ecs/classes/World.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class World {

store = createHyperStore({
name: 'WORLD',
networked: true,
getDispatchMode: () => (this.isHosting ? 'host' : 'peer'),
getDispatchId: () => Engine.userId,
getDispatchTime: () => this.fixedTick,
defaultDispatchDelay: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ describe('NetworkActionReceptors', () => {

NetworkActionReceptor.createNetworkActionReceptor(world)

ActionFunctions.clearOutgoingActions(world.store, true)
ActionFunctions.clearOutgoingActions(world.store)
ActionFunctions.applyIncomingActions(world.store)
world.execute(0)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import matches from 'ts-matches'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { addActionReceptor, dispatchAction } from '@xrengine/hyperflux'

// import { generatePhysicsObject } from '@xrengine/projects/default-project/PhysicsSimulationTestSystem'
import { Engine } from '../../ecs/classes/Engine'
import { World } from '../../ecs/classes/World'
import { addComponent, getComponent, hasComponent, removeComponent } from '../../ecs/functions/ComponentFunctions'
import { createEntity, removeEntity } from '../../ecs/functions/EntityFunctions'
import { generatePhysicsObject } from '../../physics/functions/physicsObjectDebugFunctions'
import { NetworkObjectAuthorityTag } from '../components/NetworkObjectAuthorityTag'
import { NetworkObjectComponent } from '../components/NetworkObjectComponent'
import { NetworkWorldAction } from './NetworkWorldAction'
Expand Down Expand Up @@ -106,7 +106,7 @@ const spawnDebugPhysicsObject = (
world: World,
action: ReturnType<typeof NetworkWorldAction.spawnDebugPhysicsObject>
) => {
// generatePhysicsObject(action.config, action.config.spawnPosition, true, action.config.spawnScale)
generatePhysicsObject(action.config, action.config.spawnPosition, true, action.config.spawnScale)
}

const destroyObject = (world: World, action: ReturnType<typeof NetworkWorldAction.destroyObject>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { applyIncomingActions } from '@xrengine/hyperflux'

export default async function IncomingActionSystem(world) {
return () => {
applyIncomingActions(world.store, world.isHosting)
applyIncomingActions(world.store)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const sendOutgoingActions = (world: World) => {
console.error(e)
}

clearOutgoingActions(world.store, world.isHosting)
clearOutgoingActions(world.store)

return world
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function getRandomInt(min, max) {
let uuidCounter = 0
function getUUID() {
uuidCounter++
console.log('uuidCounter', uuidCounter)
return 'physicstestuuid' + uuidCounter
}

Expand Down
2 changes: 1 addition & 1 deletion packages/gameserver/src/NetworkFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ export function handleIncomingActions(socket, message) {
for (const a of actions) {
a['$fromSocketId'] = socket.id
a.$from = userIdMap[socket.id]
world.store.actions.incoming.push(a)
dispatchAction(world.store, a)
}
// console.log('SERVER INCOMING ACTIONS', JSON.stringify(actions))
}
Expand Down
34 changes: 13 additions & 21 deletions packages/hyperflux/functions/ActionFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ const dispatchAction = <StoreName extends string, A extends Action<StoreName>>(
action['$stack'] = stack
}

store.networked
? store.actions.outgoing.push(action as Required<Action<StoreName>>)
: store.actions.incoming.push(action as Required<Action<StoreName>>)
const mode = store.getDispatchMode()
if (mode === 'local' || mode === 'host') store.actions.incoming.push(action as Required<Action<StoreName>>)
else store.actions.outgoing.push(action as Required<Action<StoreName>>)
}

/**
Expand Down Expand Up @@ -328,7 +328,7 @@ const _updateCachedActions = (store: HyperStore<any>, incomingAction: Required<A
}
}

const _applyAndArchiveIncomingAction = (store: HyperStore<any>, action: Required<Action<any>>, forward = false) => {
const _applyIncomingAction = (store: HyperStore<any>, action: Required<Action<any>>) => {
// ensure actions are idempotent
if (store.actions.incomingHistoryUUIDs.has(action.$uuid)) {
const idx = store.actions.incoming.indexOf(action)
Expand All @@ -338,11 +338,11 @@ const _applyAndArchiveIncomingAction = (store: HyperStore<any>, action: Required

try {
store[allowStateMutations] = true
console.log(`${store.name} ACTION ${action.type}`, action)
for (const receptor of [...store.receptors]) receptor(action)
store[allowStateMutations] = false
console.log(`${store.name} ACTION ${action.type}`)
store.actions.incomingHistory.push(action)
if (forward) store.actions.outgoing.push(action)
if (store.getDispatchMode() === 'host') store.actions.outgoing.push(action)
} catch (e) {
const message = (e as Error).message
const stack = (e as Error).stack!.split('\n')
Expand All @@ -361,39 +361,31 @@ const _applyAndArchiveIncomingAction = (store: HyperStore<any>, action: Required
}

/**
* This function should be called when an action is received from the network
* Process incoming actions
*
* @param store
* @param action
* @param forwardToOutgoing Whether to forward incoming actions to the outgoing queue.
* This flag should be set when an authoritative node needs to forwards actions it receives
* on the network, after proocessing them locally.
*/
const applyIncomingActions = (store: HyperStore<any>, forwardToOutgoing = false) => {
const { incoming, outgoing } = store.actions
const applyIncomingActions = (store: HyperStore<any>) => {
const { incoming } = store.actions
const now = store.getDispatchTime()
for (const action of [...incoming]) {
if (action.$time > now) {
continue
}
_updateCachedActions(store, action)
_applyAndArchiveIncomingAction(store, action, forwardToOutgoing)
_applyIncomingAction(store, action)
}
}

/**
* Clears the outgoing action queue, and adds them to the outgoing history
* Clear the outgoing action queue
* @param store
* @param loopback Redirect outgoing actions back to the incoming queue.
* This flag should be set when an authoritative node needs to send actions to the network
* and also process the actions locally
*/
const clearOutgoingActions = (store: HyperStore<any>, loopback = false) => {
const { outgoing, outgoingHistory, outgoingHistoryUUIDs, incoming } = store.actions
const clearOutgoingActions = (store: HyperStore<any>) => {
const { outgoing, outgoingHistory, outgoingHistoryUUIDs } = store.actions
for (const action of outgoing) {
outgoingHistory.push(action)
outgoingHistoryUUIDs.add(action.$uuid)
if (loopback) incoming.push(action)
}
outgoing.length = 0
}
Expand Down
11 changes: 6 additions & 5 deletions packages/hyperflux/functions/StoreFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ export interface HyperStore<StoreName extends string> {
*/
name: StringLiteral<StoreName>
/**
* If this store is networked, actions are dispatched on the outgoing queue.
* If this store is not networked, actions are dispatched on the incoming queue.
* If the store mode is `local`, actions are dispatched on the incoming queue.
* If the store mode is `host`, actions are dispatched on the incoming queue and then forwarded to the outgoing queue.
* If the store mode is `peer`, actions are dispatched on the outgoing queue.
*/
networked: boolean
getDispatchMode: () => 'local' | 'host' | 'peer'
/**
* A function which returns the dispatch id assigned to actions
* */
Expand Down Expand Up @@ -59,14 +60,14 @@ export interface HyperStore<StoreName extends string> {

function createHyperStore<StoreName extends string>(options: {
name: StringLiteral<StoreName>
networked?: boolean
getDispatchMode?: () => 'local' | 'host' | 'peer'
getDispatchId: () => string
getDispatchTime: () => number
defaultDispatchDelay?: number
}) {
return {
name: options.name,
networked: options.networked ?? false,
getDispatchMode: options.getDispatchMode ?? (() => 'local'),
getDispatchId: options.getDispatchId,
getDispatchTime: options.getDispatchTime,
defaultDispatchDelay: options.defaultDispatchDelay ?? 0,
Expand Down
Loading

0 comments on commit a93ce90

Please sign in to comment.