Skip to content

Commit

Permalink
Prevent entity spawning from stopping when it errors. Implement rando…
Browse files Browse the repository at this point in the history
…mly-generated-on-the-fly entities
  • Loading branch information
tornac1234 committed Dec 7, 2023
1 parent 31f9848 commit 61f2a7c
Show file tree
Hide file tree
Showing 20 changed files with 219 additions and 79 deletions.
15 changes: 13 additions & 2 deletions NitroxClient/GameLogic/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacke
entitySpawnersByType[typeof(InventoryItemEntity)] = new InventoryItemEntitySpawner();
entitySpawnersByType[typeof(WorldEntity)] = new WorldEntitySpawner(playerManager, localPlayer, this);
entitySpawnersByType[typeof(PlaceholderGroupWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)];
entitySpawnersByType[typeof(PrefabPlaceholderEntity)] = entitySpawnersByType[typeof(WorldEntity)];
entitySpawnersByType[typeof(EscapePodWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)];
entitySpawnersByType[typeof(PlayerWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)];
entitySpawnersByType[typeof(VehicleWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)];
Expand Down Expand Up @@ -99,8 +100,18 @@ public void BroadcastEntitySpawnedByClient(WorldEntity entity)

private IEnumerator SpawnNewEntities()
{
yield return SpawnBatchAsync(EntitiesToSpawn).OnYieldError(Log.Error);
spawningEntities = false;
bool restarted = false;
yield return SpawnBatchAsync(EntitiesToSpawn).OnYieldError(exception =>
{
Log.Error(exception);
if (EntitiesToSpawn.Count > 0)
{
restarted = true;
// It's safe to run a new time because the processed entity is removed first so it won't infinitely throw errors
CoroutineHost.StartCoroutine(SpawnNewEntities());
}
});
spawningEntities = restarted;
}

public void EnqueueEntitiesToSpawn(List<Entity> entitiesToEnqueue)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using NitroxClient.GameLogic.Spawning.Metadata;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Entities;
Expand All @@ -20,12 +19,14 @@ public class PlaceholderGroupWorldEntitySpawner : IWorldEntitySpawner
private readonly Entities entities;
private readonly WorldEntitySpawnerResolver spawnerResolver;
private readonly DefaultWorldEntitySpawner defaultSpawner;
private readonly PrefabPlaceholderEntitySpawner prefabPlaceholderEntitySpawner;

public PlaceholderGroupWorldEntitySpawner(Entities entities, WorldEntitySpawnerResolver spawnerResolver, DefaultWorldEntitySpawner defaultSpawner)
public PlaceholderGroupWorldEntitySpawner(Entities entities, WorldEntitySpawnerResolver spawnerResolver, DefaultWorldEntitySpawner defaultSpawner, PrefabPlaceholderEntitySpawner prefabPlaceholderEntitySpawner)
{
this.entities = entities;
this.spawnerResolver = spawnerResolver;
this.defaultSpawner = defaultSpawner;
this.prefabPlaceholderEntitySpawner = prefabPlaceholderEntitySpawner;
}

public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
Expand Down Expand Up @@ -67,19 +68,22 @@ public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, E
switch (current)
{
case PrefabPlaceholderEntity prefabEntity:
PrefabPlaceholder placeholder = prefabPlaceholderGroup.prefabPlaceholders[prefabEntity.ComponentIndex];
if (!SpawnWorldEntityChildSync(prefabEntity, cellRoot, placeholder.transform.parent.gameObject, childResult, out IEnumerator asyncInstructions))
if (!prefabPlaceholderEntitySpawner.SpawnSync(prefabEntity, groupObject, cellRoot, childResult))
{
yield return asyncInstructions;
yield return prefabPlaceholderEntitySpawner.SpawnAsync(prefabEntity, groupObject, cellRoot, childResult);
}
break;

case PlaceholderGroupWorldEntity groupEntity:
placeholder = prefabPlaceholderGroup.prefabPlaceholders[groupEntity.ComponentIndex];
PrefabPlaceholder placeholder = prefabPlaceholderGroup.prefabPlaceholders[groupEntity.ComponentIndex];
yield return SpawnAsync(groupEntity, placeholder.transform.parent.gameObject, cellRoot, childResult);
break;

default:
Log.Error($"[{nameof(PlaceholderGroupWorldEntitySpawner)}] Found a child entity to spawn with an unmanaged type: {entity.GetType()}");
if (!SpawnWorldEntityChildSync(entity, cellRoot, parentById.GetOrDefault(current.ParentId, null), childResult, out IEnumerator asyncInstructions))
{
yield return asyncInstructions;
}
break;
}

