-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathTeleportController.cs
More file actions
138 lines (114 loc) · 5.85 KB
/
Copy pathTeleportController.cs
File metadata and controls
138 lines (114 loc) · 5.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using Arch.Core;
using Cysharp.Threading.Tasks;
using DCL.Character;
using DCL.CharacterMotion.Components;
using DCL.Ipfs;
using DCL.Utilities;
using ECS.SceneLifeCycle;
using ECS.SceneLifeCycle.Components;
using ECS.SceneLifeCycle.Reporting;
using ECS.SceneLifeCycle.SceneDefinition;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using UnityEngine;
namespace DCL.RealmNavigation
{
public class TeleportController : ITeleportController
{
private static readonly QueryDescription BANNED_SCENES_QUERY =
new QueryDescription().WithAll<SceneDefinitionComponent, BannedSceneComponent>();
private readonly ISceneReadinessReportQueue sceneReadinessReportQueue;
private IRetrieveScene? retrieveScene;
private World? world;
private Entity playerEntity;
public IRetrieveScene SceneProviderStrategy
{
set => retrieveScene = value;
}
public World World
{
set
{
world = value;
playerEntity = world.CachePlayer();
}
}
public TeleportController(ISceneReadinessReportQueue sceneReadinessReportQueue)
{
this.sceneReadinessReportQueue = sceneReadinessReportQueue;
}
public void InvalidateRealm()
{
retrieveScene = null;
}
/// <summary>
/// If current scene is still loading it will block the teleport until its assets are resolved or timed out
/// </summary>
public UniTask<WaitForSceneReadiness?> TeleportToSceneSpawnPointAsync(Vector2Int parcel, AsyncLoadProcessReport loadReport, CancellationToken ct, bool landOnParcel = false, string? spawnPointName = null) =>
TeleportAsync(parcel, loadReport, ct, landOnParcel: landOnParcel, spawnPointName: spawnPointName);
/// <summary>
/// Debug Widget teleportation
/// </summary>
public UniTask TeleportToParcelAsync(Vector2Int parcel, AsyncLoadProcessReport loadReport, CancellationToken ct) =>
TeleportAsync(parcel, loadReport, ct, nullifySceneDef: true);
public void StartTeleportToSpawnPoint(SceneEntityDefinition sceneDataSceneEntityDefinition, CancellationToken ct) =>
world?.AddOrGet(playerEntity, new PlayerTeleportIntent(sceneDataSceneEntityDefinition, Vector2Int.zero, TeleportUtils.PickTargetWithOffset(sceneDataSceneEntityDefinition, sceneDataSceneEntityDefinition.metadata.scene.DecodedBase).targetWorldPosition, ct, isPositionSet: true));
private async UniTask<WaitForSceneReadiness?> TeleportAsync(Vector2Int parcel, AsyncLoadProcessReport loadReport, CancellationToken ct, bool nullifySceneDef = false, bool landOnParcel = false, string? spawnPointName = null)
{
if (retrieveScene == null)
{
world?.AddOrGet(playerEntity, new PlayerTeleportIntent(null, parcel, Vector3.zero, ct, loadReport, landOnParcel: landOnParcel, spawnPointName: spawnPointName));
loadReport.SetProgress(1f);
return null;
}
SceneEntityDefinition? sceneDef = await retrieveScene.ByParcelAsync(parcel, ct);
if (sceneDef != null && !TeleportUtils.IsRoad(sceneDef.metadata.OriginalJson.AsSpan()))
{
// Landing on the scene's base parcel has no sub-parcel to target, so take the
// spawn-point path: the landing then matches map/chat teleports to the same scene.
if (landOnParcel && parcel == sceneDef.metadata.scene.DecodedBase)
landOnParcel = false;
// When landing on the exact parcel, keep the requested parcel; otherwise snap to the
// scene base so the spawn point is used.
if (!landOnParcel)
parcel = sceneDef.metadata.scene.DecodedBase; // Override parcel as it's a new target
if (nullifySceneDef)
sceneDef = null;
}
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
world?.AddOrGet(playerEntity, new PlayerTeleportIntent(sceneDef, parcel, Vector3.zero, ct, loadReport, landOnParcel: landOnParcel, spawnPointName: spawnPointName));
if (sceneDef == null)
{
loadReport.SetProgress(1f); // Almost instant completion for empty parcels
return null;
}
// Banned destination: the scene has been disposed and won't be reloaded, so no system will ever
// dequeue the readiness report. Complete it now so the loading screen closes and the avatar lands
// at the requested position with the scene unloaded (same UX as cross-realm entry into a banned world).
if (IsSceneBanned(sceneDef.id))
{
loadReport.SetProgress(1f);
return null;
}
return new WaitForSceneReadiness(parcel, loadReport, sceneReadinessReportQueue);
}
private bool IsSceneBanned(string? sceneId)
{
if (world == null || string.IsNullOrEmpty(sceneId)) return false;
// Chunk iteration to avoid the delegate/closure allocation of World.Query(ForEach).
// The matched archetype is empty in the common case (no current bans), so iteration is effectively free.
foreach (ref Chunk chunk in world.Query(in BANNED_SCENES_QUERY).GetChunkIterator())
{
ref SceneDefinitionComponent first = ref chunk.GetFirst<SceneDefinitionComponent>();
foreach (int i in chunk)
{
ref SceneDefinitionComponent definition = ref Unsafe.Add(ref first, i);
if (definition.Definition.id == sceneId)
return true;
}
}
return false;
}
}
}