using System; using System.Collections.Generic; using System.Linq; using Robust.Client.Interfaces.GameObjects; using Robust.Shared.Exceptions; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Utility; namespace Robust.Client.GameObjects { /// /// Manager for entities -- controls things like template loading and instantiation /// public sealed class ClientEntityManager : EntityManager, IClientEntityManager { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IComponentFactory _compFactory = default!; #if EXCEPTION_TOLERANCE [Dependency] private readonly IRuntimeLog _runtimeLog = default!; #endif private int _nextClientEntityUid = EntityUid.ClientUid + 1; public override void Startup() { base.Startup(); if (Started) { throw new InvalidOperationException("Startup() called multiple times"); } EntitySystemManager.Initialize(); Started = true; } public List ApplyEntityStates(EntityState[]? curEntStates, IEnumerable? deletions, EntityState[]? nextEntStates) { var toApply = new Dictionary(); var toInitialize = new List(); var created = new List(); deletions ??= new EntityUid[0]; if (curEntStates != null && curEntStates.Length != 0) { foreach (var es in curEntStates) { //Known entities if (Entities.TryGetValue(es.Uid, out var entity)) { toApply.Add(entity, (es, null)); } else //Unknown entities { var metaState = (MetaDataComponentState?) es.ComponentStates ?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA); if (metaState == null) { throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!"); } var newEntity = CreateEntity(metaState.PrototypeId, es.Uid); toApply.Add(newEntity, (es, null)); toInitialize.Add(newEntity); created.Add(newEntity.Uid); } } } if (nextEntStates != null && nextEntStates.Length != 0) { foreach (var es in nextEntStates) { if (Entities.TryGetValue(es.Uid, out var entity)) { if (toApply.TryGetValue(entity, out var state)) { toApply[entity] = (state.Item1, es); } else { toApply[entity] = (null, es); } } } } // Make sure this is done after all entities have been instantiated. foreach (var kvStates in toApply) { var ent = kvStates.Key; var entity = (Entity) ent; HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1, kvStates.Value.Item2); } foreach (var kvp in toApply) { UpdateEntityTree(kvp.Key); } foreach (var id in deletions) { DeleteEntity(id); } #if EXCEPTION_TOLERANCE HashSet brokenEnts = new HashSet(); #endif foreach (var entity in toInitialize) { #if EXCEPTION_TOLERANCE try { #endif InitializeEntity(entity); #if EXCEPTION_TOLERANCE } catch (Exception e) { Logger.ErrorS("state", $"Server entity threw in Init: uid={entity.Uid}, proto={entity.Prototype}\n{e}"); brokenEnts.Add(entity); } #endif } foreach (var entity in toInitialize) { #if EXCEPTION_TOLERANCE if(brokenEnts.Contains(entity)) continue; try { #endif StartEntity(entity); #if EXCEPTION_TOLERANCE } catch (Exception e) { Logger.ErrorS("state", $"Server entity threw in Start: uid={entity.Uid}, proto={entity.Prototype}\n{e}"); brokenEnts.Add(entity); } #endif } foreach (var entity in toInitialize) { #if EXCEPTION_TOLERANCE if(brokenEnts.Contains(entity)) continue; #endif UpdateEntityTree(entity); } #if EXCEPTION_TOLERANCE foreach (var entity in brokenEnts) { entity.Delete(); } #endif return created; } /// public override IEntity CreateEntityUninitialized(string? prototypeName) { return CreateEntity(prototypeName); } /// public override IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates) { var newEntity = CreateEntity(prototypeName, GenerateEntityUid()); if (TryGetEntity(coordinates.EntityId, out var entity)) { newEntity.Transform.AttachParent(entity); newEntity.Transform.Coordinates = coordinates; } return newEntity; } /// public override IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates) { var newEntity = CreateEntity(prototypeName, GenerateEntityUid()); newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId)); newEntity.Transform.WorldPosition = coordinates.Position; return newEntity; } /// public override IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates) { var newEnt = CreateEntityUninitialized(protoName, coordinates); InitializeAndStartEntity((Entity) newEnt); UpdateEntityTree(newEnt); return newEnt; } /// public override IEntity SpawnEntity(string? protoName, MapCoordinates coordinates) { var entity = CreateEntityUninitialized(protoName, coordinates); InitializeAndStartEntity((Entity) entity); UpdateEntityTree(entity); return entity; } /// public override IEntity SpawnEntityNoMapInit(string? protoName, EntityCoordinates coordinates) { return SpawnEntity(protoName, coordinates); } protected override EntityUid GenerateEntityUid() { return new EntityUid(_nextClientEntityUid++); } private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState, EntityState? nextState) { var compStateWork = new Dictionary(); var entityUid = entity.Uid; if (curState?.ComponentChanges != null) { foreach (var compChange in curState.ComponentChanges) { if (compChange.Deleted) { if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp)) { compMan.RemoveComponent(entityUid, comp); } } else { if (compMan.HasComponent(entityUid, compChange.NetID)) continue; var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!); newComp.Owner = entity; compMan.AddComponent(entity, newComp, true); } } } if (curState?.ComponentStates != null) { foreach (var compState in curState.ComponentStates) { compStateWork[compState.NetID] = (compState, null); } } if (nextState?.ComponentStates != null) { foreach (var compState in nextState.ComponentStates) { if (compStateWork.TryGetValue(compState.NetID, out var state)) { compStateWork[compState.NetID] = (state.curState, compState); } else { compStateWork[compState.NetID] = (null, compState); } } } foreach (var (netId, (cur, next)) in compStateWork) { if (compMan.TryGetComponent(entityUid, netId, out var component)) { try { component.HandleComponentState(cur, next); } catch (Exception e) { var wrapper = new ComponentStateApplyException( $"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e); #if EXCEPTION_TOLERANCE _runtimeLog.LogException(wrapper, "Component state apply"); #else throw wrapper; #endif } } else { // The component can be null here due to interp. // Because the NEXT state will have a new component, but this one doesn't yet. // That's fine though. if (cur == null) { continue; } var eUid = entityUid; var eRegisteredNetUidName = _compFactory.GetRegistration(netId).Name; DebugTools.Assert( $"Component does not exist for state: entUid={eUid}, expectedNetId={netId}, expectedName={eRegisteredNetUidName}"); } } } } }