Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gonpombo8 committed Oct 5, 2023
1 parent 64a4a39 commit 1f54d08
Show file tree
Hide file tree
Showing 13 changed files with 446 additions and 219 deletions.
9 changes: 8 additions & 1 deletion packages/@dcl/ecs/src/runtime/initialization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createPointerEventsSystem, PointerEventsSystem } from '../../systems/ev
import { createInputSystem, IInputSystem } from './../../engine/input'
import { createRaycastSystem, RaycastSystem } from '../../systems/raycast'
import { createVideoEventsSystem, VideoEventsSystem } from '../../systems/videoEvents'
import { TweenSystem, createTweenSystem } from '../../systems/tween'

/**
* @public
Expand Down Expand Up @@ -50,13 +51,19 @@ export const raycastSystem: RaycastSystem = /* @__PURE__ */ createRaycastSystem(
export { RaycastSystem }

/**
* @alpha
* @public
* Register callback functions to a particular entity on video events.
*/
export const videoEventsSystem: VideoEventsSystem = /* @__PURE__ */ createVideoEventsSystem(engine)
export { VideoEventsSystem }

/**
* @public
* Register callback functions to a particular entity on video events.
*/
export const tweenSystem: TweenSystem = createTweenSystem(engine)
export { TweenSystem }

/**
* @public
* Runs an async function
Expand Down
148 changes: 148 additions & 0 deletions packages/@dcl/ecs/src/systems/tween.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import * as components from '../components'
import { PBTween, TweenLoop, TweenStateStatus } from '../components'
import { Entity, IEngine } from '../engine'
import { ReadWriteByteBuffer } from '../serialization/ByteBuffer'
import { dataCompare } from './crdt/utils'

export type TweenSystem = {
tweenCompleted(entity: Entity): boolean
tweenChanged(entity: Entity): boolean
}

/**
* @internal
* @returns tween helper to be used on the scene
*/
export function createTweenSystem(engine: IEngine): TweenSystem {
const Tween = components.Tween(engine)
const TweenState = components.TweenState(engine)
const TweenSequence = components.TweenSequence(engine)

const cache = new Map<
Entity,
{
// Used to detect new tweens for the same entity
tween: Uint8Array
// Avoid updaing again the tween in the case we receieve a network tween from other client
frames: number
// Trigger the isCompleted only once per tween
completed: boolean
// Tween has changed on this frame
changed: boolean
}
>()

function isCompleted(entity: Entity) {
const tweenState = TweenState.getOrNull(entity)
const tween = Tween.getOrNull(entity)
const tweenCache = cache.get(entity)
if (!tweenState || !tween) return false

if (
// Renderer notified that the tween is completed
(tweenChanged(entity) || tweenState.state === TweenStateStatus.TS_COMPLETED) &&
// Avoid sending isCompleted multiple times
!tweenCache?.completed &&
// Amount of frames needed to consider a tween completed
(tweenCache?.frames ?? 0) > 2
) {
return true
}

return false
}

function tweenChanged(entity: Entity) {
const currentTween = Tween.getOrNull(entity)
const prevTween = cache.get(entity)?.tween

if ((currentTween && !prevTween) || (!currentTween && prevTween)) {
return true
}

const currentBuff = new ReadWriteByteBuffer()
Tween.schema.serialize(currentTween!, currentBuff)
const equal = dataCompare(currentBuff.toBinary(), prevTween)

return equal
}

const restartTweens: (() => void)[] = []
// Logic for sequence tweens
engine.addSystem(() => {
for (const restart of restartTweens) {
restart()
}
restartTweens.length = 0
for (const [entity, tween] of engine.getEntitiesWith(Tween)) {
if (tweenChanged(entity)) {
const buffer = new ReadWriteByteBuffer()
Tween.schema.serialize(tween, buffer)
cache.set(entity, {
tween: buffer.toBinary(),
frames: 0,
completed: false,
changed: true
})
continue
}
const tweenCache = cache.get(entity)!
tweenCache.frames += 1
tweenCache.changed = false
if (isCompleted(entity)) {
// Reset tween frames.
tweenCache.frames = 0
// set the tween completed to avoid calling this again for the same tween
tweenCache.completed = true

const tweenSequence = TweenSequence.getOrNull(entity)
if (!tweenSequence) continue
const { sequence } = tweenSequence

if (sequence && sequence.length) {
const [nextTweenSequence, ...otherTweens] = sequence
Tween.createOrReplace(entity, nextTweenSequence)
const mutableTweenHelper = TweenSequence.getMutable(entity)
mutableTweenHelper.sequence = otherTweens
if (tweenSequence.loop === TweenLoop.TL_RESTART) {
mutableTweenHelper.sequence.push(tween)
}
} else if (tweenSequence.loop === TweenLoop.TL_YOYO) {
const newTween = backwardsTween(tween)
Tween.createOrReplace(entity, newTween)
} else if (tweenSequence.loop === TweenLoop.TL_RESTART) {
// Tween.getMutable(entity).currentTime = (tween.currentTime || 0) + 0.00001
Tween.deleteFrom(entity)
cache.delete(entity)

restartTweens.push(() => {
Tween.createOrReplace(entity, tween)
})
}
}
}
}, Number.NEGATIVE_INFINITY)

function backwardsTween(tween: PBTween): PBTween {
if (tween.mode?.$case === 'move' && tween.mode.move) {
return { ...tween, mode: { ...tween.mode, move: { start: tween.mode.move.end, end: tween.mode.move.start } } }
}
if (tween.mode?.$case === 'rotate' && tween.mode.rotate) {
return {
...tween,
mode: { ...tween.mode, rotate: { start: tween.mode.rotate.end, end: tween.mode.rotate.start } }
}
}
if (tween.mode?.$case === 'scale' && tween.mode.scale) {
return { ...tween, mode: { ...tween.mode, scale: { start: tween.mode.scale.end, end: tween.mode.scale.start } } }
}

throw new Error('Invalid tween')
}

return {
// This event is fired only once per tween
tweenCompleted: isCompleted,
tweenChanged: (entity) => !!cache.get(entity)?.changed
}
}
13 changes: 11 additions & 2 deletions packages/@dcl/playground-assets/etc/playground-assets.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3808,6 +3808,17 @@ export const enum TweenStateStatus {
TS_PAUSED = 2
}

// Warning: (ae-missing-release-tag) "TweenSystem" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type TweenSystem = {
tweenCompleted(entity: Entity): void;
tweenChanged(entity: Entity): void;
};

