Files
RobustToolbox/Robust.Shared/GameObjects/ComponentManager.cs
Paul Ritter 80f9f24243 Serialization v3 aka constant suffering (#1606)
* oops

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

* adds more serializers, actually implements them & removes most of the last of the old system

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

* Temporary fix for SERV3: Turn populate delegate into regular code

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

* Convert every non generic serializer to the new format, general fixes

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

* Create more deserialized types and store prototypes with their deserialized results

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

* Update all prototypes to have a parent and have consistent id/parent properties

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

* Add copying for value types and arrays. Fix copy internal for primitives, enums and strings

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

* Add checks for the serialization manager initializing and already being initialized

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

* Fix sprite component norot not being default true like in latest master

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

* Make yaml linter 5 times faster (~80% less execution time)

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

* Revert "Make yaml linter 5 times faster (~80% less execution time)"

This reverts commit 2ee4cc2c26.

* Add serialization to RobustServerSimulation and mock reflection methods
Fixes container tests

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

* Add IEquatable, IComparable, ToString and operators to DataPosition
Rename it to NodeMark
Make it a readonly struct

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

* Make dependencies an argument instead of a property on the serialization manager

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Vera Aguilera Puerto <zddm@outlook.es>
2021-03-04 15:59:14 -08:00

587 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.Shared.Exceptions;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
namespace Robust.Shared.GameObjects
{
/// <inheritdoc />
public class ComponentManager : IComponentManager
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IComponentDependencyManager _componentDependencyManager = default!;
#if EXCEPTION_TOLERANCE
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
private const int TypeCapacity = 32;
private const int ComponentCollectionCapacity = 1024;
private readonly Dictionary<uint, Dictionary<EntityUid, Component>> _entNetIdDict
= new();
private readonly Dictionary<Type, Dictionary<EntityUid, Component>> _entTraitDict
= new();
private readonly HashSet<Component> _deleteSet = new(TypeCapacity);
private UniqueIndexHkm<EntityUid, Component> _entCompIndex =
new(ComponentCollectionCapacity);
/// <inheritdoc />
public event EventHandler<ComponentEventArgs>? ComponentAdded;
/// <inheritdoc />
public event EventHandler<ComponentEventArgs>? ComponentRemoved;
/// <inheritdoc />
public event EventHandler<ComponentEventArgs>? ComponentDeleted;
public void Initialize()
{
FillComponentDict();
}
/// <inheritdoc />
public void Clear()
{
_entNetIdDict.Clear();
_entTraitDict.Clear();
_entCompIndex.Clear();
_deleteSet.Clear();
FillComponentDict();
}
#region Component Management
/// <inheritdoc />
public T AddComponent<T>(IEntity entity) where T : Component, new()
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
var newComponent = _componentFactory.GetComponent<T>();
newComponent.Owner = entity;
AddComponent(entity, newComponent);
return newComponent;
}
/// <inheritdoc />
public void AddComponent<T>(IEntity entity, T component, bool overwrite = false) where T : Component
{
if (entity == null || !entity.IsValid())
throw new ArgumentException("Entity is not valid.", nameof(entity));
if (component == null) throw new ArgumentNullException(nameof(component));
if (component.Owner != entity) throw new InvalidOperationException("Component is not owned by entity.");
var uid = entity.Uid;
// get interface aliases for mapping
var reg = _componentFactory.GetRegistration(component);
// Check that there are no overlapping references.
foreach (var type in reg.References)
{
var dict = _entTraitDict[type];
if (!dict.TryGetValue(uid, out var duplicate))
continue;
if (!overwrite && !duplicate.Deleted)
throw new InvalidOperationException(
$"Component reference type {type} already occupied by {duplicate}");
// these two components are required on all entities and cannot be overwritten.
if (duplicate is ITransformComponent || duplicate is IMetaDataComponent)
throw new InvalidOperationException("Tried to overwrite a protected component.");
RemoveComponentImmediate((Component) duplicate);
}
// add the component to the grid
foreach (var type in reg.References)
{
_entTraitDict[type].Add(uid, component);
_entCompIndex.Add(uid, component);
}
// add the component to the netId grid
if (component.NetID != null)
{
// the main comp grid keeps this in sync
var netId = component.NetID.Value;
_entNetIdDict[netId].Add(uid, component);
// mark the component as dirty for networking
component.Dirty();
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component));
}
_componentDependencyManager.OnComponentAdd(entity, component);
component.OnAdd();
if (!entity.Initialized && !entity.Initializing) return;
component.Initialize();
DebugTools.Assert(component.Initialized, "Component is not initialized after calling Initialize(). "
+ "Did you forget to call base.Initialize() in an override?");
if (entity.Initialized)
{
component.Running = true;
}
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent<T>(EntityUid uid)
{
RemoveComponent(uid, typeof(T));
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent(EntityUid uid, Type type)
{
RemoveComponentDeferred((Component) GetComponent(uid, type), uid, false);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent(EntityUid uid, uint netId)
{
RemoveComponentDeferred((Component) GetComponent(uid, netId), uid, false);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent(EntityUid uid, IComponent component)
{
if (component == null) throw new ArgumentNullException(nameof(component));
if (component.Owner == null || component.Owner.Uid != uid)
throw new InvalidOperationException("Component is not owned by entity.");
RemoveComponentDeferred((Component) component, uid, false);
}
private static IEnumerable<Component> InSafeOrder(IEnumerable<Component> comps, bool forCreation = false)
{
static int Sequence(IComponent x)
=> x switch
{
ITransformComponent _ => 0,
IMetaDataComponent _ => 1,
IPhysicsComponent _ => 2,
_ => int.MaxValue
};
return forCreation
? comps.OrderBy(Sequence)
: comps.OrderByDescending(Sequence);
}
/// <inheritdoc />
public void RemoveComponents(EntityUid uid)
{
foreach (var comp in InSafeOrder(_entCompIndex[uid]))
{
RemoveComponentDeferred(comp, uid, false);
}
}
/// <inheritdoc />
public void DisposeComponents(EntityUid uid)
{
foreach (var comp in InSafeOrder(_entCompIndex[uid]))
{
RemoveComponentDeferred(comp, uid, true);
}
// DisposeComponents means the entity is getting deleted.
// Safe to wipe the entity out of the index.
_entCompIndex.Remove(uid);
}
private void RemoveComponentDeferred(Component component, EntityUid uid, bool removeProtected)
{
if (component == null) throw new ArgumentNullException(nameof(component));
if (component.Deleted) return;
#if EXCEPTION_TOLERANCE
try
{
#endif
// these two components are required on all entities and cannot be removed normally.
if (!removeProtected && (component is ITransformComponent || component is IMetaDataComponent))
{
DebugTools.Assert("Tried to remove a protected component.");
return;
}
if (!_deleteSet.Add(component))
{
// already deferred deletion
return;
}
component.Running = false;
component.OnRemove();
_componentDependencyManager.OnComponentRemove(_entityManager.GetEntity(uid), component);
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component));
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_runtimeLog.LogException(e,
$"RemoveComponentDeferred, owner={component.Owner}, type={component.GetType()}");
}
#endif
}
private void RemoveComponentImmediate(Component component)
{
if (component == null) throw new ArgumentNullException(nameof(component));
if (!component.Deleted)
{
// these two components are required on all entities and cannot be removed.
if (component is ITransformComponent || component is IMetaDataComponent)
{
DebugTools.Assert("Tried to remove a protected component.");
return;
}
component.Running = false;
component.OnRemove(); // Sets delete
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component));
}
DeleteComponent(component);
}
/// <inheritdoc />
public void CullRemovedComponents()
{
foreach (var component in InSafeOrder(_deleteSet))
{
DeleteComponent(component);
}
_deleteSet.Clear();
}
private void DeleteComponent(Component component)
{
var reg = _componentFactory.GetRegistration(component.GetType());
var entityUid = component.Owner.Uid;
foreach (var refType in reg.References)
{
_entTraitDict[refType].Remove(entityUid);
}
if (component.NetID == null) return;
var netId = component.NetID.Value;
_entNetIdDict[netId].Remove(entityUid);
_entCompIndex.Remove(entityUid, component);
// mark the owning entity as dirty for networking
component.Owner.Dirty();
ComponentDeleted?.Invoke(this, new DeletedComponentEventArgs(component));
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent<T>(EntityUid uid)
{
return HasComponent(uid, typeof(T));
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(EntityUid uid, Type type)
{
var dict = _entTraitDict[type];
return dict.TryGetValue(uid, out var comp) && !comp.Deleted;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(EntityUid uid, uint netId)
{
var dict = _entNetIdDict[netId];
return dict.TryGetValue(uid, out var comp) && !comp.Deleted;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetComponent<T>(EntityUid uid)
{
return (T) GetComponent(uid, typeof(T));
}
/// <inheritdoc />
public IComponent GetComponent(EntityUid uid, Type type)
{
// ReSharper disable once InvertIf
var dict = _entTraitDict[type];
if (dict.TryGetValue(uid, out var comp))
{
if (!comp.Deleted)
{
return comp;
}
}
var ent = _entityManager.GetEntity(uid);
throw new KeyNotFoundException($"Entity {ent} does not have a component of type {type}");
}
/// <inheritdoc />
public IComponent GetComponent(EntityUid uid, uint netId)
{
// ReSharper disable once InvertIf
var dict = _entNetIdDict[netId];
if (dict.TryGetValue(uid, out var comp))
{
if (!comp.Deleted)
{
return comp;
}
}
var ent = _entityManager.GetEntity(uid);
throw new KeyNotFoundException($"Entity {ent} does not have a component of NetID {netId}");
}
/// <inheritdoc />
public bool TryGetComponent<T>(EntityUid uid, [NotNullWhen(true)] out T component)
{
if (TryGetComponent(uid, typeof(T), out var comp))
{
if (!comp.Deleted)
{
component = (T) comp;
return true;
}
}
component = default!;
return false;
}
/// <inheritdoc />
public bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component)
{
var dict = _entTraitDict[type];
if (dict.TryGetValue(uid, out var comp))
{
if (!comp.Deleted)
{
component = comp;
return true;
}
}
component = null;
return false;
}
/// <inheritdoc />
public bool TryGetComponent(EntityUid uid, uint netId, [NotNullWhen(true)] out IComponent? component)
{
var dict = _entNetIdDict[netId];
if (dict.TryGetValue(uid, out var comp))
{
if (!comp.Deleted)
{
component = comp;
return true;
}
}
component = null;
return false;
}
/// <inheritdoc />
public IEnumerable<IComponent> GetComponents(EntityUid uid)
{
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (Component comp in _entCompIndex[uid].ToArray())
{
if (comp.Deleted) continue;
yield return comp;
}
}
/// <inheritdoc />
public IEnumerable<T> GetComponents<T>(EntityUid uid)
{
var comps = _entCompIndex[uid];
foreach (var comp in comps)
{
if (comp.Deleted || !(comp is T tComp)) continue;
yield return tComp;
}
}
/// <inheritdoc />
public IEnumerable<IComponent> GetNetComponents(EntityUid uid)
{
var comps = _entCompIndex[uid];
foreach (var comp in comps)
{
if (comp.Deleted || comp.NetID == null) continue;
yield return comp;
}
}
#region Join Functions
/// <inheritdoc />
public IEnumerable<T> EntityQuery<T>(bool includePaused = false)
{
var comps = _entTraitDict[typeof(T)];
foreach (var comp in comps.Values)
{
if (comp.Deleted || !includePaused && comp.Paused) continue;
yield return (T) (object) comp;
}
}
/// <inheritdoc />
public IEnumerable<(TComp1, TComp2)> EntityQuery<TComp1, TComp2>(bool includePaused = false)
where TComp1 : IComponent
where TComp2 : IComponent
{
// this would prob be faster if trait1 was a list (or an array of structs hue).
var trait1 = _entTraitDict[typeof(TComp1)];
var trait2 = _entTraitDict[typeof(TComp2)];
// you really want trait1 to be the smaller set of components
foreach (var kvComp in trait1)
{
var uid = kvComp.Key;
if (!trait2.TryGetValue(uid, out var t2Comp) || t2Comp.Deleted || !includePaused && kvComp.Value.Paused)
continue;
yield return ((TComp1) (object) kvComp.Value, (TComp2) (object) t2Comp);
}
}
/// <inheritdoc />
public IEnumerable<(TComp1, TComp2, TComp3)> EntityQuery<TComp1, TComp2, TComp3>(bool includePaused = false)
where TComp1 : IComponent
where TComp2 : IComponent
where TComp3 : IComponent
{
var trait1 = _entTraitDict[typeof(TComp1)];
var trait2 = _entTraitDict[typeof(TComp2)];
var trait3 = _entTraitDict[typeof(TComp3)];
foreach (var kvComp in trait1)
{
var uid = kvComp.Key;
if (!trait2.TryGetValue(uid, out var t2Comp) || t2Comp.Deleted || !includePaused && kvComp.Value.Paused)
continue;
if (!trait3.TryGetValue(uid, out var t3Comp) || t3Comp.Deleted)
continue;
yield return ((TComp1) (object) kvComp.Value,
(TComp2) (object) t2Comp,
(TComp3) (object) t3Comp);
}
}
/// <inheritdoc />
public IEnumerable<(TComp1, TComp2, TComp3, TComp4)> EntityQuery<TComp1, TComp2, TComp3, TComp4>(bool includePaused = false)
where TComp1 : IComponent
where TComp2 : IComponent
where TComp3 : IComponent
where TComp4 : IComponent
{
var trait1 = _entTraitDict[typeof(TComp1)];
var trait2 = _entTraitDict[typeof(TComp2)];
var trait3 = _entTraitDict[typeof(TComp3)];
var trait4 = _entTraitDict[typeof(TComp4)];
foreach (var kvComp in trait1)
{
var uid = kvComp.Key;
if (!trait2.TryGetValue(uid, out var t2Comp) || t2Comp.Deleted || !includePaused && kvComp.Value.Paused)
continue;
if (!trait3.TryGetValue(uid, out var t3Comp) || t3Comp.Deleted)
continue;
if (!trait4.TryGetValue(uid, out var t4Comp) || t4Comp.Deleted)
continue;
yield return ((TComp1) (object) kvComp.Value,
(TComp2) (object) t2Comp,
(TComp3) (object) t3Comp,
(TComp4) (object) t4Comp);
}
}
#endregion
/// <inheritdoc />
public IEnumerable<IComponent> GetAllComponents(Type type, bool includePaused = false)
{
var comps = _entTraitDict[type];
foreach (var comp in comps.Values)
{
if (comp.Deleted || !includePaused && comp.Paused) continue;
yield return comp;
}
}
#endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FillComponentDict()
{
foreach (var refType in _componentFactory.GetAllRefTypes())
{
_entTraitDict.Add(refType, new Dictionary<EntityUid, Component>());
}
foreach (var netId in _componentFactory.GetAllNetIds())
{
_entNetIdDict.Add(netId, new Dictionary<EntityUid, Component>());
}
}
}
}