Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jitter buffer #8020

Merged
merged 6 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 0 additions & 2 deletions packages/client-core/src/components/World/EngineHooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,6 @@ export const useOfflineScene = (props?: { spectate?: boolean }) => {
if (props?.spectate) return

receiveJoinWorld({
highResTimeOrigin: performance.timeOrigin,
worldStartTime: performance.now(),
cachedActions: [],
peerIndex,
routerRtpCapabilities: undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,8 +722,8 @@ export async function createDataProducer(
appData: { data: customInitInfo },
ordered: false,
label: dataChannelType,
maxPacketLifeTime: 0,
// maxRetransmits: 3,
// maxPacketLifeTime: 0,
maxRetransmits: 1,
protocol: type // sub-protocol for type of data to be transmitted on the channel e.g. json, raw etc. maybe make type an enum rather than string
})
// dataProducer.on("open", () => {
Expand Down
28 changes: 10 additions & 18 deletions packages/client-core/src/world/startClientSystems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ import { XRUISystem } from '@etherealengine/engine/src/xrui/systems/XRUISystem'

export const startClientSystems = () => {
/** Input */
startSystems(
[IncomingNetworkSystem, XRSystem, MotionCaptureSystem, ClientInputSystem, AvatarInputGroup, CameraInputSystem],
{ with: InputSystemGroup }
)
startSystems([XRSystem, MotionCaptureSystem, ClientInputSystem, AvatarInputGroup, CameraInputSystem], {
with: InputSystemGroup
})

/** Fixed */
startSystems([WorldNetworkActionSystem, EquippableSystem, AvatarSimulationGroup], { with: SimulationSystemGroup })
startSystems(
[IncomingNetworkSystem, WorldNetworkActionSystem, EquippableSystem, AvatarSimulationGroup, OutgoingNetworkSystem],
{ with: SimulationSystemGroup }
)

/** Physics */
startSystems([PhysicsSystem], { after: SimulationSystemGroup })
Expand Down Expand Up @@ -77,17 +79,7 @@ export const startClientSystems = () => {
})

/** Post Render */
startSystems(
[
ButtonCleanupSystem,
PortalSystem,
ECSSerializerSystem,
PositionalAudioSystem,
SceneSystemLoadGroup,
OutgoingNetworkSystem
],
{
after: PresentationSystemGroup
}
)
startSystems([ButtonCleanupSystem, PortalSystem, ECSSerializerSystem, PositionalAudioSystem, SceneSystemLoadGroup], {
after: PresentationSystemGroup
})
}
12 changes: 6 additions & 6 deletions packages/editor/src/functions/EditorControlFunctions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('EditorControlFunctions', () => {
createEngine()
registerEditorReceptors()

Engine.instance.store.defaultDispatchDelay = 0
Engine.instance.store.defaultDispatchDelay = () => 0

const rootNode = getState(SceneState).sceneEntity
nodes = [createEntity(), createEntity()]
Expand Down Expand Up @@ -108,7 +108,7 @@ describe('EditorControlFunctions', () => {
beforeEach(() => {
createEngine()
registerEditorReceptors()
Engine.instance.store.defaultDispatchDelay = 0
Engine.instance.store.defaultDispatchDelay = () => 0

rootNode = getState(SceneState).sceneEntity
})
Expand All @@ -124,7 +124,7 @@ describe('EditorControlFunctions', () => {
beforeEach(() => {
createEngine()
registerEditorReceptors()
Engine.instance.store.defaultDispatchDelay = 0
Engine.instance.store.defaultDispatchDelay = () => 0

const world = getState(SceneState)

Expand Down Expand Up @@ -191,7 +191,7 @@ describe('EditorControlFunctions', () => {
beforeEach(() => {
createEngine()
registerEditorReceptors()
Engine.instance.store.defaultDispatchDelay = 0
Engine.instance.store.defaultDispatchDelay = () => 0

const rootNode = getState(SceneState).sceneEntity
nodes = [createEntity(), createEntity()]
Expand Down Expand Up @@ -230,7 +230,7 @@ describe('EditorControlFunctions', () => {
beforeEach(() => {
createEngine()
registerEditorReceptors()
Engine.instance.store.defaultDispatchDelay = 0
Engine.instance.store.defaultDispatchDelay = () => 0

const world = getState(SceneState)

Expand Down Expand Up @@ -277,7 +277,7 @@ describe('EditorControlFunctions', () => {
beforeEach(() => {
createEngine()
registerEditorReceptors()
Engine.instance.store.defaultDispatchDelay = 0
Engine.instance.store.defaultDispatchDelay = () => 0

const rootNode = getState(SceneState).sceneEntity
nodes = [createEntity(), createEntity()]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ export function createAvatarAnimationGraph(
rule: compositeTransitionRule(
[
booleanTransitionRule(jumpValue, 'isInAir'),
thresholdTransitionRule(locomotion, 'y', -0.1 / getState(EngineState).fixedDeltaSeconds, false)
thresholdTransitionRule(locomotion, 'y', -0.1 / getState(EngineState).simulationTimestep, false)
],
'and'
),
Expand Down
8 changes: 4 additions & 4 deletions packages/engine/src/avatar/functions/moveAvatar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('moveAvatar function tests', () => {

it('should apply world.fixedDelta @ 60 tick to avatar movement, consistent with physics simulation', () => {
const engineState = getMutableState(EngineState)
engineState.fixedDeltaSeconds.set(1000 / 60)
engineState.simulationTimestep.set(1000 / 60)

const spawnAvatar = WorldNetworkAction.spawnAvatar({
$from: Engine.instance.userId,
Expand Down Expand Up @@ -65,7 +65,7 @@ describe('moveAvatar function tests', () => {

it('should apply world.fixedDelta @ 120 tick to avatar movement, consistent with physics simulation', () => {
const engineState = getMutableState(EngineState)
engineState.fixedDeltaSeconds.set(1000 / 60)
engineState.simulationTimestep.set(1000 / 60)

const spawnAvatar = WorldNetworkAction.spawnAvatar({
$from: Engine.instance.userId,
Expand Down Expand Up @@ -97,7 +97,7 @@ describe('moveAvatar function tests', () => {
Engine.instance.userId = 'user' as UserId

const engineState = getMutableState(EngineState)
engineState.fixedDeltaSeconds.set(1000 / 60)
engineState.simulationTimestep.set(1000 / 60)

/* mock */
Engine.instance.physicsWorld.timestep = 1 / 2
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('moveAvatar function tests', () => {
Engine.instance.userId = 'user' as UserId

const engineState = getMutableState(EngineState)
engineState.fixedDeltaSeconds.set(1000 / 60)
engineState.simulationTimestep.set(1000 / 60)

const spawnAvatar = WorldNetworkAction.spawnAvatar({
$from: Engine.instance.userId,
Expand Down
4 changes: 2 additions & 2 deletions packages/engine/src/common/functions/Timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type TimerUpdateCallback = (elapsedTime: number) => any
const TPS_REPORTS_ENABLED = false
const TPS_REPORT_INTERVAL_MS = 10000

export function Timer(update: TimerUpdateCallback, tickRate: number) {
export function Timer(update: TimerUpdateCallback, serverTickRate = 90) {
let debugTick = 0

const newEngineTicks = {
Expand Down Expand Up @@ -135,7 +135,7 @@ export function Timer(update: TimerUpdateCallback, tickRate: number) {
const _update = () => {
onFrame(nowMilliseconds(), null)
}
serverLoop = new ServerLoop(_update, tickRate).start()
serverLoop = new ServerLoop(_update, serverTickRate).start()
}
tpsReset()
}
Expand Down
38 changes: 12 additions & 26 deletions packages/engine/src/ecs/classes/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ export class Engine {

api: FeathersApplication<ServiceTypes>

tickRate = 60

/** The uuid of the logged-in user */
userId: UserId

Expand All @@ -112,18 +110,11 @@ export class Engine {
},
getDispatchId: () => Engine.instance.userId,
getPeerId: () => Engine.instance.peerID,
getDispatchTime: () => Date.now(),
defaultDispatchDelay: 1 / this.tickRate,
getDispatchTime: () => getState(EngineState).simulationTime,
defaultDispatchDelay: () => getState(EngineState).simulationTimestep,
getCurrentReactorRoot: () => Engine.instance.activeSystemReactors.get(Engine.instance.currentSystemUUID)
}) as HyperStore

/**
* Current frame timestamp, relative to performance.timeOrigin
*/
get frameTime() {
return getState(EngineState).frameTime
}

engineTimer = null! as ReturnType<typeof Timer>

/**
Expand Down Expand Up @@ -157,41 +148,36 @@ export class Engine {

widgets = new Map<string, Widget>()

/**
* The time origin for this world, relative to performance.timeOrigin
*/
startTime = nowMilliseconds()

/**
* The seconds since the last world execution
* @deprecated use getState(EngineState).deltaSeconds
*/
get deltaSeconds() {
return getState(EngineState).deltaSeconds
}

/**
* The elapsed seconds since `startTime`
* The elapsed seconds since `performance.timeOrigin`
* @deprecated use `getState(EngineState).elapsedSeconds`
*/
get elapsedSeconds() {
return getState(EngineState).elapsedSeconds
}

/**
* The elapsed seconds since `startTime`, in fixed time steps.
* The current fixed tick (simulationTime / simulationTimeStep)
* @deprecated
*/
get fixedElapsedSeconds() {
return getState(EngineState).fixedElapsedSeconds
get fixedTick() {
const engineState = getState(EngineState)
return engineState.simulationTime / engineState.simulationTimestep
}

/**
* The current fixed tick (fixedElapsedSeconds / fixedDeltaSeconds)
* @deprecated use `getState(EngineState).simulationTimestep / 1000`
*/
get fixedTick() {
return getState(EngineState).fixedTick
}

get fixedDeltaSeconds() {
return getState(EngineState).fixedDeltaSeconds
return getState(EngineState).simulationTimestep / 1000
}

physicsWorld: PhysicsWorld
Expand Down
11 changes: 7 additions & 4 deletions packages/engine/src/ecs/classes/EngineState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import { Entity } from './Entity'
export const EngineState = defineState({
name: 'EngineState',
initial: {
speigg marked this conversation as resolved.
Show resolved Hide resolved
frameTime: 0,
simulationTimestep: 1000 / 60,

frameTime: Date.now(),
simulationTime: Date.now(),

deltaSeconds: 0,
elapsedSeconds: 0,

physicsSubsteps: 1,
fixedDeltaSeconds: 1 / 60,
fixedElapsedSeconds: 0,
fixedTick: 0,

isEngineInitialized: false,
sceneLoading: false,
sceneLoaded: false,
Expand Down
8 changes: 4 additions & 4 deletions packages/engine/src/ecs/functions/EngineFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,16 @@ const entityRemovedQuery = defineQuery([EntityRemovedComponent])
*/
export const executeSystems = (frameTime: number) => {
const engineState = getMutableState(EngineState)
engineState.frameTime.set(frameTime)
engineState.frameTime.set(performance.timeOrigin + frameTime)

const start = nowMilliseconds()
const incomingActions = [...Engine.instance.store.actions.incoming]

const worldElapsedSeconds = (frameTime - Engine.instance.startTime) / 1000
const elapsedSeconds = frameTime / 1000
engineState.deltaSeconds.set(
Math.max(0.001, Math.min(TimerConfig.MAX_DELTA_SECONDS, worldElapsedSeconds - Engine.instance.elapsedSeconds))
Math.max(0.001, Math.min(TimerConfig.MAX_DELTA_SECONDS, elapsedSeconds - engineState.elapsedSeconds.value))
)
engineState.elapsedSeconds.set(worldElapsedSeconds)
engineState.elapsedSeconds.set(elapsedSeconds)

executeSystem(RootSystemGroup)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,13 @@ describe('FixedPipelineSystem', () => {
const mockState = getMutableState(MockState)

assert.equal(Engine.instance.elapsedSeconds, 0)
assert.equal(Engine.instance.fixedElapsedSeconds, 0)
assert.equal(Engine.instance.fixedTick, 0)
assert.equal(mockState.count.value, 0)

const ticks = 3
const deltaSeconds = ticks / 60
executeSystems(Engine.instance.startTime + 1000 * deltaSeconds)
executeSystems(1000 * deltaSeconds)
assert.equal(Engine.instance.elapsedSeconds, deltaSeconds)
assert.equal(Engine.instance.fixedElapsedSeconds, deltaSeconds)
assert.equal(Engine.instance.fixedTick, ticks)
assert.equal(mockState.count.value, ticks)
})
Expand All @@ -53,16 +51,13 @@ describe('FixedPipelineSystem', () => {

const mockState = getMutableState(MockState)

Engine.instance.startTime = 0
assert.equal(Engine.instance.elapsedSeconds, 0)
assert.equal(Engine.instance.fixedElapsedSeconds, 0)
assert.equal(Engine.instance.fixedTick, 0)
assert.equal(mockState.count.value, 0)

const deltaSeconds = 1000
executeSystems(1000 * deltaSeconds)
assert.equal(Engine.instance.elapsedSeconds, deltaSeconds)
assert.equal(Engine.instance.fixedElapsedSeconds, deltaSeconds)
assert.equal(Engine.instance.fixedTick, 60000)
assert((mockState.count.value * 1) / 60 < 5)
})
Expand Down
37 changes: 17 additions & 20 deletions packages/engine/src/ecs/functions/FixedPipelineSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,40 @@ export const executeFixedPipeline = () => {
const start = nowMilliseconds()
let timeUsed = 0

let accumulator = Engine.instance.elapsedSeconds - Engine.instance.fixedElapsedSeconds

const { fixedDeltaSeconds: timestep, elapsedSeconds, fixedTick } = getState(EngineState)
const engineState = getMutableState(EngineState)
const { frameTime, simulationTime, simulationTimestep } = getState(EngineState)

let simulationDelay = frameTime - simulationTime

const maxMilliseconds = 8

// If the difference between fixedElapsedTime and elapsedTime becomes too large,
// If the difference between simulationTime and frameTime becomes too large,
// we should simply skip ahead.
const maxFixedFrameDelay = Math.max(1, Engine.instance.deltaSeconds / timestep)
const maxSimulationDelay = 5000 // 5 seconds

if (accumulator < 0) {
engineState.fixedTick.set(Math.floor(elapsedSeconds / timestep))
engineState.fixedElapsedSeconds.set(fixedTick * timestep)
if (simulationDelay < simulationTimestep) {
engineState.simulationTime.set(Math.floor(frameTime / simulationTimestep) * simulationTimestep)
// simulation time is already up-to-date with frame time, so do nothing
return
}

let accumulatorDepleted = accumulator < timestep
let timeout = timeUsed > maxMilliseconds
let updatesLimitReached = false

while (!accumulatorDepleted && !timeout && !updatesLimitReached) {
engineState.fixedTick.set(engineState.fixedTick.value + 1)
engineState.fixedElapsedSeconds.set(engineState.fixedTick.value * timestep)
while (simulationDelay > simulationTimestep && !timeout && !updatesLimitReached) {
engineState.simulationTime.set(
(t) => Math.floor((t + simulationTimestep) / simulationTimestep) * simulationTimestep
)

executeSystem(SimulationSystemGroup)

accumulator -= timestep

const frameDelay = accumulator / timestep

simulationDelay -= simulationTimestep
timeUsed = nowMilliseconds() - start
accumulatorDepleted = accumulator < timestep
timeout = timeUsed > maxMilliseconds

if (frameDelay >= maxFixedFrameDelay) {
engineState.fixedTick.set(Math.floor(elapsedSeconds / timestep))
engineState.fixedElapsedSeconds.set(engineState.fixedTick.value * timestep)
if (simulationDelay >= maxSimulationDelay) {
// fast-forward if the simulation is too far behind
engineState.simulationTime.set((t) => Math.floor(frameTime / simulationTimestep) * simulationTimestep)
break
}
}
Expand Down
Loading
Loading