Expand All @@ -93,15 +97,15 @@ public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, E
entities.MarkAsSpawned(current);
parentById[current.Id] = childObject;
EntityMetadataProcessor.ApplyMetadata(childObject, current.Metadata);

// PlaceholderGroupWorldEntity's children spawning is already handled by this function which is called recursively
if (current is not PlaceholderGroupWorldEntity)
{
continue;
}
NitroxEntity.SetNewId(childObject, current.Id);
// Adding children to be spawned by this loop
foreach (Entity slotEntityChild in current.ChildEntities)
{
stack.Push(slotEntityChild);
// Adding children to be spawned by this loop
foreach (Entity slotEntityChild in current.ChildEntities)
{
stack.Push(slotEntityChild);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Collections;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.Util;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;

namespace NitroxClient.GameLogic.Spawning.WorldEntities;

public class PrefabPlaceholderEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
{
private readonly DefaultWorldEntitySpawner defaultEntitySpawner;

public PrefabPlaceholderEntitySpawner(DefaultWorldEntitySpawner defaultEntitySpawner)
{
this.defaultEntitySpawner = defaultEntitySpawner;
}

// TODO: Clean the spawners (move to to Setup() and IsValidOrError() after rebase)
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
{
if (entity is not PrefabPlaceholderEntity prefabEntity)
{
yield break;
}
if (!parent.Value || !parent.Value.TryGetComponent(out PrefabPlaceholdersGroup group))
{
Log.Error($"[{nameof(PrefabPlaceholderEntity)}] Can't find a {nameof(PrefabPlaceholdersGroup)} on parent for {entity.Id}");
yield break;
}
PrefabPlaceholder placeholder = group.prefabPlaceholders[prefabEntity.ComponentIndex];
yield return defaultEntitySpawner.SpawnAsync(entity, placeholder.transform.parent.gameObject, cellRoot, result);
if (!result.value.HasValue)
{
yield break;
}
SetupObject(entity, result.value.Value);
}

public bool SpawnsOwnChildren() => false;

public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
{
if (entity is not PrefabPlaceholderEntity prefabEntity)
{
return true;
}
if (!parent.Value || !parent.Value.TryGetComponent(out PrefabPlaceholdersGroup group))
{
Log.Error($"[{nameof(PrefabPlaceholderEntity)}] Can't find a {nameof(PrefabPlaceholdersGroup)} on parent for {entity.Id}");
return true;
}
PrefabPlaceholder placeholder = group.prefabPlaceholders[prefabEntity.ComponentIndex];
if (!defaultEntitySpawner.SpawnSync(entity, placeholder.transform.parent.gameObject, cellRoot, result))
{
return false;
}
SetupObject(entity, result.value.Value);
return true;
}

private void SetupObject(WorldEntity entity, GameObject gameObject)
{
gameObject.transform.localPosition = entity.Transform.LocalPosition.ToUnity();
gameObject.transform.localRotation = entity.Transform.LocalRotation.ToUnity();
gameObject.transform.localScale = entity.Transform.LocalScale.ToUnity();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class SerializedWorldEntitySpawner : IWorldEntitySpawner, IWorldEntitySyn
/// <summary>
/// Contains the only types we allow the server to instantiate on clients (for security concerns)
/// </summary>
private readonly List<Type> typesWhitelist = new()
private readonly HashSet<Type> typesWhitelist = new()
{
typeof(Light), typeof(DisableBeforeExplosion), typeof(BoxCollider), typeof(SphereCollider)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public class WorldEntitySpawnerResolver
private readonly DefaultWorldEntitySpawner defaultEntitySpawner = new();
private readonly VehicleWorldEntitySpawner vehicleWorldEntitySpawner;

private readonly PlaceholderGroupWorldEntitySpawner prefabWorldEntitySpawner;
private readonly PrefabPlaceholderEntitySpawner prefabPlaceholderEntitySpawner;
private readonly PlaceholderGroupWorldEntitySpawner placeholderGroupWorldEntitySpawner;
private readonly PlayerWorldEntitySpawner playerWorldEntitySpawner;
private readonly SerializedWorldEntitySpawner serializedWorldEntitySpawner;

Expand All @@ -23,7 +24,8 @@ public WorldEntitySpawnerResolver(PlayerManager playerManager, ILocalNitroxPlaye
customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner();

vehicleWorldEntitySpawner = new(entities);
prefabWorldEntitySpawner = new PlaceholderGroupWorldEntitySpawner(entities, this, defaultEntitySpawner);
prefabPlaceholderEntitySpawner = new(defaultEntitySpawner);
placeholderGroupWorldEntitySpawner = new PlaceholderGroupWorldEntitySpawner(entities, this, defaultEntitySpawner, prefabPlaceholderEntitySpawner);
playerWorldEntitySpawner = new PlayerWorldEntitySpawner(playerManager, localPlayer);
serializedWorldEntitySpawner = new SerializedWorldEntitySpawner();
}
Expand All @@ -32,8 +34,10 @@ public IWorldEntitySpawner ResolveEntitySpawner(WorldEntity entity)
{
switch (entity)
{
case PrefabPlaceholderEntity:
return prefabPlaceholderEntitySpawner;
case PlaceholderGroupWorldEntity:
return prefabWorldEntitySpawner;
return placeholderGroupWorldEntitySpawner;
case PlayerWorldEntity:
return playerWorldEntitySpawner;
case VehicleWorldEntity:
Expand Down
19 changes: 16 additions & 3 deletions NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace NitroxClient.GameLogic.Spawning;

public class WorldEntitySpawner : EntitySpawner<WorldEntity>
public class WorldEntitySpawner : SyncEntitySpawner<WorldEntity>
{
private readonly WorldEntitySpawnerResolver worldEntitySpawnResolver;
private readonly Dictionary<Int3, BatchCells> batchCellsById;
Expand All @@ -30,8 +30,6 @@ public WorldEntitySpawner(PlayerManager playerManager, ILocalNitroxPlayer localP

protected override IEnumerator SpawnAsync(WorldEntity entity, TaskResult<Optional<GameObject>> result)
{
LargeWorldStreamer.main.cellManager.UnloadBatchCells(entity.AbsoluteEntityCell.CellId.ToUnity()); // Just in case

EntityCell cellRoot = EnsureCell(entity);
if (cellRoot == null)
{
Expand All @@ -58,6 +56,21 @@ protected override bool SpawnsOwnChildren(WorldEntity entity)
return entitySpawner.SpawnsOwnChildren();
}

protected override bool SpawnSync(WorldEntity entity, TaskResult<Optional<GameObject>> result)
{
EntityCell cellRoot = EnsureCell(entity);
if (cellRoot == null)
{
// Error logging is done in EnsureCell
return true;
}

Optional<GameObject> parent = (entity.ParentId != null) ? NitroxEntity.GetObjectFrom(entity.ParentId) : Optional.Empty;
IWorldEntitySpawner entitySpawner = worldEntitySpawnResolver.ResolveEntitySpawner(entity);

return entitySpawner is IWorldEntitySyncSpawner syncSpawner && syncSpawner.SpawnSync(entity, parent, cellRoot, result);
}

private EntityCell EnsureCell(WorldEntity entity)
{
EntityCell entityCell;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace NitroxModel_Subnautica.DataStructures.GameLogic.Entities;

public class SubnauticaUwePrefabFactory : UwePrefabFactory
public class SubnauticaUwePrefabFactory : IUwePrefabFactory
{
private readonly LootDistributionData lootDistributionData;
private readonly Dictionary<string, List<UwePrefab>> cache = new();
Expand All @@ -18,7 +18,7 @@ public SubnauticaUwePrefabFactory(string lootDistributionJson)
lootDistributionData = GetLootDistributionData(lootDistributionJson);
}

public override List<UwePrefab> GetPossiblePrefabs(string biome)
public List<UwePrefab> GetPossiblePrefabs(string biome)
{
List<UwePrefab> prefabs = new();
if (biome == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace NitroxModel_Subnautica.DataStructures.GameLogic.Entities;

public class SubnauticaUweWorldEntityFactory : UweWorldEntityFactory
public class SubnauticaUweWorldEntityFactory : IUweWorldEntityFactory
{
private readonly Dictionary<string, WorldEntityInfo> worldEntitiesByClassId;

Expand All @@ -13,7 +13,7 @@ public SubnauticaUweWorldEntityFactory(Dictionary<string, WorldEntityInfo> world
this.worldEntitiesByClassId = worldEntitiesByClassId;
}

public override bool TryFind(string classId, out UweWorldEntity uweWorldEntity)
public bool TryFind(string classId, out UweWorldEntity uweWorldEntity)
{
if (worldEntitiesByClassId.TryGetValue(classId, out WorldEntityInfo worldEntityInfo))
{
Expand Down
8 changes: 1 addition & 7 deletions NitroxModel/DataStructures/GameLogic/Entities/UwePrefab.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
namespace NitroxModel.DataStructures.GameLogic.Entities;

public record struct UwePrefab(string ClassId, int Count, float Probability, bool IsFragment)
{
public string ClassId { get; } = ClassId;
public int Count { get; } = Count;
public float Probability { get; set; } = Probability;
public bool IsFragment { get; } = IsFragment;
}
public readonly record struct UwePrefab(string ClassId, int Count, float Probability, bool IsFragment);
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System.Collections.Generic;
using System.Collections.Generic;

namespace NitroxModel.DataStructures.GameLogic.Entities
namespace NitroxModel.DataStructures.GameLogic.Entities;

public interface IUwePrefabFactory
{
public abstract class UwePrefabFactory
{
public abstract List<UwePrefab> GetPossiblePrefabs(string biomeType);
}
public abstract List<UwePrefab> GetPossiblePrefabs(string biomeType);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace NitroxModel.DataStructures.GameLogic.Entities
namespace NitroxModel.DataStructures.GameLogic.Entities;

public interface IUweWorldEntityFactory
{
public abstract class UweWorldEntityFactory
{
public abstract bool TryFind(string classId, out UweWorldEntity uweWorldEntity);
}
public abstract bool TryFind(string classId, out UweWorldEntity uweWorldEntity);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace NitroxModel.DataStructures.GameLogic.Entities
[ProtoInclude(51, typeof(CellRootEntity))]
[ProtoInclude(52, typeof(GlobalRootEntity))]
[ProtoInclude(53, typeof(SerializedWorldEntity))]
[ProtoInclude(54, typeof(PrefabPlaceholderEntity))]
public class WorldEntity : Entity
{
public virtual AbsoluteEntityCell AbsoluteEntityCell => new(Transform.Position, Level);
Expand Down
13 changes: 6 additions & 7 deletions NitroxModel/DataStructures/GameLogic/Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ namespace NitroxModel.DataStructures.GameLogic
[Serializable]
[DataContract]
[ProtoInclude(60, typeof(PrefabChildEntity))]
[ProtoInclude(70, typeof(PrefabPlaceholderEntity))]
[ProtoInclude(80, typeof(InventoryEntity))]
[ProtoInclude(90, typeof(InventoryItemEntity))]
[ProtoInclude(100, typeof(PathBasedChildEntity))]
[ProtoInclude(110, typeof(InstalledBatteryEntity))]
[ProtoInclude(120, typeof(InstalledModuleEntity))]
[ProtoInclude(130, typeof(WorldEntity))]
[ProtoInclude(70, typeof(InventoryEntity))]
[ProtoInclude(80, typeof(InventoryItemEntity))]
[ProtoInclude(90, typeof(PathBasedChildEntity))]
[ProtoInclude(100, typeof(InstalledBatteryEntity))]
[ProtoInclude(110, typeof(InstalledModuleEntity))]
[ProtoInclude(120, typeof(WorldEntity))]
public abstract class Entity
{
[DataMember(Order = 1)]
Expand Down
Loading

0 comments on commit 61f2a7c

Please sign in to comment.