forked from Unity-Technologies/EntityComponentSystemSamples
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNetcodeLevelSync.cs
More file actions
184 lines (161 loc) · 7.68 KB
/
Copy pathNetcodeLevelSync.cs
File metadata and controls
184 lines (161 loc) · 7.68 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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
public enum LevelSyncState
{
Idle,
LevelLoadRequest,
LevelLoadInProgress,
LevelLoaded
}
public struct ClientLoadLevel : IRpcCommand
{
public int LevelIndex;
}
public struct ClientReady : IRpcCommand
{
public int LevelIndex;
}
// Add to network connections when level load sync starts, clients without this when loading is done are new connections
public struct LevelLoadingInProgress : IComponentData { }
// Add when connection/client is done loading so in progress count should equal done count when server can start
public struct LevelLoadingDone : IComponentData { }
public struct LevelSyncStateComponent : IComponentData
{
public LevelSyncState State;
public int CurrentLevel;
// When client state is LevelLoadInProgress this level should be loaded
public int NextLevel;
}
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
[CreateBefore(typeof(LevelLoader))]
public partial class NetcodeClientLevelSync : SystemBase
{
protected override void OnCreate()
{
RequireForUpdate<NetworkId>();
EntityManager.CreateEntity(typeof(LevelSyncStateComponent));
}
protected override void OnUpdate()
{
var connectionEntity = SystemAPI.GetSingletonEntity<NetworkId>();
var levelState = SystemAPI.GetSingleton<LevelSyncStateComponent>();
if (!SystemAPI.QueryBuilder().WithAll<ClientLoadLevel, ReceiveRpcCommandRequest>().Build().IsEmptyIgnoreFilter)
{
FixedString64Bytes worldName = World.Name;
var ecb = new EntityCommandBuffer(Allocator.Temp);
// When load level command arrives, disable ghost sync, unload current level and load specified level
foreach (var (level, entity) in SystemAPI.Query<RefRO<ClientLoadLevel>>().WithEntityAccess()
.WithAll<ReceiveRpcCommandRequest>())
{
UnityEngine.Debug.Log($"[{worldName}] received command to load {level.ValueRO.LevelIndex}");
ecb.RemoveComponent<NetworkStreamInGame>(connectionEntity);
levelState.State = LevelSyncState.LevelLoadRequest;
levelState.NextLevel = level.ValueRO.LevelIndex;
SystemAPI.SetSingleton(levelState);
ecb.DestroyEntity(entity);
}
ecb.Playback(EntityManager);
}
if (levelState.State == LevelSyncState.LevelLoaded)
{
if (!EntityManager.HasComponent<NetworkStreamInGame>(connectionEntity))
{
var netId = SystemAPI.GetSingleton<NetworkId>();
UnityEngine.Debug.Log($"{World.Name} enable sync on connection {netId.Value}");
EntityManager.AddComponent<NetworkStreamInGame>(connectionEntity);
}
UnityEngine.Debug.Log($"[{World.Name}] notifying server it's finished loading {levelState.CurrentLevel}");
var rpcCmd = EntityManager.CreateEntity(typeof(ClientReady), typeof(SendRpcCommandRequest));
EntityManager.AddComponentData(rpcCmd, new ClientReady(){LevelIndex = levelState.CurrentLevel});
levelState.State = LevelSyncState.Idle;
SystemAPI.SetSingleton(levelState);
}
}
}
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[CreateBefore(typeof(LevelLoader))]
public partial class NetcodeServerLevelSync : SystemBase
{
private EntityQuery m_ClientsReadyQuery;
private EntityQuery m_ClientsLoadingQuery;
protected override void OnCreate()
{
m_ClientsReadyQuery = EntityManager.CreateEntityQuery(ComponentType.ReadOnly<LevelLoadingDone>());
m_ClientsLoadingQuery = EntityManager.CreateEntityQuery(ComponentType.ReadOnly<LevelLoadingInProgress>());
RequireForUpdate<NetworkId>();
EntityManager.CreateEntity(typeof(LevelSyncStateComponent));
}
protected override void OnUpdate()
{
var ecb = new EntityCommandBuffer(Allocator.Temp);
var connections = GetComponentLookup<NetworkId>();
var loadingInProgress = GetComponentLookup<LevelLoadingInProgress>();
// TODO: Level number not being used for anything atm
Entities.ForEach((Entity entity, in ClientReady level, in ReceiveRpcCommandRequest req) =>
{
UnityEngine.Debug.Log($"Client {connections[req.SourceConnection].Value} finished loading {level.LevelIndex}.");
ecb.AddComponent<LevelLoadingDone>(req.SourceConnection);
if (!loadingInProgress.HasComponent(req.SourceConnection))
UnityEngine.Debug.LogError("Ready client was never marked as starting level loading");
ecb.DestroyEntity(entity);
}).Run();
ecb.Playback(EntityManager);
var readyCount = m_ClientsReadyQuery.CalculateEntityCount();
var loadingCount = m_ClientsLoadingQuery.CalculateEntityCount();
// All scenes finished loading and clients are ready
var levelState = SystemAPI.GetSingleton<LevelSyncStateComponent>();
if (levelState.State == LevelSyncState.LevelLoaded && loadingCount == readyCount)
{
UnityEngine.Debug.Log("Server subscenes finished loading and all clients are ready");
var conQuery = EntityManager.CreateEntityQuery(ComponentType.ReadOnly<NetworkId>());
var cons = conQuery.ToEntityArray(Allocator.Temp);
var conIds = conQuery.ToComponentDataArray<NetworkId>(Allocator.Temp);
for (int i = 0; i < cons.Length; ++i)
{
if (!EntityManager.HasComponent<NetworkStreamInGame>(cons[i]))
{
UnityEngine.Debug.Log($"[{World.Name}] Enable sync on {conIds[i].Value}");
EntityManager.AddComponent<NetworkStreamInGame>(cons[i]);
}
}
levelState.State = LevelSyncState.Idle;
SystemAPI.SetSingleton(levelState);
ecb = new EntityCommandBuffer(Allocator.Temp);
FixedString64Bytes world = World.Name;
Entities.WithAll<NetworkId>().ForEach((Entity entity) =>
{
ecb.RemoveComponent<LevelLoadingInProgress>(entity);
ecb.RemoveComponent<LevelLoadingDone>(entity);
}).Run();
ecb.Playback(EntityManager);
}
}
}
public static class NetcodeLevelSync
{
public static void TriggerClientLoadLevel(int level, World serverWorld)
{
// Trigger level load on all clients
var rpcCmd = serverWorld.EntityManager.CreateEntity();
serverWorld.EntityManager.AddComponentData(rpcCmd, new ClientLoadLevel(){LevelIndex = level});
serverWorld.EntityManager.AddComponent<SendRpcCommandRequest>(rpcCmd);
// Mark each connection as being in progress of loading
var connectionsQuery =
serverWorld.EntityManager.CreateEntityQuery(ComponentType.ReadOnly<NetworkId>());
var connectionEntities = connectionsQuery.ToEntityArray(Allocator.Temp);
foreach (var connection in connectionEntities)
{
serverWorld.EntityManager.AddComponentData(connection, new LevelLoadingInProgress());
}
}
public static void SetLevelState(LevelSyncState state, World world)
{
var levelStateQuery = world.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<LevelSyncStateComponent>());
var levelStateEntity = levelStateQuery.ToEntityArray(Allocator.Temp);
var levelStateData = levelStateQuery.ToComponentDataArray<LevelSyncStateComponent>(Allocator.Temp);
var levelState = levelStateData[0];
levelState.State = state;
world.EntityManager.SetComponentData(levelStateEntity[0], levelState);
}
}