// @public
export const tweenSystem: TweenSystem;

// @public
export interface UiAvatarTexture {
// (undocumented)
Expand Down Expand Up @@ -4062,8 +4073,6 @@ export interface VideoEventsSystem {
removeVideoEventsEntity(entity: Entity): void;
}

// Warning: (ae-extra-release-tag) The doc comment should not contain more than one release tag
//
// @public
export const videoEventsSystem: VideoEventsSystem;

Expand Down
24 changes: 17 additions & 7 deletions test/build-ecs/fixtures/sdk7-humming-birds-sync/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import {
pointerEventsSystem,
Schemas,
SyncComponents,
Transform,
Tween
Transform
} from '@dcl/sdk/ecs'
import { getRealm } from '~system/Runtime'
import { createNetworkManager } from '@dcl/sdk/network-transport'
Expand All @@ -21,6 +20,7 @@ import { getUserData } from '~system/UserIdentity'
import { NetworkManager } from '@dcl/sdk/network-transport/types'
import { createMovingPlatforms } from './moving-platforms'
import { changeColorSystem, createCube } from './create-cube'
import { createMovingPlatformsOld } from './moving-platforms-old'

export const GameStatus = engine.defineComponent('game-status', { paused: Schemas.Boolean })

Expand All @@ -36,17 +36,20 @@ export async function main() {
const serverUrl = realm.realmInfo?.isPreview
? 'ws://127.0.0.1:3000/ws/localScene'
: 'wss://scene-state-server.decentraland.org/ws/boedo.dcl.eth'
const networkManager = await createNetworkManager({
serverUrl,
networkEntitiesLimit: { serverLimit: 500, clientLimit: 15 }
})
const networkManager =
engine ||
(await createNetworkManager({
serverUrl,
networkEntitiesLimit: { serverLimit: 500, clientLimit: 15 }
}))
const userId = (await getUserData({})).data?.userId ?? ''

setupUi(userId)
if (server) {
if (server || true) {
engine.addSystem(moveHummingBirds)
gameStatusServer(networkManager)
createMovingPlatforms(networkManager)
createMovingPlatformsOld(networkManager)
for (const [x, y, z] of [
[44, 1, 26],
[36, 2, 37],
Expand All @@ -61,6 +64,7 @@ export async function main() {
}
// return
}

if (!server) {
engine.addSystem(changeColorSystem)
engine.addSystem(shootBirds(userId))
Expand All @@ -85,6 +89,12 @@ export async function main() {
src: 'models/staticPlatforms.glb'
})

const staticPlatform = engine.addEntity()
Transform.create(staticPlatform, { position: { x: 0, y: 0, z: 20 } })
GltfContainer.create(staticPlatform, {
src: 'models/staticPlatforms.glb'
})

const tree = engine.addEntity()
Transform.create(tree, {
position: { x: 20, y: 0, z: 8 },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { GltfContainer, Transform, SyncComponents, Entity } from '@dcl/ecs'
import * as utils from '@dcl-sdk/utils'
import { Vector3 } from '@dcl/sdk/math'
import { engine } from '@dcl/sdk/ecs'
import { NetworkManager } from '@dcl/sdk/network-transport/types'

const diffZ = 20

export function createMovingPlatformsOld(networkedEntityFactory: NetworkManager) {
//// triggerable platform

//// only horizontal
const platform1 = networkedEntityFactory.addEntity(engine)
GltfContainer.create(platform1, {
src: 'models/movingPlatform.glb'
})
Transform.create(platform1, {
position: Vector3.create(2, 1.5, 8 + diffZ)
})
SyncComponents.create(platform1, { componentIds: [Transform.componentId] })
//// only vertical
const platform2 = networkedEntityFactory.addEntity(engine)
GltfContainer.create(platform2, {
src: 'models/movingPlatform.glb'
})
Transform.create(platform2, {
position: Vector3.create(4, 1.5, 14 + diffZ)
})
SyncComponents.create(platform2, { componentIds: [Transform.componentId] })

//// path with many waypoints
const platform4 = networkedEntityFactory.addEntity(engine)
GltfContainer.create(platform4, {
src: 'models/movingPlatform.glb'
})
Transform.create(platform4, {
position: Vector3.create(6.5, 7, 4 + diffZ)
})
SyncComponents.create(platform4, { componentIds: [Transform.componentId] })

const platform3 = networkedEntityFactory.addEntity(engine)
GltfContainer.create(platform3, {
src: 'models/movingPlatform.glb'
})
Transform.create(platform3, {
position: Vector3.create(14, 4, 12 + diffZ)
})
SyncComponents.create(platform3, { componentIds: [Transform.componentId] })
startPath(
platform3,
[Vector3.create(14, 4, 12 + diffZ), Vector3.create(14, 4, 4 + diffZ), Vector3.create(14, 4, 12 + diffZ)],
3,
false,
true
)

startPath(
platform1,
[Vector3.create(2, 1.5, 8 + diffZ), Vector3.create(2, 1.5, 10 + diffZ), Vector3.create(2, 1.5, 8 + diffZ)],
3,
false,
true
)

startPath(
platform2,
[Vector3.create(4, 1.5, 14 + diffZ), Vector3.create(4, 4, 14 + diffZ), Vector3.create(4, 1.5, 14 + diffZ)],
2,
false,
true
)

startPath(
platform4,
[
Vector3.create(6.5, 7, 4 + diffZ),
Vector3.create(6.5, 7, 12 + diffZ),
Vector3.create(6.5, 10.5, 12 + diffZ),
Vector3.create(6.5, 10.5, 4 + diffZ),
Vector3.create(6.5, 7, 4 + diffZ)
],
40,
false,
true
)
}

// function to make path following recursive
function startPath(entity: Entity, path: Vector3[], duration: number, facePath?: boolean, loop?: boolean) {
utils.paths.startStraightPath(entity, path, duration, false, function () {
if (loop) startPath(entity, path, duration, facePath, loop)
})
}
Loading

0 comments on commit 1f54d08

Please sign in to comment.