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>
This commit is contained in:
Paul Ritter
2021-03-05 00:59:14 +01:00
committed by GitHub
parent 93018c9843
commit 80f9f24243
202 changed files with 8614 additions and 5345 deletions

View File

@@ -0,0 +1,10 @@
using Microsoft.CodeAnalysis;
namespace Robust.Generators
{
public static class Diagnostics
{
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Generators;
namespace Robust.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MeansImplicitAssigmentSuppressor : DiagnosticSuppressor
{
const string MeansImplicitAssignmentAttribute = "Robust.Shared.MeansImplicitAssignmentAttribute";
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
var implAttr = context.Compilation.GetTypeByMetadataName(MeansImplicitAssignmentAttribute);
foreach (var reportedDiagnostic in context.ReportedDiagnostics)
{
if(reportedDiagnostic.Id != Diagnostics.MeansImplicitAssignment.SuppressedDiagnosticId) continue;
var node = reportedDiagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(reportedDiagnostic.Location.SourceSpan);
if (node == null) continue;
var symbol = context.GetSemanticModel(reportedDiagnostic.Location.SourceTree).GetDeclaredSymbol(node);
if (symbol == null || !symbol.GetAttributes().Any(a =>
a.AttributeClass?.GetAttributes().Any(attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, implAttr)) == true))
{
continue;
}
context.ReportSuppression(Suppression.Create(
Diagnostics.MeansImplicitAssignment,
reportedDiagnostic));
}
}
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Diagnostics.MeansImplicitAssignment);
}
}

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
/// <seealso cref="AnimationPlayerComponent"/>
public sealed class Animation
{
public readonly List<AnimationTrack> AnimationTracks = new();
public List<AnimationTrack> AnimationTracks { get; private set; } = new();
public TimeSpan Length { get; set; }
}

View File

@@ -15,7 +15,7 @@ namespace Robust.Client.Animations
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; private set; } = new();
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
{

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.Animations
/// </summary>
public abstract class AnimationTrackProperty : AnimationTrack
{
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; protected set; } = new();
/// <summary>
/// How to interpolate values when between two keyframes.

View File

@@ -15,7 +15,7 @@ namespace Robust.Client.Animations
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; private set; } = new();
// TODO: Should this layer key be per keyframe maybe?
/// <summary>

View File

@@ -27,6 +27,8 @@ using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
namespace Robust.Client
{
@@ -101,7 +103,6 @@ namespace Robust.Client
IoCManager.Register<IViewVariablesManagerInternal, ViewVariablesManager>();
IoCManager.Register<IClientConGroupController, ClientConGroupController>();
IoCManager.Register<IScriptClient, ScriptClient>();
//IoCManager.Register<IXamlCompiler, XamlCompiler>();
}
}
}

View File

@@ -12,7 +12,6 @@ using Robust.Client.Input;
using Robust.Client.Debugging;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
@@ -27,6 +27,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -44,7 +45,7 @@ namespace Robust.Client
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
@@ -154,6 +155,8 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
IoCManager.Resolve<ISerializationManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
@@ -163,7 +166,7 @@ namespace Robust.Client
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();

View File

@@ -17,7 +17,6 @@ namespace Robust.Client
RegisterReflection();
}
internal static void RegisterReflection()
{
// Gets a handle to the shared and the current (client) dll.

View File

@@ -54,7 +54,6 @@ namespace Robust.Client.GameObjects
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionExposeDataComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif

View File

@@ -4,8 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
@@ -15,13 +14,11 @@ namespace Robust.Client.GameObjects
{
[ViewVariables]
private Dictionary<object, object> data = new();
[ViewVariables]
[DataField("visuals")]
internal List<AppearanceVisualizer> Visualizers = new();
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private static bool _didRegisterSerializer;
[ViewVariables]
private bool _appearanceDirty;
@@ -107,18 +104,6 @@ namespace Robust.Client.GameObjects
_appearanceDirty = false;
}
public override void ExposeData(ObjectSerializer serializer)
{
if (!_didRegisterSerializer)
{
YamlObjectSerializer.RegisterTypeSerializer(typeof(AppearanceVisualizer),
new VisualizerTypeSerializer(_reflectionManager));
_didRegisterSerializer = true;
}
serializer.DataFieldCached(ref Visualizers, "visuals", new List<AppearanceVisualizer>());
}
public override void Initialize()
{
base.Initialize();
@@ -131,78 +116,6 @@ namespace Robust.Client.GameObjects
MarkDirty();
}
class VisualizerTypeSerializer : YamlObjectSerializer.TypeSerializer
{
private readonly IReflectionManager _reflectionManager;
public VisualizerTypeSerializer(IReflectionManager reflectionManager)
{
_reflectionManager = reflectionManager;
}
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)
{
var mapping = (YamlMappingNode) node;
var nodeType = mapping.GetNode("type");
switch (nodeType.AsString())
{
case SpriteLayerToggle.NAME:
var keyString = mapping.GetNode("key").AsString();
object key;
if (_reflectionManager.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
var layer = mapping.GetNode("layer").AsInt();
return new SpriteLayerToggle(key, layer);
default:
var visType = _reflectionManager.LooseGetType(nodeType.AsString());
if (!typeof(AppearanceVisualizer).IsAssignableFrom(visType))
{
throw new InvalidOperationException();
}
var vis = (AppearanceVisualizer) Activator.CreateInstance(visType)!;
vis.LoadData(mapping);
return vis;
}
}
public override YamlNode TypeToNode(object obj, YamlObjectSerializer serializer)
{
switch (obj)
{
case SpriteLayerToggle spriteLayerToggle:
YamlScalarNode key;
if (spriteLayerToggle.Key is Enum)
{
var name = spriteLayerToggle.Key.GetType().FullName;
key = new YamlScalarNode($"{name}.{spriteLayerToggle.Key}");
}
else
{
key = new YamlScalarNode(spriteLayerToggle.Key.ToString());
}
return new YamlMappingNode
{
{new YamlScalarNode("type"), new YamlScalarNode(SpriteLayerToggle.NAME)},
{new YamlScalarNode("key"), key},
{new YamlScalarNode("layer"), new YamlScalarNode(spriteLayerToggle.SpriteLayer.ToString())},
};
default:
// TODO: A proper way to do serialization here.
// I can't use the ExposeData system here since that's specific to entity serializers.
return new YamlMappingNode();
}
}
}
internal class SpriteLayerToggle : AppearanceVisualizer
{
@@ -223,15 +136,9 @@ namespace Robust.Client.GameObjects
/// Handles the visualization of data inside of an appearance component.
/// Implementations of this class are NOT bound to a specific entity, they are flyweighted across multiple.
/// </summary>
[ImplicitDataDefinitionForInheritors]
public abstract class AppearanceVisualizer
{
/// <summary>
/// Load data from the prototype declaring this visualizer, to configure settings and such.
/// </summary>
public virtual void LoadData(YamlMappingNode node)
{
}
/// <summary>
/// Initializes an entity to be managed by this appearance controller.
/// DO NOT assume this is your only entity. Visualizers are shared.

View File

@@ -3,7 +3,9 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
@@ -20,7 +22,9 @@ namespace Robust.Client.GameObjects
// Horrible hack to get around ordering issues.
private bool _setCurrentOnInitialize;
private bool _setDrawFovOnInitialize;
[DataField("drawFov")]
private bool _setDrawFovOnInitialize = true;
[DataField("zoom")]
private Vector2 _setZoomOnInitialize = Vector2.One/2f;
private Vector2 _offset = Vector2.Zero;
@@ -157,15 +161,6 @@ namespace Robust.Client.GameObjects
Current = false;
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref _setZoomOnInitialize, "zoom", Vector2.One/2f);
serializer.DataFieldCached(ref _setDrawFovOnInitialize, "drawFov", true);
}
/// <summary>
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
/// keep the view following the entity.

View File

@@ -1,105 +1,51 @@
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public class IconComponent : Component
public class IconComponent : Component, ISerializationHooks
{
public override string Name => "Icon";
public IDirectionalTextureProvider? Icon { get; private set; }
[Dependency] private readonly IResourceCache _resourceCache = default!;
[DataField("sprite")]
private ResourcePath? rsi;
[DataField("state")]
private string? stateID;
void ISerializationHooks.AfterDeserialization()
{
if (rsi != null && stateID != null)
{
Icon = new SpriteSpecifier.Rsi(rsi, stateID).Frame0();
}
}
public const string LogCategory = "go.comp.icon";
const string SerializationCache = "icon";
public override void ExposeData(ObjectSerializer serializer)
private static IRsiStateLike TextureForConfig(IconComponent compData, IResourceCache resourceCache)
{
base.ExposeData(serializer);
// TODO: Does this need writing?
if (serializer.Reading)
{
Icon = TextureForConfig(serializer, _resourceCache);
}
}
private static IRsiStateLike TextureForConfig(ObjectSerializer serializer, IResourceCache resourceCache)
{
DebugTools.Assert(serializer.Reading);
if (serializer.TryGetCacheData<IRsiStateLike>(SerializationCache, out var dirTex))
{
return dirTex;
}
var tex = serializer.ReadDataField<string?>("texture", null);
if (!string.IsNullOrWhiteSpace(tex))
{
dirTex = resourceCache.GetResource<TextureResource>(SpriteComponent.TextureRoot / tex).Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
RSI rsi;
var rsiPath = serializer.ReadDataField<string?>("sprite", null);
if (string.IsNullOrWhiteSpace(rsiPath))
{
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
var path = SpriteComponent.TextureRoot / rsiPath;
try
{
rsi = resourceCache.GetResource<RSIResource>(path).RSI;
}
catch
{
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
var stateId = serializer.ReadDataField<string?>("state", null);
if (string.IsNullOrWhiteSpace(stateId))
{
Logger.ErrorS(LogCategory, "No state specified.");
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
if (rsi.TryGetState(stateId, out var state))
{
serializer.SetCacheData(SerializationCache, state);
return state;
}
else
{
Logger.ErrorS(LogCategory, "State '{0}' does not exist on RSI.", stateId);
return resourceCache.GetFallback<TextureResource>().Texture;
}
return compData.Icon?.Default ?? resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
if (!prototype.Components.TryGetValue("Icon", out var mapping))
if (!prototype.Components.TryGetValue("Icon", out var compData))
{
return null;
}
return TextureForConfig(YamlObjectSerializer.NewReader(mapping), resourceCache);
return TextureForConfig((IconComponent)compData, resourceCache);
}
}
}

View File

@@ -1,6 +1,8 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
@@ -17,14 +19,7 @@ namespace Robust.Client.GameObjects
/// The context that will be made active for a client that attaches to this entity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public string ContextName { get; set; } = default!;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction("context", InputContextContainer.DefaultContextName, value => ContextName = value, () => ContextName);
}
[DataField("context")]
public string ContextName { get; set; } = InputContextContainer.DefaultContextName;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
@@ -7,13 +7,14 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
public class PointLightComponent : Component, IPointLightComponent
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
@@ -134,16 +135,25 @@ namespace Robust.Client.GameObjects
}
}
private float _radius = 5;
[DataField("radius")]
private float _radius = 5f;
[DataField("nestedvisible")]
private bool _visibleNested = true;
private bool _lightOnParent;
[DataField("color")]
private Color _color = Color.White;
private Vector2 _offset;
[DataField("offset")]
private Vector2 _offset = Vector2.Zero;
[DataField("enabled")]
private bool _enabled = true;
[DataField("autoRot")]
private bool _maskAutoRotate;
private Angle _rotation;
private float _energy;
private float _softness;
[DataField("energy")]
private float _energy = 1f;
[DataField("softness")]
private float _softness = 1f;
[DataField("mask")]
private string? _maskPath;
/// <summary>
@@ -169,6 +179,14 @@ namespace Robust.Client.GameObjects
Mask = null;
}
void ISerializationHooks.AfterDeserialization()
{
if (_maskPath != null)
{
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(_maskPath);
}
}
public override void Initialize()
{
base.Initialize();
@@ -180,7 +198,7 @@ namespace Robust.Client.GameObjects
{
base.HandleMessage(message, component);
if ((message is ParentChangedMessage msg))
if (message is ParentChangedMessage msg)
{
HandleTransformParentChanged(msg);
}
@@ -204,19 +222,6 @@ namespace Robust.Client.GameObjects
}
}
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataFieldCached(ref _offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref _radius, "radius", 5f);
serializer.DataFieldCached(ref _color, "color", Color.White);
serializer.DataFieldCached(ref _enabled, "enabled", true);
serializer.DataFieldCached(ref _energy, "energy", 1f);
serializer.DataFieldCached(ref _softness, "softness", 1f);
serializer.DataFieldCached(ref _maskAutoRotate, "autoRot", false);
serializer.DataFieldCached(ref _visibleNested, "nestedvisible", true);
serializer.DataFieldCached(ref _maskPath, "mask", null);
}
public override void OnRemove()
{
base.OnRemove();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -17,6 +17,8 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -25,8 +27,12 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
namespace Robust.Client.GameObjects
{
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
IComponentDebug
IComponentDebug, ISerializationHooks
{
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[DataField("visible")]
private bool _visible = true;
[ViewVariables(VVAccess.ReadWrite)]
@@ -36,6 +42,7 @@ namespace Robust.Client.GameObjects
set => _visible = value;
}
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
private int drawDepth = DrawDepthTag.Default;
/// <summary>
@@ -48,6 +55,7 @@ namespace Robust.Client.GameObjects
set => drawDepth = value;
}
[DataField("scale")]
private Vector2 scale = Vector2.One;
/// <summary>
@@ -61,7 +69,8 @@ namespace Robust.Client.GameObjects
set => scale = value;
}
private Angle rotation;
[DataField("rotation")]
private Angle rotation = Angle.Zero;
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
@@ -71,6 +80,7 @@ namespace Robust.Client.GameObjects
set => rotation = value;
}
[DataField("offset")]
private Vector2 offset = Vector2.Zero;
/// <summary>
@@ -84,6 +94,7 @@ namespace Robust.Client.GameObjects
set => offset = value;
}
[DataField("color")]
private Color color = Color.White;
[Animatable]
@@ -108,18 +119,152 @@ namespace Robust.Client.GameObjects
set => _directional = value;
}
[DataField("directional")]
private bool _directional = true;
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
{
get
{
var layerDatums = new List<PrototypeLayerData>();
foreach (var layer in Layers)
{
layerDatums.Add(layer.ToPrototypeData());
}
return layerDatums;
}
set
{
if(value == null) return;
Layers.Clear();
foreach (var layerDatum in value)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
{
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
}
catch
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
layerDatum.State);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist.",
layerDatum.Shader);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
Layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = Layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
LayerMap.Add(key, index);
}
}
}
_layerMapShared = true;
UpdateIsInert();
}
}
private RSI? _baseRsi;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("rsi", priority: 2)]
public RSI? BaseRSI
{
get => _baseRsi;
set
{
_baseRsi = value;
if (Layers == null || value == null)
if (value == null)
{
return;
}
@@ -147,6 +292,12 @@ namespace Robust.Client.GameObjects
}
}
[DataField("sprite", readOnly: true)] private string? rsi;
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
[DataField("state", readOnly: true)] private string? state;
[DataField("texture", readOnly: true)] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded { get; set; }
@@ -158,11 +309,7 @@ namespace Robust.Client.GameObjects
[ViewVariables] private Dictionary<object, int> LayerMap = new();
[ViewVariables] private bool _layerMapShared;
[ViewVariables] private List<Layer> Layers = default!;
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[Dependency] private readonly IReflectionManager reflectionManager = default!;
[ViewVariables] private List<Layer> Layers = new();
[ViewVariables(VVAccess.ReadWrite)] public uint RenderOrder { get; set; }
@@ -170,10 +317,9 @@ namespace Robust.Client.GameObjects
private static ShaderInstance? _defaultShader;
[ViewVariables]
private ShaderInstance? DefaultShader => _defaultShader ??
(_defaultShader = prototypes
.Index<ShaderPrototype>("shaded")
.Instance());
private ShaderInstance? DefaultShader => _defaultShader ??= prototypes
.Index<ShaderPrototype>("shaded")
.Instance();
public const string LogCategory = "go.comp.sprite";
const string LayerSerializationCache = "spritelayer";
@@ -181,6 +327,46 @@ namespace Robust.Client.GameObjects
[ViewVariables(VVAccess.ReadWrite)] public bool IsInert { get; private set; }
void ISerializationHooks.AfterDeserialization()
{
{
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
if (layerDatums.Count == 0)
{
if (state != null || texture != null)
{
layerDatums.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texture) ? null : texture,
State = string.IsNullOrWhiteSpace(state) ? null : state,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
state = null;
texture = null;
}
}
if (layerDatums.Count != 0)
{
LayerDatums = layerDatums;
}
}
/// <summary>
/// Update this sprite component to visibly match the current state of other at the time
/// this is called. Does not keep them perpetually in sync.
@@ -1005,9 +1191,14 @@ namespace Robust.Client.GameObjects
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
}
private bool _screenLock = false;
private Direction _overrideDirection = Direction.South;
private bool _enableOverrideDirection = false;
[DataField("noRot")]
private bool _screenLock = true;
[DataField("overrideDir")]
private Direction _overrideDirection = Direction.East;
[DataField("enableOverrideDir")]
private bool _enableOverrideDirection;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
@@ -1153,205 +1344,6 @@ namespace Robust.Client.GameObjects
return texture;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref scale, "scale", Vector2.One);
serializer.DataFieldCached(ref rotation, "rotation", Angle.Zero);
serializer.DataFieldCached(ref offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref drawDepth, "drawdepth", DrawDepthTag.Default,
WithFormat.Constants<DrawDepthTag>());
serializer.DataFieldCached(ref color, "color", Color.White);
serializer.DataFieldCached(ref _visible, "visible", true);
serializer.DataFieldCached(ref _directional, "directional", true); //TODO: Kill ME
serializer.DataFieldCached(ref _screenLock, "noRot", true);
serializer.DataFieldCached(ref _enableOverrideDirection, "enableOverrideDir", false);
serializer.DataFieldCached(ref _overrideDirection, "overrideDir", Direction.East);
// TODO: Writing?
if (!serializer.Reading)
{
return;
}
{
var rsi = serializer.ReadDataField<string?>("sprite", null);
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = resourceCache.GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
List<Layer> CloneLayers(List<Layer> source)
{
var clone = new List<Layer>(source.Count);
foreach (var layer in source)
{
clone.Add(new Layer(layer, this));
}
return clone;
}
if (serializer.TryGetCacheData<List<Layer>>(LayerSerializationCache, out var layers))
{
LayerMap = serializer.GetCacheData<Dictionary<object, int>>(LayerMapSerializationCache);
_layerMapShared = true;
Layers = CloneLayers(layers);
UpdateIsInert();
return;
}
layers = new List<Layer>();
var layerMap = new Dictionary<object, int>();
var layerData =
serializer.ReadDataField("layers", new List<PrototypeLayerData>());
if(layerData.Count == 0){
var baseState = serializer.ReadDataField<string?>("state", null);
var texturePath = serializer.ReadDataField<string?>("texture", null);
if (baseState != null || texturePath != null)
{
layerData.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texturePath) ? null : texturePath,
State = string.IsNullOrWhiteSpace(baseState) ? null : baseState,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
}
}
foreach (var layerDatum in layerData)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
{
layer.RSI = resourceCache.GetResource<RSIResource>(path).RSI;
}
catch
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from."
+ "cannot use 'state' property. Prototype: '{0}'", Owner.Prototype?.ID);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist. Prototype: '{1}'",
layerDatum.Shader, Owner.Prototype?.ID);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (reflectionManager.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (layerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
layerMap.Add(key, index);
}
}
}
Layers = layers;
LayerMap = layerMap;
_layerMapShared = true;
serializer.SetCacheData(LayerSerializationCache, CloneLayers(Layers));
serializer.SetCacheData(LayerMapSerializationCache, layerMap);
UpdateIsInert();
}
public override void OnRemove()
{
base.OnRemove();
@@ -1663,7 +1655,7 @@ namespace Robust.Client.GameObjects
Flip = 3,
}
private class Layer : ISpriteLayer
public class Layer : ISpriteLayer
{
[ViewVariables] private readonly SpriteComponent _parent;
@@ -1730,6 +1722,22 @@ namespace Robust.Client.GameObjects
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
public PrototypeLayerData ToPrototypeData()
{
return new PrototypeLayerData
{
Color = Color,
Rotation = Rotation,
Scale = Scale,
//todo Shader = Shader,
State = State.Name,
Visible = Visible,
RsiPath = RSI?.Path?.ToString(),
//todo TexturePath = Textur
//todo MapKeys
};
}
bool ISpriteLayer.Visible
{
get => Visible;
@@ -2000,7 +2008,6 @@ namespace Robust.Client.GameObjects
}
return state;
}
}
@@ -2051,7 +2058,6 @@ namespace Robust.Client.GameObjects
if (!anyTexture)
yield return resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
@@ -2098,6 +2104,7 @@ namespace Robust.Client.GameObjects
public T AddComponent<T>() where T : Component, new()
{
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
_components[typeof(T)] = comp;
comp.Owner = this;
@@ -2107,9 +2114,9 @@ namespace Robust.Client.GameObjects
_components[typeof(ISpriteComponent)] = comp;
}
if (Prototype != null && Prototype.Components.TryGetValue(comp.Name, out var node))
if (Prototype != null && Prototype.TryGetComponent<T>(comp.Name, out var node))
{
comp.ExposeData(YamlObjectSerializer.NewReader(node));
comp = serializationManager.Copy(node, comp)!;
}
return comp;

View File

@@ -6,41 +6,31 @@ using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Client.GameObjects
{
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
private readonly Dictionary<object, BoundUserInterface> _openInterfaces =
new();
private Dictionary<object, PrototypeData> _interfaceData = default!;
#pragma warning disable 649
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
#pragma warning restore 649
private readonly Dictionary<object, PrototypeData> _interfaces = new();
public override void ExposeData(ObjectSerializer serializer)
[DataField("interfaces", readOnly: true)]
private List<PrototypeData> _interfaceData = new();
void ISerializationHooks.AfterDeserialization()
{
base.ExposeData(serializer);
_interfaces.Clear();
const string cache = "ui_cache";
if (serializer.TryGetCacheData<Dictionary<object, PrototypeData>>(cache, out var interfaceData))
foreach (var data in _interfaceData)
{
_interfaceData = interfaceData;
return;
_interfaces[data.UiKey] = data;
}
var data = serializer.ReadDataFieldCached("interfaces", new List<PrototypeData>());
interfaceData = new Dictionary<object, PrototypeData>();
foreach (var prototypeData in data)
{
interfaceData[prototypeData.UiKey] = prototypeData;
}
serializer.SetCacheData(cache, interfaceData);
_interfaceData = interfaceData;
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
@@ -81,7 +71,7 @@ namespace Robust.Client.GameObjects
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
{
var data = _interfaceData[wrapped.UiKey];
var data = _interfaces[wrapped.UiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Buffers;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.ResourceManagement;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using StencilOp = Robust.Client.Graphics.StencilOp;

View File

@@ -1,23 +1,30 @@
using System;
using System.Collections.Generic;
using Robust.Client.ResourceManagement;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
public sealed class ShaderPrototype : IPrototype
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
{
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public string ID { get; private set; } = default!;
[ViewVariables]
[field: DataField("id", required: true)]
public string ID { get; } = default!;
[ViewVariables]
[field: DataField("parent")]
public string? Parent { get; }
private ShaderKind Kind;
@@ -31,11 +38,14 @@ namespace Robust.Client.Graphics
private ShaderInstance? _cachedInstance;
private bool _stencilEnabled;
private int _stencilRef;
private int _stencilReadMask = unchecked((int) uint.MaxValue);
private int _stencilWriteMask = unchecked((int) uint.MaxValue);
private StencilFunc _stencilFunc = StencilFunc.Always;
private StencilOp _stencilOp = StencilOp.Keep;
private int _stencilRef => StencilDataHolder?.StencilRef ?? 0;
private int _stencilReadMask => StencilDataHolder?.ReadMask ?? unchecked((int) uint.MaxValue);
private int _stencilWriteMask => StencilDataHolder?.WriteMask ?? unchecked((int) uint.MaxValue);
private StencilFunc _stencilFunc => StencilDataHolder?.StencilFunc ?? StencilFunc.Always;
private StencilOp _stencilOp => StencilDataHolder?.StencilOp ?? StencilOp.Keep;
[DataField("stencil")]
private StencilData? StencilDataHolder;
/// <summary>
/// Retrieves a ready-to-use instance of this shader.
@@ -64,12 +74,12 @@ namespace Robust.Client.Graphics
switch (Kind)
{
case ShaderKind.Source:
instance = _clyde.InstanceShader(Source!.ClydeHandle);
instance = IoCManager.Resolve<IClydeInternal>().InstanceShader(Source!.ClydeHandle);
_applyDefaultParameters(instance);
break;
case ShaderKind.Canvas:
instance = _clyde.InstanceShader(CompiledCanvasShader);
instance = IoCManager.Resolve<IClydeInternal>().InstanceShader(CompiledCanvasShader);
break;
default:
@@ -95,135 +105,108 @@ namespace Robust.Client.Graphics
return Instance().Duplicate();
}
public void LoadFrom(YamlMappingNode mapping)
{
ID = mapping.GetNode("id").ToString();
[DataField("kind", readOnly: true, required: true)] private string _rawKind = default!;
[DataField("path", readOnly: true)] private ResourcePath? path;
[DataField("params", readOnly: true)] private Dictionary<string, string>? paramMapping;
[DataField("light_mode", readOnly: true)] private string? rawMode;
[DataField("blend_mode", readOnly: true)] private string? rawBlendMode;
var kind = mapping.GetNode("kind").AsString();
switch (kind)
void ISerializationHooks.AfterDeserialization()
{
switch (_rawKind)
{
case "source":
Kind = ShaderKind.Source;
ReadSourceKind(mapping);
if (path == null) throw new InvalidOperationException();
Source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(path);
if (paramMapping != null)
{
ShaderParams = new Dictionary<string, object>();
foreach (var item in paramMapping!)
{
var name = item.Key;
if (!Source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
{
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
continue;
}
var value = _parseUniformValue(item.Value, uniformDefinition.Type.Type);
ShaderParams.Add(name, value);
}
}
break;
case "canvas":
Kind = ShaderKind.Canvas;
ReadCanvasKind(mapping);
var source = "";
if(rawMode != null)
{
switch (rawMode)
{
case "normal":
break;
case "unshaded":
source += "light_mode unshaded;\n";
break;
default:
throw new InvalidOperationException($"Invalid light mode: '{rawMode}'");
}
}
if(rawBlendMode != null){
switch (rawBlendMode)
{
case "mix":
source += "blend_mode mix;\n";
break;
case "add":
source += "blend_mode add;\n";
break;
case "subtract":
source += "blend_mode subtract;\n";
break;
case "multiply":
source += "blend_mode multiply;\n";
break;
default:
throw new InvalidOperationException($"Invalid blend mode: '{rawBlendMode}'");
}
}
source += "void fragment() {\n COLOR = zTexture(UV);\n}";
var preset = ShaderParser.Parse(source, _resourceCache);
CompiledCanvasShader = IoCManager.Resolve<IClydeInternal>().LoadShader(preset, $"canvas_preset_{ID}");
break;
default:
throw new InvalidOperationException($"Invalid shader kind: '{kind}'");
throw new InvalidOperationException($"Invalid shader kind: '{_rawKind}'");
}
// Load stencil data.
if (mapping.TryGetNode("stencil", out YamlMappingNode? stencilData))
{
ReadStencilData(stencilData);
}
if (StencilDataHolder != null) _stencilEnabled = true;
}
private void ReadStencilData(YamlMappingNode stencilData)
[DataDefinition]
public class StencilData
{
_stencilEnabled = true;
[DataField("ref")] public int StencilRef;
if (stencilData.TryGetNode("ref", out var dataNode))
{
_stencilRef = dataNode.AsInt();
}
[DataField("op")] public StencilOp StencilOp;
if (stencilData.TryGetNode("op", out dataNode))
{
_stencilOp = dataNode.AsEnum<StencilOp>();
}
[DataField("func")] public StencilFunc StencilFunc;
if (stencilData.TryGetNode("func", out dataNode))
{
_stencilFunc = dataNode.AsEnum<StencilFunc>();
}
[DataField("readMask")] public int ReadMask = unchecked((int) uint.MaxValue);
if (stencilData.TryGetNode("readMask", out dataNode))
{
_stencilReadMask = dataNode.AsInt();
}
if (stencilData.TryGetNode("writeMask", out dataNode))
{
_stencilWriteMask = dataNode.AsInt();
}
}
private void ReadSourceKind(YamlMappingNode mapping)
{
var path = mapping.GetNode("path").AsResourcePath();
Source = _resourceCache.GetResource<ShaderSourceResource>(path);
if (mapping.TryGetNode<YamlMappingNode>("params", out var paramMapping))
{
ShaderParams = new Dictionary<string, object>();
foreach (var item in paramMapping)
{
var name = item.Key.AsString();
if (!Source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
{
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
continue;
}
var value = _parseUniformValue(item.Value, uniformDefinition.Type.Type);
ShaderParams.Add(name, value);
}
}
}
private void ReadCanvasKind(YamlMappingNode mapping)
{
var source = "";
if (mapping.TryGetNode("light_mode", out var node))
{
switch (node.AsString())
{
case "normal":
break;
case "unshaded":
source += "light_mode unshaded;\n";
break;
default:
throw new InvalidOperationException($"Invalid light mode: '{node.AsString()}'");
}
}
if (mapping.TryGetNode("blend_mode", out node))
{
switch (node.AsString())
{
case "mix":
source += "blend_mode mix;\n";
break;
case "add":
source += "blend_mode add;\n";
break;
case "subtract":
source += "blend_mode subtract;\n";
break;
case "multiply":
source += "blend_mode multiply;\n";
break;
default:
throw new InvalidOperationException($"Invalid blend mode: '{node.AsString()}'");
}
}
source += "void fragment() {\n COLOR = zTexture(UV);\n}";
var preset = ShaderParser.Parse(source, _resourceCache);
CompiledCanvasShader = _clyde.LoadShader(preset, $"canvas_preset_{ID}");
[DataField("writeMask")] public int WriteMask = unchecked((int) uint.MaxValue);
}
private static object _parseUniformValue(YamlNode node, ShaderDataType dataType)

View File

@@ -19,6 +19,8 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.Core;
@@ -117,8 +119,8 @@ namespace Robust.Client.Input
public void SaveToUserData()
{
var mapping = new YamlMappingNode();
var ser = YamlObjectSerializer.NewWriter(mapping);
var mapping = new MappingDataNode();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var modifiedBindings = _modifiedKeyFunctions
.Select(p => _bindingsByFunction[p])
@@ -141,15 +143,13 @@ namespace Robust.Client.Input
.Where(p => _bindingsByFunction[p].Count == 0)
.ToArray();
var version = 1;
ser.DataField(ref version, "version", 1);
ser.DataField(ref modifiedBindings, "binds", Array.Empty<KeyBindingRegistration>());
ser.DataField(ref leaveEmpty, "leaveEmpty", Array.Empty<BoundKeyFunction>());
mapping.AddNode("version", new ValueDataNode("1"));
mapping.AddNode("binds", serializationManager.WriteValue(modifiedBindings));
mapping.AddNode("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
var path = new ResourcePath(KeybindsPath);
using var writer = new StreamWriter(_resourceMan.UserData.Create(path));
var stream = new YamlStream {new(mapping)};
var stream = new YamlStream {new(mapping.ToMappingNode())};
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
}
@@ -416,12 +416,14 @@ namespace Robust.Client.Input
var mapping = (YamlMappingNode) yamlStream.Documents[0].RootNode;
var baseSerializer = YamlObjectSerializer.NewReader(mapping);
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var robustMapping = mapping.ToDataNode() as MappingDataNode;
if (robustMapping == null) throw new InvalidOperationException();
var foundBinds = baseSerializer.TryReadDataField<KeyBindingRegistration[]>("binds", out var baseKeyRegs);
if (foundBinds && baseKeyRegs != null && baseKeyRegs.Length > 0)
if (robustMapping.TryGetNode("binds", out var BaseKeyRegsNode))
{
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
foreach (var reg in baseKeyRegs)
{
if (!NetworkBindMap.FunctionExists(reg.Function.FunctionName))
@@ -447,11 +449,11 @@ namespace Robust.Client.Input
}
}
if (userData)
if (userData && robustMapping.TryGetNode("leaveEmpty", out var node))
{
var foundLeaveEmpty = baseSerializer.TryReadDataField<BoundKeyFunction[]>("leaveEmpty", out var leaveEmpty);
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);
if (foundLeaveEmpty && leaveEmpty != null && leaveEmpty.Length > 0)
if (leaveEmpty.Length > 0)
{
// Adding to _modifiedKeyFunctions means that these keybinds won't be loaded from the base file.
// Because they've been explicitly cleared.

View File

@@ -1,33 +1,30 @@
using Robust.Shared.Input;
using Robust.Shared.Serialization;
using Robust.Shared.Input;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Client.Input
{
public struct KeyBindingRegistration : IExposeData
[DataDefinition]
public class KeyBindingRegistration
{
[DataField("function")]
public BoundKeyFunction Function;
public KeyBindingType Type;
[DataField("type")]
public KeyBindingType Type = KeyBindingType.State;
[DataField("key")]
public Keyboard.Key BaseKey;
[DataField("mod1")]
public Keyboard.Key Mod1;
[DataField("mod2")]
public Keyboard.Key Mod2;
[DataField("mod3")]
public Keyboard.Key Mod3;
[DataField("priority")]
public int Priority;
[DataField("canFocus")]
public bool CanFocus;
[DataField("canRepeat")]
public bool CanRepeat;
[DataField("allowSubCombs")]
public bool AllowSubCombs;
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Function, "function", default);
serializer.DataField(ref Type, "type", KeyBindingType.State);
serializer.DataField(ref BaseKey, "key", default);
serializer.DataField(ref Mod1, "mod1", default);
serializer.DataField(ref Mod2, "mod2", default);
serializer.DataField(ref Mod3, "mod3", default);
serializer.DataField(ref Priority, "priority", 0);
serializer.DataField(ref CanFocus, "canFocus", false);
serializer.DataField(ref CanRepeat, "canRepeat", false);
serializer.DataField(ref AllowSubCombs, "allowSubCombs", false);
}
}
}

View File

@@ -1,27 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Maths;
using Robust.Shared.Map;
using Robust.Shared.Network.Messages;
using Robust.Client.Graphics;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Client.Placement
{
@@ -126,14 +124,6 @@ namespace Robust.Client.Placement
if (value != null)
{
PlacementOffset = value.PlacementOffset;
if (value.Components.ContainsKey("BoundingBox") && value.Components.ContainsKey("Physics"))
{
var map = value.Components["BoundingBox"];
var serializer = YamlObjectSerializer.NewReader(map);
serializer.DataField(ref _colliderAABB, "aabb", new Box2(0f, 0f, 0f, 0f));
return;
}
}
_colliderAABB = new Box2(0f, 0f, 0f, 0f);

View File

@@ -5,7 +5,7 @@ using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
namespace Robust.Client.ResourceManagement.ResourceTypes
namespace Robust.Client.ResourceManagement
{
/// <summary>
/// Loads the **source code** of a shader.

View File

@@ -0,0 +1,73 @@
using Robust.Client.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Client.Serialization
{
[TypeSerializer]
public class AppearanceVisualizerSerializer : ITypeSerializer<AppearanceVisualizer, MappingDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
{
if (!node.TryGetNode("type", out var typeNode))
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
if (typeNode is not ValueDataNode typeValueDataNode)
throw new InvalidMappingException("Type node not a value node for AppearanceVisualizer!");
var type = IoCManager.Resolve<IReflectionManager>()
.YamlTypeTagLookup(typeof(AppearanceVisualizer), typeValueDataNode.Value);
if (type == null)
throw new InvalidMappingException(
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
var newNode = (MappingDataNode)node.Copy();
newNode.RemoveNode("type");
return serializationManager.Read(type, newNode, context, skipHook);
}
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies,
ISerializationContext? context)
{
if (!node.TryGetNode("type", out var typeNode) || typeNode is not ValueDataNode valueNode)
{
return new ErrorNode(node, "Missing/Invalid type", true);
}
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
var type = reflectionManager.YamlTypeTagLookup(typeof(AppearanceVisualizer), valueNode.Value);
if (type == null)
{
return new ErrorNode(node, $"Failed to resolve type: {valueNode.Value}", true);
}
return serializationManager.ValidateNode(type, node.CopyCast<MappingDataNode>().RemoveNode("type"));
}
public DataNode Write(ISerializationManager serializationManager, AppearanceVisualizer value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var mapping = serializationManager.WriteValueAs<MappingDataNode>(value.GetType(), value, alwaysWrite, context);
mapping.AddNode("type", new ValueDataNode(value.GetType().Name));
return mapping;
}
public AppearanceVisualizer Copy(ISerializationManager serializationManager, AppearanceVisualizer source,
AppearanceVisualizer target, bool skipHook, ISerializationContext? context = null)
{
return serializationManager.Copy(source, target, context)!;
}
}
}

View File

@@ -26,12 +26,10 @@ namespace Robust.Client.Utility
{
if (cache.TryGetResource<RSIResource>(
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
out var theRsi))
out var theRsi) &&
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
if (theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
return state;
}
return state;
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using NUnit.Framework;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Generators.UnitTesting
{
[Parallelizable]
public abstract class AnalyzerTest
{
protected static Assembly GetAssemblyFromCompilation(Compilation newComp)
{
using var stream = new MemoryStream();
newComp.Emit(stream);
var assembly = Assembly.Load(stream.ToArray());
return assembly;
}
protected static Compilation CreateCompilation(string source)
{
var dd = typeof(Enumerable).GetTypeInfo().Assembly.Location;
var coreDir = Directory.GetParent(dd) ?? throw new Exception("Couldn't find location of coredir");
var references = new[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Dictionary<,>).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(DataFieldAttribute).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"),
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar +
"System.Runtime.dll"),
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar +
"System.Collections.dll"),
};
var syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview));
return CSharpCompilation.Create(
"comp",
new[] {syntaxTree},
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}
protected static (Compilation, ImmutableArray<Diagnostic> diagnostics) RunGenerators(Compilation c,
params ISourceGenerator[] gens)
{
var driver = CSharpGeneratorDriver.Create(
ImmutableArray.Create(gens),
ImmutableArray<AdditionalText>.Empty,
(CSharpParseOptions) c.SyntaxTrees.First().Options);
driver.RunGeneratorsAndUpdateCompilation(c, out var d, out var diagnostics);
return (d, diagnostics);
}
}
}

View File

@@ -0,0 +1,82 @@
using System.Linq;
using NUnit.Framework;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Generators.UnitTesting
{
public class DataClassTests : AnalyzerTest
{
[Test]
public void DCTest()
{
const string source = @"
using System.Collections.Generic;
using Robust.Shared.Prototypes;
//using Robust.Shared.Serialization;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Test{
[DataClass]
public class TestClass{
[DataFieldWithConstant(""drawdepth"", typeof(DrawDepthTag))]
private int _drawDepth = DrawDepthTag.Default;
[DataField(""myList"")]
public List<TestClass> testList;
[DataField(""myList"")]
public string abc = ""testing"";
[DataField(""drawdepth"", constType: typeof(TestClass))]
public int test;
}
}
";
var comp = CreateCompilation(source);
//Assert.IsEmpty(comp.GetDiagnostics());
var (newcomp, generatorDiags) = RunGenerators(comp, new DataClassGenerator());
Assert.IsEmpty(generatorDiags);
var type = newcomp.GetTypeByMetadataName("Test.TestClass_AUTODATA");
Assert.NotNull(type);
var memberNames = type.MemberNames.ToArray();
// 3 properties
Assert.That(memberNames, Has.Length.EqualTo(3));
Assert.That(memberNames, Contains.Item("testList"));
Assert.That(memberNames, Contains.Item("abc"));
Assert.That(memberNames, Contains.Item("test"));
var members = type.GetMembers();
// 3 properties + constructor
Assert.That(members, Has.Length.EqualTo(4));
var memberDictionary = members.ToDictionary(m => m.Name, m => m);
var yamlFieldNamespace = typeof(DataFieldAttribute).FullName;
Assert.NotNull(yamlFieldNamespace);
var yamlFieldAttribute = comp.GetTypeByMetadataName(yamlFieldNamespace);
var testListYamlAttribute = memberDictionary["testList"].GetAttribute(yamlFieldAttribute);
Assert.NotNull(testListYamlAttribute);
Assert.That(testListYamlAttribute.ConstructorArguments[0].Value, Is.EqualTo("myList"));
var abcYamlAttribute = memberDictionary["abc"].GetAttribute(yamlFieldAttribute);
Assert.NotNull(abcYamlAttribute);
Assert.That(abcYamlAttribute.ConstructorArguments[0].Value, Is.EqualTo("myList"));
var testYamlAttribute = memberDictionary["test"].GetAttribute(yamlFieldAttribute);
Assert.NotNull(testYamlAttribute);
Assert.That(testYamlAttribute.ConstructorArguments[0].Value, Is.EqualTo("drawdepth"));
Assert.NotNull(testYamlAttribute.ConstructorArguments[3].Value);
Assert.That(testYamlAttribute.ConstructorArguments[3].Value.ToString(), Is.EqualTo("Test.TestClass"));
//TODO Check for dataclass & if its correct
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Generators\Robust.Generators.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.11.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit.Analyzers" Version="0.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
</Project>

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -1,23 +1,10 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
using Robust.Server.Console;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Server.ViewVariables;
using Robust.Shared.Asynchronous;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Exceptions;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Server.DataMetrics;
using Robust.Server.Debugging;
using Robust.Server.GameObjects;
@@ -25,12 +12,26 @@ using Robust.Server.GameStates;
using Robust.Server.Log;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Server.Utility;
using Robust.Server.ViewVariables;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Serilog.Debugging;
using Serilog.Sinks.Loki;
using Stopwatch = Robust.Shared.Timing.Stopwatch;
@@ -298,6 +299,8 @@ namespace Robust.Server
_entities.Initialize();
IoCManager.Resolve<ISerializationManager>().Initialize();
// because of 'reasons' this has to be called after the last assembly is loaded
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();

View File

@@ -1,14 +1,19 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public class EyeComponent : SharedEyeComponent
{
private bool _drawFov;
private Vector2 _zoom;
[DataField("drawFov")]
private bool _drawFov = true;
[DataField("zoom")]
private Vector2 _zoom = Vector2.One/2f;
private Vector2 _offset;
private Angle _rotation;
@@ -68,13 +73,5 @@ namespace Robust.Server.GameObjects
{
return new EyeComponentState(DrawFov, Zoom, Offset, Rotation);
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _zoom, "zoom", Vector2.One/2f);
serializer.DataFieldCached(ref _drawFov, "drawFov", true);
}
}
}

View File

@@ -1,7 +1,9 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
@@ -10,10 +12,14 @@ namespace Robust.Server.GameObjects
[ComponentReference(typeof(IPointLightComponent))]
public class PointLightComponent : Component, IPointLightComponent
{
private Color _color;
private bool _enabled;
private float _radius;
private Vector2 _offset;
[DataField("color")]
private Color _color = new(200, 200, 200);
[DataField("enabled")]
private bool _enabled = true;
[DataField("radius")]
private float _radius = 10;
[DataField("offset")]
private Vector2 _offset = Vector2.Zero;
public override string Name => "PointLight";
public override uint? NetID => NetIDs.POINT_LIGHT;
@@ -73,17 +79,6 @@ namespace Robust.Server.GameObjects
}
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _enabled, "enabled", true);
serializer.DataField(ref _color, "color", new Color(200, 200, 200));
serializer.DataField(ref _radius, "radius", 10);
serializer.DataField(ref _offset, "offset", Vector2.Zero);
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new PointLightComponentState(Enabled, Color, Radius, Offset);

View File

@@ -4,27 +4,49 @@ using Robust.Shared.GameObjects;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public class SpriteComponent : SharedSpriteComponent, ISpriteRenderableComponent
public class SpriteComponent : SharedSpriteComponent, ISpriteRenderableComponent, ISerializationHooks
{
const string LayerSerializationCache = "spritelayersrv";
[ViewVariables]
[DataField("layers", priority: 2, readOnly: true)]
private List<PrototypeLayerData> Layers = new();
private bool _visible;
[DataField("visible")]
private bool _visible = true;
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
private int _drawDepth = DrawDepthTag.Default;
private Vector2 _scale;
private Vector2 _offset;
private Color _color;
private bool _directional;
[DataField("scale")]
private Vector2 _scale = Vector2.One;
[DataField("offset")]
private Vector2 _offset = Vector2.Zero;
[DataField("color")]
private Color _color = Color.White;
[DataField("directional")]
private bool _directional = true;
[DataField("sprite")]
private string? _baseRSIPath;
private Angle _rotation;
[DataField("rotation")]
private Angle _rotation = Angle.Zero;
[DataField("state")] private string? state;
[DataField("texture")] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public int DrawDepth
@@ -129,6 +151,31 @@ namespace Robust.Server.GameObjects
[ViewVariables]
public int LayerCount => Layers.Count;
void ISerializationHooks.AfterDeserialization()
{
if (Layers.Count == 0)
{
if (state != null || texture != null)
{
var layerZeroData = SharedSpriteComponent.PrototypeLayerData.New();
if (!string.IsNullOrWhiteSpace(state))
{
layerZeroData.State = state;
}
if (!string.IsNullOrWhiteSpace(texture))
{
layerZeroData.TexturePath = texture;
}
Layers.Insert(0, layerZeroData);
state = null;
texture = null;
}
}
}
public int AddLayerWithSprite(SpriteSpecifier specifier)
{
var layer = PrototypeLayerData.New();
@@ -272,7 +319,7 @@ namespace Robust.Server.GameObjects
{
if (Layers.Count <= layer)
{
Logger.ErrorS("go.comp.sprite", "Layer with index '{0}' does not exist, cannot set set! Trace:\n{1}",
Logger.ErrorS("go.comp.sprite", "Layer with index '{0}' does not exist, cannot set state! Trace:\n{1}",
layer, Environment.StackTrace);
return;
}
@@ -389,59 +436,6 @@ namespace Robust.Server.GameObjects
Dirty();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref _visible, "visible", true);
serializer.DataFieldCached(ref _drawDepth, "drawdepth", DrawDepthTag.Default, WithFormat.Constants<DrawDepthTag>());
serializer.DataFieldCached(ref _offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref _scale, "scale", Vector2.One);
serializer.DataFieldCached(ref _color, "color", Color.White);
serializer.DataFieldCached(ref _directional, "directional", true);
serializer.DataFieldCached(ref _baseRSIPath, "sprite", null);
serializer.DataFieldCached(ref _rotation, "rotation", Angle.Zero);
// TODO: Writing?
if (!serializer.Reading)
{
return;
}
if (serializer.TryGetCacheData<List<PrototypeLayerData>>(LayerSerializationCache, out var layers))
{
Layers = layers.ShallowClone();
return;
}
var layerData =
serializer.ReadDataField<List<PrototypeLayerData>>("layers", new List<PrototypeLayerData>());
if(layerData.Count == 0){
var baseState = serializer.ReadDataField<string?>("state", null);
var texturePath = serializer.ReadDataField<string?>("texture", null);
if (baseState != null || texturePath != null)
{
var layerZeroData = PrototypeLayerData.New();
if (!string.IsNullOrWhiteSpace(baseState))
{
layerZeroData.State = baseState;
}
if (!string.IsNullOrWhiteSpace(texturePath))
{
layerZeroData.TexturePath = texturePath;
}
layerData.Insert(0, layerZeroData);
}
}
serializer.SetCacheData(LayerSerializationCache, layerData.ShallowClone());
Layers = layerData;
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new SpriteComponentState(Visible, DrawDepth, Scale, Rotation, Offset, Color,

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Server.GameObjects
{
@@ -19,27 +20,24 @@ namespace Robust.Server.GameObjects
/// </summary>
/// <seealso cref="BoundUserInterface"/>
[PublicAPI]
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
private readonly Dictionary<object, BoundUserInterface> _interfaces =
new();
[DataField("interfaces", readOnly: true)]
private List<PrototypeData> _interfaceData = new();
/// <summary>
/// Enumeration of all the interfaces this component provides.
/// </summary>
public IEnumerable<BoundUserInterface> Interfaces => _interfaces.Values;
public override void ExposeData(ObjectSerializer serializer)
void ISerializationHooks.AfterDeserialization()
{
base.ExposeData(serializer);
_interfaces.Clear();
if (!serializer.Reading)
{
return;
}
var data = serializer.ReadDataFieldCached("interfaces", new List<PrototypeData>());
foreach (var prototypeData in data)
foreach (var prototypeData in _interfaceData)
{
_interfaces[prototypeData.UiKey] = new BoundUserInterface(prototypeData.UiKey, this);
}

View File

@@ -1,5 +1,7 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
@@ -7,6 +9,7 @@ namespace Robust.Server.GameObjects
[RegisterComponent]
public class VisibilityComponent : Component
{
[DataField("layer")]
private int _layer = 1;
public override string Name => "Visibility";
@@ -20,12 +23,5 @@ namespace Robust.Server.GameObjects
get => _layer;
set => _layer = value;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _layer, "layer", 1);
}
}
}

View File

@@ -50,7 +50,6 @@ namespace Robust.Server.GameObjects
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionExposeDataComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif

View File

@@ -1,21 +1,26 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using YamlDotNet.RepresentationModel;
using Robust.Shared.Utility;
using Robust.Shared.Serialization;
using Robust.Shared.GameObjects;
using System.Globalization;
using System.Linq;
using Robust.Server.GameObjects;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace Robust.Server.Maps
{
@@ -223,7 +228,10 @@ namespace Robust.Server.Maps
/// <summary>
/// Handles the primary bulk of state during the map serialization process.
/// </summary>
private class MapContext : YamlObjectSerializer.Context, IEntityLoadContext
private class MapContext : ISerializationContext, IEntityLoadContext,
ITypeSerializer<GridId, ValueDataNode>,
ITypeSerializer<EntityUid, ValueDataNode>,
ITypeReaderWriter<IEntity, ValueDataNode>
{
private readonly IMapManagerInternal _mapManager;
private readonly ITileDefinitionManager _tileDefinitionManager;
@@ -255,6 +263,10 @@ namespace Robust.Server.Maps
private Dictionary<ushort, string>? _tileMap;
public Dictionary<(Type, Type), object> TypeReaders { get; }
public Dictionary<Type, object> TypeWriters { get; }
public Dictionary<Type, object> TypeCopiers => TypeWriters;
public bool MapIsPostInit { get; private set; }
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
@@ -269,6 +281,18 @@ namespace Robust.Server.Maps
_prototypeManager = prototypeManager;
RootNode = new YamlMappingNode();
TypeWriters = new Dictionary<Type, object>()
{
{typeof(IEntity), this},
{typeof(GridId), this},
{typeof(EntityUid), this}
};
TypeReaders = new Dictionary<(Type, Type), object>()
{
{(typeof(IEntity), typeof(ValueDataNode)), this},
{(typeof(GridId), typeof(ValueDataNode)), this},
{(typeof(EntityUid), typeof(ValueDataNode)), this}
};
}
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
@@ -286,6 +310,18 @@ namespace Robust.Server.Maps
RootNode = node;
TargetMap = targetMapId;
_prototypeManager = prototypeManager;
TypeWriters = new Dictionary<Type, object>()
{
{typeof(IEntity), this},
{typeof(GridId), this},
{typeof(EntityUid), this}
};
TypeReaders = new Dictionary<(Type, Type), object>()
{
{(typeof(IEntity), typeof(ValueDataNode)), this},
{(typeof(GridId), typeof(ValueDataNode)), this},
{(typeof(EntityUid), typeof(ValueDataNode)), this}
};
}
// Deserialization
@@ -530,7 +566,10 @@ namespace Robust.Server.Maps
{
foreach (var compData in componentList)
{
CurrentReadingEntityComponents[compData["type"].AsString()] = (YamlMappingNode) compData;
var copy = new YamlMappingNode(((YamlMappingNode)compData).AsEnumerable());
copy.Children.Remove(new YamlScalarNode("type"));
//TODO Paul: maybe replace mapping with datanode
CurrentReadingEntityComponents[compData["type"].AsString()] = copy;
}
}
@@ -687,9 +726,11 @@ namespace Robust.Server.Maps
private void WriteEntitySection()
{
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var entities = new YamlSequenceNode();
RootNode.Add("entities", entities);
var prototypeCompCache = new Dictionary<string, Dictionary<string, MappingDataNode>>();
foreach (var entity in Entities.OrderBy(e => EntityUidMap[e.Uid]))
{
CurrentWritingEntity = entity;
@@ -701,6 +742,14 @@ namespace Robust.Server.Maps
if (entity.Prototype != null)
{
mapping.Add("type", entity.Prototype.ID);
if (!prototypeCompCache.ContainsKey(entity.Prototype.ID))
{
prototypeCompCache[entity.Prototype.ID] = new Dictionary<string, MappingDataNode>();
foreach (var (compType, comp) in entity.Prototype.Components)
{
prototypeCompCache[entity.Prototype.ID].Add(compType, serializationManager.WriteValueAs<MappingDataNode>(comp.GetType(), comp));
}
}
}
var components = new YamlSequenceNode();
@@ -710,18 +759,21 @@ namespace Robust.Server.Maps
if (component is MapSaveIdComponent)
continue;
var compMapping = new YamlMappingNode();
CurrentWritingComponent = component.Name;
var compSerializer = YamlObjectSerializer.NewWriter(compMapping, this);
var compMapping = serializationManager.WriteValueAs<MappingDataNode>(component.GetType(), component, context: this);
component.ExposeData(compSerializer);
if (entity.Prototype != null && prototypeCompCache[entity.Prototype.ID].TryGetValue(component.Name, out var protMapping))
{
compMapping = compMapping.Except(protMapping);
if(compMapping == null) continue;
}
// Don't need to write it if nothing was written!
if (compMapping.Children.Count != 0)
{
compMapping.AddNode("type", new ValueDataNode(component.Name));
// Something actually got written!
compMapping.Add("type", component.Name);
components.Add(compMapping);
components.Add(compMapping.ToYamlNode());
}
}
@@ -734,140 +786,32 @@ namespace Robust.Server.Maps
}
}
public override bool TryNodeToType(YamlNode node, Type type, [NotNullWhen(true)] out object? obj)
{
if (type == typeof(GridId))
{
if (node.AsString() == "null")
{
obj = GridId.Invalid;
return true;
}
var val = node.AsInt();
if (val >= Grids.Count)
{
Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
}
else
{
obj = Grids[val].Index;
return true;
}
}
if (type == typeof(EntityUid))
{
if (node.AsString() == "null")
{
obj = EntityUid.Invalid;
return true;
}
var val = node.AsInt();
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.",
val);
}
else
{
obj = UidEntityMap[val];
return true;
}
}
if (typeof(IEntity).IsAssignableFrom(type))
{
var val = node.AsInt();
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.",
val);
}
else
{
obj = Entities[val];
return true;
}
}
obj = null;
return false;
}
public override bool TryTypeToNode(object obj, [NotNullWhen(true)] out YamlNode? node)
{
switch (obj)
{
case GridId gridId:
if (!GridIDMap.TryGetValue(gridId, out var gridMapped))
{
Logger.WarningS("map", "Cannot write grid ID '{0}', falling back to nullspace.", gridId);
break;
}
else
{
node = new YamlScalarNode(gridMapped.ToString(CultureInfo.InvariantCulture));
return true;
}
case EntityUid entityUid:
if (!EntityUidMap.TryGetValue(entityUid, out var entityUidMapped))
{
// Terrible hack to mute this warning on the grids themselves when serializing blueprints.
if (!IsBlueprintMode || !CurrentWritingEntity!.HasComponent<MapGridComponent>() ||
CurrentWritingComponent != "Transform")
{
Logger.WarningS("map", "Cannot write entity UID '{0}'.", entityUid);
}
node = new YamlScalarNode("null");
return true;
}
else
{
node = new YamlScalarNode(entityUidMapped.ToString(CultureInfo.InvariantCulture));
return true;
}
case IEntity entity:
if (!EntityUidMap.TryGetValue(entity.Uid, out var entityMapped))
{
Logger.WarningS("map", "Cannot write entity UID '{0}'.", entity.Uid);
break;
}
else
{
node = new YamlScalarNode(entityMapped.ToString(CultureInfo.InvariantCulture));
return true;
}
}
node = null;
return false;
}
// Create custom object serializers that will correctly allow data to be overriden by the map file.
ObjectSerializer IEntityLoadContext.GetComponentSerializer(string componentName, YamlMappingNode? protoData)
IComponent IEntityLoadContext.GetComponentData(string componentName,
IComponent? protoData)
{
if (CurrentReadingEntityComponents == null)
{
throw new InvalidOperationException();
}
var list = new List<YamlMappingNode>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var factory = IoCManager.Resolve<IComponentFactory>();
IComponent data = protoData != null
? serializationManager.CreateCopy(protoData, this)!
: (IComponent) Activator.CreateInstance(factory.GetRegistration(componentName).Type)!;
if (CurrentReadingEntityComponents.TryGetValue(componentName, out var mapping))
{
list.Add(mapping);
var mapData = (IDeserializedDefinition) serializationManager.Read(
factory.GetRegistration(componentName).Type,
mapping.ToDataNode(), this);
var newData = serializationManager.PopulateDataDefinition(data, mapData);
data = (IComponent) newData.RawValue!;
}
if (protoData != null)
{
list.Add(protoData);
}
return YamlObjectSerializer.NewReader(list, this);
return data;
}
public IEnumerable<string> GetExtraComponentTypes()
@@ -875,34 +819,6 @@ namespace Robust.Server.Maps
return CurrentReadingEntityComponents!.Keys;
}
public override bool IsValueDefault<T>(string field, T value, WithFormat<T> format)
{
if (CurrentWritingEntity!.Prototype == null)
{
// No prototype, can't be default.
return false;
}
if (!CurrentWritingEntity.Prototype.Components.TryGetValue(CurrentWritingComponent!, out var compData))
{
// This component was added mid-game.
return false;
}
var testSer = YamlObjectSerializer.NewReader(compData);
if (testSer.TryReadDataFieldCached(field, format, out var prototypeVal))
{
if (value == null)
{
return prototypeVal == null;
}
return YamlObjectSerializer.IsSerializedEqual(value, prototypeVal);
}
return false;
}
private bool IsMapSavable(IEntity entity)
{
if (entity.Prototype?.MapSavable == false || !GridIDMap.ContainsKey(entity.Transform.GridID))
@@ -925,6 +841,173 @@ namespace Robust.Server.Maps
return true;
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
{
if (node.Value == "null") return new DeserializedValue<GridId>(GridId.Invalid);
var val = int.Parse(node.Value);
if (val >= Grids.Count)
{
Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
}
else
{
return new DeserializedValue<GridId>(Grids[val].Index);
}
return new DeserializedValue<GridId>(GridId.Invalid);
}
ValidationNode ITypeReader<IEntity, ValueDataNode>.Validate(ISerializationManager serializationManager,
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
{
if (!int.TryParse(node.Value, out var val) || val >= Entities.Count)
{
return new ErrorNode(node, "Invalid EntityUid", true);
}
return new ValidatedValueNode(node);
}
ValidationNode ITypeReader<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
{
if (node.Value == "null")
{
return new ValidatedValueNode(node);
}
if (!int.TryParse(node.Value, out var val) || val >= Entities.Count)
{
return new ErrorNode(node, "Invalid EntityUid", true);
}
return new ValidatedValueNode(node);
}
ValidationNode ITypeReader<GridId, ValueDataNode>.Validate(ISerializationManager serializationManager,
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
{
if (node.Value == "null") return new ValidatedValueNode(node);
if (!int.TryParse(node.Value, out var val) || val >= Grids.Count)
{
return new ErrorNode(node, "Invalid GridId", true);
}
return new ValidatedValueNode(node);
}
public DataNode Write(ISerializationManager serializationManager, IEntity value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
if (!EntityUidMap.TryGetValue(value.Uid, out var entityMapped))
{
Logger.WarningS("map", "Cannot write entity UID '{0}'.", value.Uid);
return new ValueDataNode("");
}
else
{
return new ValueDataNode(entityMapped.ToString(CultureInfo.InvariantCulture));
}
}
public DataNode Write(ISerializationManager serializationManager, EntityUid value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
if (!EntityUidMap.TryGetValue(value, out var entityUidMapped))
{
// Terrible hack to mute this warning on the grids themselves when serializing blueprints.
if (!IsBlueprintMode || !CurrentWritingEntity!.HasComponent<MapGridComponent>() ||
CurrentWritingComponent != "Transform")
{
Logger.WarningS("map", "Cannot write entity UID '{0}'.", value);
}
return new ValueDataNode("null");
}
else
{
return new ValueDataNode(entityUidMapped.ToString(CultureInfo.InvariantCulture));
}
}
public DataNode Write(ISerializationManager serializationManager, GridId value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
if (!GridIDMap.TryGetValue(value, out var gridMapped))
{
Logger.WarningS("map", "Cannot write grid ID '{0}', falling back to nullspace.", gridMapped);
return new ValueDataNode("");
}
else
{
return new ValueDataNode(gridMapped.ToString(CultureInfo.InvariantCulture));
}
}
DeserializationResult ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager serializationManager,
ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
{
if (node.Value == "null")
{
return new DeserializedValue<EntityUid>(EntityUid.Invalid);
}
var val = int.Parse(node.Value);
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
}
else
{
return new DeserializedValue<EntityUid>(UidEntityMap[val]);
}
return new DeserializedValue<EntityUid>(EntityUid.Invalid);
}
DeserializationResult ITypeReader<IEntity, ValueDataNode>.Read(ISerializationManager serializationManager,
ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
{
var val = int.Parse(node.Value);
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
return null!;
}
else
{
return new DeserializedValue<IEntity>(Entities[val]);
}
}
[MustUseReturnValue]
public GridId Copy(ISerializationManager serializationManager, GridId source, GridId target,
bool skipHook,
ISerializationContext? context = null)
{
return new(source.Value);
}
[MustUseReturnValue]
public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target,
bool skipHook,
ISerializationContext? context = null)
{
return new((int) source);
}
}
/// <summary>

View File

@@ -1,6 +1,8 @@
using Robust.Shared.Serialization;
using System;
using System.Diagnostics.Contracts;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Audio
{
@@ -8,43 +10,52 @@ namespace Robust.Shared.Audio
/// Contains common audio parameters for audio playback on the client.
/// </summary>
[Serializable, NetSerializable]
public struct AudioParams : IExposeData
[DataDefinition]
public struct AudioParams : IPopulateDefaultValues
{
/// <summary>
/// Base volume to play the audio at, in dB.
/// </summary>
[DataField("volume")]
public float Volume { get; set; }
/// <summary>
/// Scale for the audio pitch.
/// </summary>
[DataField("pitchscale")]
public float PitchScale { get; set; }
/// <summary>
/// Audio bus to play on.
/// </summary>
[DataField("busname")]
public string BusName { get; set; }
/// <summary>
/// Only applies to positional audio.
/// The maximum distance from which the audio is hearable.
/// </summary>
[DataField("maxdistance")]
public float MaxDistance { get; set; }
/// <summary>
/// Only applies to positional audio.
/// Positional audio is dampened over distance with this as exponent.
/// </summary>
[DataField("attenuation")]
public float Attenuation { get; set; }
/// <summary>
/// Only applies to global (non-positional) audio.
/// Target channels if the audio configuration has more than 2 speakers.
/// </summary>
[DataField("mixtarget")]
public AudioMixTarget MixTarget { get; set; }
[DataField("loop")]
public bool Loop { get; set; }
[DataField("playoffset")]
public float PlayOffsetSeconds { get; set; }
// For the max distance value: it's 2000 in Godot, but I assume that's PIXELS due to the 2D positioning,
@@ -54,18 +65,6 @@ namespace Robust.Shared.Audio
/// </summary>
public static readonly AudioParams Default = new(0, 1, "Master", 62.5f, 1, AudioMixTarget.Stereo, false, 0f);
void IExposeData.ExposeData(ObjectSerializer serializer)
{
Volume = serializer.ReadDataField("volume", 0f);
PitchScale = serializer.ReadDataField("pitchscale", 1f);
BusName = serializer.ReadDataField("busname", "Master");
MaxDistance = serializer.ReadDataField("maxdistance", 62.5f);
Attenuation = serializer.ReadDataField("attenuation", 1f);
MixTarget = serializer.ReadDataField("mixtarget", AudioMixTarget.Stereo);
Loop = serializer.ReadDataField("loop", false);
PlayOffsetSeconds = serializer.ReadDataField("playoffset", 0f);
}
public AudioParams(float volume, float pitchScale, string busName, float maxDistance, float attenuation,
AudioMixTarget mixTarget, bool loop, float playOffsetSeconds) : this()
{
@@ -169,6 +168,15 @@ namespace Robust.Shared.Audio
me.PlayOffsetSeconds = offset;
return me;
}
public void PopulateDefaultValues()
{
PitchScale = 1f;
BusName = "Master";
MaxDistance = 62.5f;
Attenuation = 1f;
MixTarget = AudioMixTarget.Stereo;
}
}
/// <summary>

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -32,6 +32,7 @@ namespace Robust.Shared.Containers
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
[field: DataField("occludes")]
public bool OccludesLight { get; set; } = true;
/// <inheritdoc />
@@ -40,6 +41,7 @@ namespace Robust.Shared.Containers
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
[field: DataField("showEnts")]
public bool ShowContents { get; set; }
/// <summary>
@@ -165,13 +167,5 @@ namespace Robust.Shared.Containers
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toremove, true));
Manager.Dirty();
}
/// <inheritdoc />
public virtual void ExposeData(ObjectSerializer serializer)
{
// ID and Manager are filled in Initialize
serializer.DataReadWriteFunction("showEnts", false, value => ShowContents = value, () => ShowContents);
serializer.DataReadWriteFunction("occludes", true, value => OccludesLight = value, () => OccludesLight);
}
}
}

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Containers
{
@@ -22,37 +21,14 @@ namespace Robust.Shared.Containers
/// <summary>
/// The generic container class uses a list of entities
/// </summary>
private List<IEntity> _containerList = new();
[DataField("ents")]
private readonly List<IEntity> _containerList = new();
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;
/// <inheritdoc />
public override string ContainerType => ClassName;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
#if SERV3
// ONLY PAUL CAN MAKE ME WHOLE
serializer.DataField(ref _containerList, "ents", new List<IEntity>());
#else
if (serializer.Writing)
{
serializer.DataWriteFunction("ents", new List<EntityUid>(),
() => _containerList.Select(e => e.Uid).ToList());
}
else
{
var entMan = IoCManager.Resolve<IEntityManager>();
serializer.DataReadFunction("ents", new List<EntityUid>(),
value => _containerList = value.Select((uid => entMan.GetEntity(uid))).ToList());
}
#endif
}
/// <inheritdoc />
protected override void InternalInsert(IEntity toinsert)

View File

@@ -7,6 +7,8 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Containers
@@ -20,8 +22,10 @@ namespace Robust.Shared.Containers
{
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[ViewVariables] private Dictionary<string, IContainer> _containers = new();
[ViewVariables]
[DataField("containers")]
private Dictionary<string, IContainer> _containers = new();
/// <inheritdoc />
public sealed override string Name => "ContainerContainer";
@@ -81,7 +85,7 @@ namespace Robust.Shared.Containers
_containers.Remove(dead);
}
}
// Add new containers and update existing contents.
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet)
@@ -135,13 +139,6 @@ namespace Robust.Shared.Containers
newContainer.Manager = this;
return newContainer;
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _containers, "containers", new Dictionary<string, IContainer>());
}
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)
@@ -319,9 +316,13 @@ namespace Robust.Shared.Containers
}
}
private struct ContainerPrototypeData : IExposeData
[DataDefinition]
private struct ContainerPrototypeData : IPopulateDefaultValues
{
[DataField("entities")]
public List<EntityUid> Entities;
[DataField("type")]
public string? Type;
public ContainerPrototypeData(List<EntityUid> entities, string type)
@@ -330,10 +331,9 @@ namespace Robust.Shared.Containers
Type = type;
}
void IExposeData.ExposeData(ObjectSerializer serializer)
public void PopulateDefaultValues()
{
serializer.DataField(ref Entities, "entities", new List<EntityUid>());
serializer.DataField(ref Type, "type", null);
Entities = new List<EntityUid>();
}
}

View File

@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Containers
@@ -15,8 +14,6 @@ namespace Robust.Shared.Containers
{
private const string ClassName = "ContainerSlot";
private IEntity? _containedEntity;
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities
{
@@ -29,38 +26,11 @@ namespace Robust.Shared.Containers
}
[ViewVariables]
public IEntity? ContainedEntity
{
get => _containedEntity;
private set => _containedEntity = value;
}
[field: DataField("ent")]
public IEntity? ContainedEntity { get; private set; }
/// <inheritdoc />
public override string ContainerType => ClassName;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
#if SERV3
// ONLY PAUL CAN MAKE ME WHOLE
serializer.DataField(ref _containedEntity, "ent", default);
#else
if (serializer.Writing)
{
serializer.DataWriteFunction("ents", EntityUid.Invalid,
() => _containedEntity?.Uid ?? EntityUid.Invalid);
}
else
{
var entMan = IoCManager.Resolve<IEntityManager>();
serializer.DataReadFunction("ent", EntityUid.Invalid,
value => _containedEntity = value != EntityUid.Invalid ? entMan.GetEntity(value) : null);
}
#endif
}
/// <inheritdoc />
public override bool CanInsert(IEntity toinsert)

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Containers
{
@@ -25,7 +26,8 @@ namespace Robust.Shared.Containers
/// </remarks>
/// <seealso cref="IContainerManager" />
[PublicAPI]
public interface IContainer : IExposeData
[ImplicitDataDefinitionForInheritors]
public interface IContainer
{
/// <summary>
/// Readonly collection of all the entities contained within this specific container

View File

@@ -1,8 +1,10 @@
using System;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
@@ -10,6 +12,7 @@ namespace Robust.Shared.GameObjects
{
/// <inheritdoc />
[Reflect(false)]
[ImplicitDataDefinitionForInheritorsAttribute]
public abstract class Component : IComponent
{
/// <inheritdoc />
@@ -24,6 +27,7 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public virtual bool NetworkSynchronizeExistence => false;
[DataField("netsync")]
private bool _netSyncEnabled = true;
/// <inheritdoc />
[ViewVariables]
@@ -159,12 +163,6 @@ namespace Robust.Shared.GameObjects
_running = false;
}
/// <inheritdoc />
public virtual void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _netSyncEnabled, "netsync", true);
}
/// <inheritdoc />
public void Dirty()
{

View File

@@ -3,6 +3,7 @@
namespace Robust.Shared.GameObjects
{
[AttributeUsage(AttributeTargets.Field)]
[MeansImplicitAssignment]
public class ComponentDependencyAttribute : Attribute
{
public readonly string? OnAddMethodName;

View File

@@ -129,13 +129,6 @@ namespace Robust.Shared.GameObjects
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component));
}
if (entity.Initialized || entity.Initializing)
{
var defaultSerializer = DefaultValueSerializer.Reader();
defaultSerializer.CurrentType = component.GetType();
component.ExposeData(defaultSerializer);
}
_componentDependencyManager.OnComponentAdd(entity, component);
component.OnAdd();

View File

@@ -5,8 +5,10 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -43,10 +45,15 @@ namespace Robust.Shared.GameObjects
{
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
private bool _canCollide;
private bool _isHard;
private BodyStatus _status;
private BodyType _bodyType;
[DataField("on")]
private bool _canCollide = true;
[DataField("hard")]
private bool _isHard = true;
[DataField("status")]
private BodyStatus _status = BodyStatus.OnGround;
[DataField("bodyType")]
private BodyType _bodyType = BodyType.Static;
[DataField("shapes")]
private List<IPhysShape> _physShapes = new();
/// <inheritdoc />
@@ -119,20 +126,6 @@ namespace Robust.Shared.GameObjects
PhysicsShapes = new PhysShapeList(this);
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _canCollide, "on", true);
serializer.DataField(ref _isHard, "hard", true);
serializer.DataField(ref _status, "status", BodyStatus.OnGround);
serializer.DataField(ref _bodyType, "bodyType", BodyType.Static);
serializer.DataField(ref _physShapes, "shapes", new List<IPhysShape> {new PhysShapeAabb()});
serializer.DataField(ref _anchored, "anchored", true);
serializer.DataField(ref _mass, "mass", 1.0f);
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)

View File

@@ -4,6 +4,8 @@ using System.Diagnostics.CodeAnalysis;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -168,11 +170,13 @@ namespace Robust.Shared.GameObjects
{
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
private float _mass = 1;
[DataField("mass")]
private float _mass = 1.0f;
private float _angularMass = 1;
private Vector2 _linVelocity;
private float _angVelocity;
private Dictionary<Type, VirtualController> _controllers = new();
[DataField("anchored")]
private bool _anchored = true;
private float _friction = 1;

View File

@@ -20,16 +20,6 @@ namespace Robust.Shared.GameObjects
public override void OnAdd() => throw new NotSupportedException();
}
/// <summary>
/// Throws an exception in <see cref="ExposeData" />.
/// </summary>
public sealed class DebugExceptionExposeDataComponent : Component
{
public override string Name => "DebugExceptionExposeData";
public override void ExposeData(ObjectSerializer serializer) => throw new NotSupportedException();
}
/// <summary>
/// Throws an exception in <see cref="Initialize" />.
/// </summary>

View File

@@ -1,8 +1,10 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -12,7 +14,9 @@ namespace Robust.Shared.GameObjects
public sealed override string Name => "Occluder";
public sealed override uint? NetID => NetIDs.OCCLUDER;
[DataField("enabled")]
private bool _enabled = true;
[DataField("boundingBox")]
private Box2 _boundingBox = new(-0.5f, -0.5f, 0.5f, 0.5f);
[ViewVariables(VVAccess.ReadWrite)]
@@ -53,14 +57,6 @@ namespace Robust.Shared.GameObjects
EntitySystem.Get<OccluderSystem>().AddOrUpdateEntity(Owner, Owner.Transform.Coordinates);
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _enabled, "enabled", true);
serializer.DataField(ref _boundingBox, "boundingBox", new Box2(-0.5f, -0.5f, 0.5f, 0.5f));
}
public override void OnRemove()
{
base.OnRemove();

View File

@@ -1,8 +1,5 @@
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects.Components.Localization
@@ -14,27 +11,15 @@ namespace Robust.Shared.GameObjects.Components.Localization
public override uint? NetID => NetIDs.GRAMMAR;
[ViewVariables]
[DataField("localizationId")]
public string LocalizationId = "";
[ViewVariables]
[DataField("gender")]
public Gender? Gender = null;
[ViewVariables]
[DataField("proper")]
public bool? ProperNoun = null;
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref LocalizationId, "localizationId", "");
if (serializer.TryReadDataFieldCached("gender", out string? gender0))
{
var refl = IoCManager.Resolve<IReflectionManager>();
if (refl.TryParseEnumReference(gender0!, out var gender))
{
Gender = (Gender)gender;
}
}
serializer.DataField(ref ProperNoun, "proper", null);
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -19,7 +21,8 @@ namespace Robust.Shared.GameObjects
public class MapComponent : Component, IMapComponent
{
[ViewVariables(VVAccess.ReadOnly)]
private MapId _mapIndex;
[DataField("index")]
private MapId _mapIndex = MapId.Nullspace;
/// <inheritdoc />
public override string Name => "Map";
@@ -59,14 +62,6 @@ namespace Robust.Shared.GameObjects
((TransformComponent) Owner.Transform).ChangeMapId(_mapIndex);
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _mapIndex, "index", MapId.Nullspace);
}
}
/// <summary>

View File

@@ -2,8 +2,10 @@
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -24,7 +26,8 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IMapManager _mapManager = default!;
[ViewVariables(VVAccess.ReadOnly)]
private GridId _gridIndex;
[DataField("index")]
private GridId _gridIndex = GridId.Invalid;
/// <inheritdoc />
public override string Name => "MapGrid";
@@ -80,14 +83,6 @@ namespace Robust.Shared.GameObjects
_gridIndex = state.GridIndex;
Grid.HasGravity = state.HasGravity;
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _gridIndex, "index", GridId.Invalid);
}
}
/// <summary>

View File

@@ -3,6 +3,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -68,7 +69,9 @@ namespace Robust.Shared.GameObjects
{
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[DataField("name")]
private string? _entityName;
[DataField("desc")]
private string? _entityDescription;
private EntityPrototype? _entityPrototype;
@@ -160,18 +163,6 @@ namespace Robust.Shared.GameObjects
_entityPrototype = _prototypes.Index<EntityPrototype>(state.PrototypeId);
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _entityName, "name", null);
serializer.DataField(ref _entityDescription, "desc", null);
//serializer.DataField(ref _entityPrototype, "proto", null,
// s => _prototypes.Index<EntityPrototype>(s),
// p => p.ID);
}
internal override void ClearTicks()
{
// Do not clear modified ticks.

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
@@ -54,16 +55,26 @@ namespace Robust.Shared.GameObjects
}
[Serializable, NetSerializable]
protected struct PrototypeLayerData : IExposeData
[DataDefinition]
public class PrototypeLayerData
{
[DataField("shader")]
public string? Shader;
[DataField("texture")]
public string? TexturePath;
[DataField("sprite")]
public string? RsiPath;
[DataField("state")]
public string? State;
public Vector2 Scale;
public Angle Rotation;
public bool Visible;
public Color Color;
[DataField("scale")]
public Vector2 Scale = Vector2.One;
[DataField("rotation")]
public Angle Rotation = Angle.Zero;
[DataField("visible")]
public bool Visible = true;
[DataField("color")]
public Color Color = Color.White;
[DataField("map")]
public List<string>? MapKeys;
public static PrototypeLayerData New()
@@ -75,19 +86,6 @@ namespace Robust.Shared.GameObjects
Visible = true,
};
}
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Shader, "shader", null);
serializer.DataField(ref TexturePath, "texture", null);
serializer.DataField(ref RsiPath, "sprite", null);
serializer.DataField(ref State, "state", null);
serializer.DataField(ref Scale, "scale", Vector2.One);
serializer.DataField(ref Rotation, "rotation", Angle.Zero);
serializer.DataField(ref Visible, "visible", true);
serializer.DataField(ref Color, "color", Color.White);
serializer.DataField(ref MapKeys, "map", null);
}
}
}
}

View File

@@ -6,7 +6,9 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
{
@@ -19,6 +21,7 @@ namespace Robust.Shared.GameObjects
public sealed override string Name => "SnapGrid";
private bool IsSet;
[DataField("offset")]
private SnapGridOffset _offset = SnapGridOffset.Center;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -53,13 +56,6 @@ namespace Robust.Shared.GameObjects
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref _offset, "offset", SnapGridOffset.Center);
}
/// <summary>
/// Returns an enumerable over all the entities which are one tile over in a certain direction.
/// </summary>

View File

@@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Robust.Shared.Animations;
using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -15,9 +18,13 @@ namespace Robust.Shared.GameObjects
{
internal class TransformComponent : Component, ITransformComponent, IComponentDebug
{
[DataField("parent")]
private EntityUid _parent;
private Vector2 _localPosition; // holds offset from grid, or offset from parent
[DataField("pos")]
private Vector2 _localPosition = Vector2.Zero; // holds offset from grid, or offset from parent
[DataField("rot")]
private Angle _localRotation; // local rotation
[DataField("noRot")]
private bool _noLocalRotation;
private Matrix3 _localMatrix = Matrix3.Identity;
@@ -689,16 +696,6 @@ namespace Robust.Shared.GameObjects
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _parent, "parent", new EntityUid());
serializer.DataField(ref _localPosition, "pos", Vector2.Zero);
serializer.DataField(ref _localRotation, "rot", new Angle());
serializer.DataField(ref _noLocalRotation, "noRot", false);
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)

View File

@@ -1,5 +1,8 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
{
@@ -8,15 +11,28 @@ namespace Robust.Shared.GameObjects
public sealed override string Name => "UserInterface";
public sealed override uint? NetID => NetIDs.USERINTERFACE;
protected sealed class PrototypeData : IExposeData
[DataDefinition]
public sealed class PrototypeData : ISerializationHooks
{
public object UiKey { get; private set; } = default!;
public string ClientType { get; private set; } = default!;
public object UiKey { get; set; } = default!;
void IExposeData.ExposeData(ObjectSerializer serializer)
[DataField("key", readOnly: true, required: true)]
private string _uiKeyRaw = default!;
[DataField("type", readOnly: true, required: true)]
public string ClientType { get; set; } = default!;
void ISerializationHooks.AfterDeserialization()
{
UiKey = serializer.ReadStringEnumKey("key");
ClientType = serializer.ReadDataField<string>("type");
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
if (reflectionManager.TryParseEnumReference(_uiKeyRaw, out var @enum))
{
UiKey = @enum;
return;
}
UiKey = _uiKeyRaw;
}
}

View File

@@ -100,13 +100,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
void Initialize();
/// <summary>
/// This allows setting of the component's parameters from YAML once it is instantiated.
/// This should basically be overridden by every inheriting component, as parameters will be different
/// across the board.
/// </summary>
void ExposeData(ObjectSerializer serializer);
/// <summary>
/// Handles a local incoming component message.
/// </summary>

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
{
[CopyByRef]
public interface IEntity
{
GameTick LastModifiedTick { get; }
@@ -40,7 +42,7 @@ namespace Robust.Shared.GameObjects
/// True if the entity has been deleted.
/// </summary>
bool Deleted { get; }
bool Paused { get; set; }
/// <summary>

View File

@@ -1,5 +1,7 @@
using System.Collections.Generic;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.GameObjects
@@ -12,11 +14,11 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Gets the serializer used to ExposeData a specific component.
/// </summary>
ObjectSerializer GetComponentSerializer(string componentName, YamlMappingNode? protoData);
IComponent GetComponentData(string componentName, IComponent? protoData);
/// <summary>
/// Gets extra component names that must also be instantiated on top of the ones defined in the prototype,
/// (and then deserialized with <see cref="GetComponentSerializer"/>)
/// (and then deserialized with <see cref="GetComponentData"/>)
/// </summary>
IEnumerable<string> GetExtraComponentTypes();
}

View File

@@ -3,6 +3,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Map
@@ -11,9 +12,11 @@ namespace Robust.Shared.Map
/// A physics shape that represents a <see cref="MapGrid"/>.
/// </summary>
[Serializable, NetSerializable]
public class PhysShapeGrid : IPhysShape
[DataDefinition]
public class PhysShapeGrid : IPhysShape, ISerializationHooks
{
private GridId _gridId;
[DataField("grid")]
private GridId _gridId = GridId.Invalid;
[NonSerialized]
private IMapGridInternal _mapGrid = default!;
@@ -40,6 +43,12 @@ namespace Robust.Shared.Map
set { }
}
void ISerializationHooks.AfterDeserialization()
{
var mapMan = IoCManager.Resolve<IMapManager>();
_mapGrid = (IMapGridInternal)mapMan.GetGrid(_gridId);
}
/// <inheritdoc />
public void ApplyState()
{
@@ -81,18 +90,6 @@ namespace Robust.Shared.Map
_gridId = _mapGrid.Index;
}
/// <inheritdoc />
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _gridId, "grid", GridId.Invalid);
if (serializer.Reading) // There is no Initialize function
{
var mapMan = IoCManager.Resolve<IMapManager>();
_mapGrid = (IMapGridInternal)mapMan.GetGrid(_gridId);
}
}
public event Action? OnDataChanged { add { } remove { } }
/// <inheritdoc />

View File

@@ -0,0 +1,6 @@
using System;
namespace Robust.Shared
{
public class MeansImplicitAssignmentAttribute : Attribute { }
}

View File

@@ -2,7 +2,7 @@
#nullable disable
namespace Robust.Shared.Network.Messages
namespace Robust.Shared.Network.Messages.Handshake
{
internal sealed class MsgEncryptionRequest : NetMessage
{

View File

@@ -3,7 +3,7 @@ using Lidgren.Network;
#nullable disable
namespace Robust.Shared.Network.Messages
namespace Robust.Shared.Network.Messages.Handshake
{
internal sealed class MsgEncryptionResponse : NetMessage
{

View File

@@ -2,7 +2,7 @@
#nullable disable
namespace Robust.Shared.Network.Messages
namespace Robust.Shared.Network.Messages.Handshake
{
internal sealed class MsgLoginStart : NetMessage
{

View File

@@ -2,7 +2,7 @@
#nullable disable
namespace Robust.Shared.Network.Messages
namespace Robust.Shared.Network.Messages.Handshake
{
internal sealed class MsgLoginSuccess : NetMessage
{

View File

@@ -13,6 +13,7 @@ using Lidgren.Network;
using Newtonsoft.Json;
using Robust.Shared.Log;
using Robust.Shared.Network.Messages;
using Robust.Shared.Network.Messages.Handshake;
using Robust.Shared.Utility;
namespace Robust.Shared.Network

View File

@@ -8,6 +8,7 @@ using Lidgren.Network;
using Newtonsoft.Json;
using Robust.Shared.Log;
using Robust.Shared.Network.Messages;
using Robust.Shared.Network.Messages.Handshake;
using Robust.Shared.Utility;
using UsernameHelpers = Robust.Shared.AuthLib.UsernameHelpers;

View File

@@ -1,6 +1,7 @@
using System;
using Robust.Shared.Maths;
namespace Robust.Shared.Maths
namespace Robust.Shared.Physics
{
/// <summary>
/// A representation of a 2D ray.

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.Physics
/// <summary>
/// A primitive physical shape that is used by a <see cref="IPhysBody"/>.
/// </summary>
public interface IPhysShape : IExposeData
public interface IPhysShape
{
/// <summary>
/// Raised when any of the parameters on this physics shape change.

View File

@@ -1,6 +1,7 @@
using System;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics
@@ -11,10 +12,14 @@ namespace Robust.Shared.Physics
/// entity origin in world space.
/// </summary>
[Serializable, NetSerializable]
[DataDefinition]
public class PhysShapeAabb : IPhysShape
{
[DataFieldWithFlag("layer", typeof(CollisionLayer))]
private int _collisionLayer;
[DataFieldWithFlag("mask", typeof(CollisionMask))]
private int _collisionMask;
[DataField("bounds")]
private Box2 _localBounds = Box2.UnitCentered;
/// <summary>
@@ -78,13 +83,5 @@ namespace Robust.Shared.Physics
{
return _localBounds;
}
/// <inheritdoc />
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _collisionLayer, "layer", 0, WithFormat.Flags<CollisionLayer>());
serializer.DataField(ref _collisionMask, "mask", 0, WithFormat.Flags<CollisionMask>());
serializer.DataField(ref _localBounds, "bounds", Box2.UnitCentered);
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics
@@ -10,12 +11,16 @@ namespace Robust.Shared.Physics
/// and it's origin is always the same as the entity position.
/// </summary>
[Serializable, NetSerializable]
[DataDefinition]
public class PhysShapeCircle : IPhysShape
{
private const float DefaultRadius = 0.5f;
[DataFieldWithFlag("layer", typeof(CollisionLayer))]
private int _collisionLayer;
[DataFieldWithFlag("mask", typeof(CollisionMask))]
private int _collisionMask;
[DataField("radius")]
private float _radius = DefaultRadius;
/// <inheritdoc />
@@ -60,14 +65,6 @@ namespace Robust.Shared.Physics
}
}
/// <inheritdoc />
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _collisionLayer, "layer", 0, WithFormat.Flags<CollisionLayer>());
serializer.DataField(ref _collisionMask, "mask", 0, WithFormat.Flags<CollisionMask>());
serializer.DataField(ref _radius, "radius", DefaultRadius);
}
/// <inheritdoc />
public Box2 CalculateLocalBounds(Angle rotation)
{

View File

@@ -1,6 +1,7 @@
using System;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics
@@ -11,11 +12,15 @@ namespace Robust.Shared.Physics
/// entity origin in world space.
/// </summary>
[Serializable, NetSerializable]
[DataDefinition]
public class PhysShapeRect : IPhysShape
{
[DataFieldWithFlag("layer", typeof(CollisionLayer))]
private int _collisionLayer;
[DataFieldWithFlag("mask", typeof(CollisionMask))]
private int _collisionMask;
[DataField("bounds")]
private Box2 _rectangle = Box2.UnitCentered;
[ViewVariables(VVAccess.ReadWrite)]
public Box2 Rectangle
@@ -64,13 +69,6 @@ namespace Robust.Shared.Physics
handle.SetTransform(Matrix3.Identity);
}
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _collisionLayer, "layer", 0, WithFormat.Flags<CollisionLayer>());
serializer.DataField(ref _collisionMask, "mask", 0, WithFormat.Flags<CollisionMask>());
serializer.DataField(ref _rectangle, "bounds", Box2.UnitCentered);
}
[field: NonSerialized]
public event Action? OnDataChanged;

View File

@@ -1,7 +1,8 @@
using Robust.Shared.Utility;
using System;
using System;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
namespace Robust.Shared.Physics
{
/// <summary>
/// A representation of a 2D ray.

View File

@@ -8,36 +8,45 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Prototypes
{
/// <summary>
/// Prototype that represents game entities.
/// </summary>
[Prototype("entity")]
public class EntityPrototype : IPrototype, ISyncingPrototype
[Prototype("entity", -1)]
public class EntityPrototype : IPrototype
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
/// <summary>
/// The "in code name" of the object. Must be unique.
/// </summary>
[ViewVariables]
[DataField("id")]
public string ID { get; private set; } = default!;
/// <summary>
/// The "in game name" of the object. What is displayed to most players.
/// </summary>
[ViewVariables, CanBeNull]
public string Name { get; private set; } = "";
[DataField("name")]
public string Name {
get => _name;
private set
{
_nameModified = true;
_name = Loc.GetString(value);
}
}
private string _name = "";
private bool _nameModified;
[DataField("localizationId")]
string? _localizationId;
/// <summary>
@@ -46,18 +55,8 @@ namespace Robust.Shared.Prototypes
[ViewVariables, CanBeNull]
public string? LocalizationID
{
get
{
if(_localizationId == null)
{
_localizationId = $"ent-{CaseConversion.PascalToKebab(ID)}";
}
return _localizationId;
}
private set
{
_localizationId = value;
}
get => _localizationId ??= $"ent-{CaseConversion.PascalToKebab(ID)}";
private set => _localizationId = value;
}
/// <summary>
@@ -65,13 +64,31 @@ namespace Robust.Shared.Prototypes
/// to provide additional info without ruining the Name property itself.
/// </summary>
[ViewVariables]
public string? EditorSuffix { get; private set; }
[DataField("suffix")]
public string? EditorSuffix
{
get => _editorSuffix;
private set => _editorSuffix = value != null ? Loc.GetString(value) : null;
}
private string? _editorSuffix;
/// <summary>
/// The description of the object that shows upon using examine
/// </summary>
[ViewVariables]
public string Description { get; private set; } = "";
[DataField("description")]
public string Description
{
get => _description;
private set
{
_descriptionModified = true;
_description = Loc.GetString(value);
}
}
private string _description = "";
private bool _descriptionModified;
@@ -79,54 +96,61 @@ namespace Robust.Shared.Prototypes
/// If true, this object should not show up in the entity spawn panel.
/// </summary>
[ViewVariables]
[NeverPushInheritance]
[DataField("abstract")]
public bool Abstract { get; private set; }
[DataField("placement")]
private EntityPlacementProperties PlacementProperties = new();
/// <summary>
/// The different mounting points on walls. (If any).
/// </summary>
[ViewVariables]
public List<int>? MountingPoints { get; private set; }
public List<int>? MountingPoints => PlacementProperties.MountingPoints;
/// <summary>
/// The Placement mode used for client-initiated placement. This is used for admin and editor placement. The serverside version controls what type the server assigns in normal gameplay.
/// </summary>
[ViewVariables]
public string PlacementMode { get; protected set; } = "PlaceFree";
public string PlacementMode => PlacementProperties.PlacementMode;
/// <summary>
/// The Range this entity can be placed from. This is only used serverside since the server handles normal gameplay. The client uses unlimited range since it handles things like admin spawning and editing.
/// </summary>
[ViewVariables]
public int PlacementRange { get; protected set; } = DEFAULT_RANGE;
public int PlacementRange => PlacementProperties.PlacementRange;
private const int DEFAULT_RANGE = 200;
/// <summary>
/// Set to hold snapping categories that this object has applied to it such as pipe/wire/wallmount
/// </summary>
private readonly HashSet<string> _snapFlags = new();
private HashSet<string> _snapFlags => PlacementProperties.SnapFlags;
private bool _snapOverriden = false;
private bool _snapOverriden => PlacementProperties.SnapOverriden;
/// <summary>
/// Offset that is added to the position when placing. (if any). Client only.
/// </summary>
[ViewVariables]
public Vector2i PlacementOffset { get; protected set; }
public Vector2i PlacementOffset => PlacementProperties.PlacementOffset;
private bool _placementOverriden = false;
private bool _placementOverriden => PlacementProperties.PlacementOverriden;
/// <summary>
/// True if this entity will be saved by the map loader.
/// </summary>
[ViewVariables]
[DataField("save")]
public bool MapSavable { get; protected set; } = true;
/// <summary>
/// The prototype we inherit from.
/// </summary>
[ViewVariables]
public EntityPrototype? Parent { get; private set; }
[DataField("parent")]
public string? Parent { get; private set; }
/// <summary>
/// A list of children inheriting from this prototype.
@@ -136,20 +160,12 @@ namespace Robust.Shared.Prototypes
public bool IsRoot => Parent == null;
/// <summary>
/// Used to store the parent id until we sync when all templates are done loading.
/// </summary>
private string? parentTemp;
/// <summary>
/// A dictionary mapping the component type list to the YAML mapping containing their settings.
/// </summary>
public Dictionary<string, YamlMappingNode> Components { get; } = new();
/// <summary>
/// The mapping node inside the <c>data</c> field of the prototype. Null if no data field exists.
/// </summary>
public YamlMappingNode? DataNode { get; set; }
[field: DataField("components")]
[field: AlwaysPushInheritance]
public ComponentRegistry Components { get; } = new();
private readonly HashSet<Type> ReferenceTypes = new();
@@ -163,322 +179,27 @@ namespace Robust.Shared.Prototypes
public EntityPrototype()
{
// Everybody gets a transform component!
Components.Add("Transform", new YamlMappingNode());
Components.Add("Transform", new TransformComponent());
// And a metadata component too!
Components.Add("MetaData", new YamlMappingNode());
Components.Add("MetaData", new MetaDataComponent());
}
public void LoadFrom(YamlMappingNode mapping)
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent
{
var loc = IoCManager.Resolve<ILocalizationManager>();
ID = mapping.GetNode("id").AsString();
if (mapping.TryGetNode("localizationId", out var locId)) {
_localizationId = locId.ToString();
}
if (mapping.TryGetNode("name", out var node))
if (!Components.TryGetValue(name, out var componentUnCast))
{
_nameModified = true;
Name = loc.GetString(node.AsString());
}
else if (loc.TryGetString($"ent-{CaseConversion.PascalToKebab(ID)}", out var name))
{
_nameModified = true;
Name = name;
component = default;
return false;
}
if (mapping.TryGetNode("parent", out node))
{
parentTemp = node.AsString();
}
// DESCRIPTION
if (mapping.TryGetNode("description", out node))
{
_descriptionModified = true;
Description = loc.GetString(node.AsString());
}
else if (loc.TryGetString($"ent-{CaseConversion.PascalToKebab(ID)}.desc", out var name))
{
_descriptionModified = true;
Description = loc.GetString(name);
}
if (mapping.TryGetNode("suffix", out node))
{
EditorSuffix = loc.GetString(node.AsString());
}
// COMPONENTS
if (mapping.TryGetNode<YamlSequenceNode>("components", out var componentsequence))
{
foreach (var componentMapping in componentsequence.Cast<YamlMappingNode>())
{
ReadComponent(componentMapping, _componentFactory);
}
// Assert that there are no conflicting component references.
foreach (var componentName in Components.Keys)
{
var registration = _componentFactory.GetRegistration(componentName);
foreach (var type in registration.References)
{
if (ReferenceTypes.Contains(type))
{
throw new InvalidOperationException(
$"Duplicate component reference in prototype: '{type}'");
}
ReferenceTypes.Add(type);
}
}
}
// DATA FIELD
if (mapping.TryGetNode<YamlMappingNode>("data", out var dataMapping))
{
DataNode = dataMapping;
}
// PLACEMENT
// TODO: move to a component or something. Shouldn't be a root part of prototypes IMO.
if (mapping.TryGetNode<YamlMappingNode>("placement", out var placementMapping))
{
ReadPlacementProperties(placementMapping);
}
// SAVING
if (mapping.TryGetNode("save", out node))
{
MapSavable = node.AsBool();
}
if (mapping.TryGetNode("abstract", out node))
{
Abstract = node.AsBool();
}
}
private void ReadPlacementProperties(YamlMappingNode mapping)
{
if (mapping.TryGetNode("mode", out var node))
{
PlacementMode = node.AsString();
_placementOverriden = true;
}
if (mapping.TryGetNode("offset", out node))
{
PlacementOffset = node.AsVector2i();
_placementOverriden = true;
}
if (mapping.TryGetNode<YamlSequenceNode>("nodes", out var sequence))
{
MountingPoints = sequence.Select(p => p.AsInt()).ToList();
}
if (mapping.TryGetNode("range", out node))
{
PlacementRange = node.AsInt();
}
// Reads snapping flags that this object holds that describe its properties to such as wire/pipe/wallmount, used to prevent certain stacked placement
if (mapping.TryGetNode<YamlSequenceNode>("snap", out var snapSequence))
{
var flagsList = snapSequence.Select(p => p.AsString());
foreach (var flag in flagsList)
{
_snapFlags.Add(flag);
}
_snapOverriden = true;
}
}
public void Reset()
{
Children.Clear();
}
// Resolve inheritance.
public bool Sync(IPrototypeManager manager, int stage)
{
switch (stage)
{
case 0:
if (parentTemp == null)
{
return true;
}
Parent = manager.Index<EntityPrototype>(parentTemp);
if (Parent.Children == null)
{
Parent.Children = new List<EntityPrototype>();
}
Parent.Children.Add(this);
return false;
case 1:
// We are a root-level prototype.
// As such we're getting the duty of pushing inheritance into everybody's face.
// Can't do a "puller" system where each queries the parent because it requires n stages
// (n being the depth of each inheritance tree)
if (Children == null)
{
break;
}
PushInheritanceAll();
break;
}
return false;
}
/// <summary>
/// Iteratively pushes inheritance down to all children, children's children, etc. breadth-first.
/// </summary>
private void PushInheritanceAll()
{
if (Children == null)
{
return;
}
var sourceTargets = new List<(EntityPrototype, List<EntityPrototype>)> {(this, Children)};
var newSources = new List<EntityPrototype>();
while (true)
{
foreach (var (source, targetList) in sourceTargets)
{
if (targetList == null)
{
continue;
}
foreach (var target in targetList)
{
PushInheritance(_componentFactory, source, target);
}
newSources.AddRange(targetList);
}
if (newSources.Count == 0)
{
break;
}
sourceTargets.Clear();
foreach (var newSource in newSources)
{
sourceTargets.Add((newSource, newSource.Children));
}
newSources.Clear();
}
}
private static void PushInheritance(IComponentFactory factory, EntityPrototype source, EntityPrototype target)
{
// Copy component data over.
foreach (KeyValuePair<string, YamlMappingNode> component in source.Components)
{
if (target.Components.TryGetValue(component.Key, out var targetComponent))
{
// Copy over values the target component does not have.
foreach (YamlNode key in component.Value.Children.Keys)
{
if (!targetComponent.Children.ContainsKey(key))
{
targetComponent.Children[key] = component.Value[key];
}
}
}
else
{
// Copy component into the target, since it doesn't have it yet.
// Unless it'd cause a conflict.
foreach (var refType in factory.GetRegistration(component.Key).References)
{
if (target.ReferenceTypes.Contains(refType))
{
// yeah nope the child's got it!! NEXT!!
goto next;
}
}
target.Components[component.Key] = new YamlMappingNode(component.Value.AsEnumerable());
}
next: ;
}
// Copy all simple data over.
if (!target._placementOverriden)
{
target.PlacementMode = source.PlacementMode;
}
if (!target._placementOverriden)
{
target.PlacementOffset = source.PlacementOffset;
}
if (target.MountingPoints == null && source.MountingPoints != null)
{
target.MountingPoints = new List<int>(source.MountingPoints);
}
if (target.PlacementRange == DEFAULT_RANGE)
{
target.PlacementRange = source.PlacementRange;
}
if (!target._descriptionModified)
{
target.Description = source.Description;
}
if (target.EditorSuffix == null)
{
target.EditorSuffix = source.EditorSuffix;
}
if (!target._snapOverriden)
{
foreach (var flag in source._snapFlags)
{
target._snapFlags.Add(flag);
}
}
if (!target._nameModified)
{
target.Name = source.Name;
}
if (target.Children == null)
{
return;
}
// There are no duplicate component names
// TODO Sanity check with names being in an attribute of the type instead
component = (T) componentUnCast;
return true;
}
public void UpdateEntity(Entity entity)
{
bool HasBeenModified(string name, YamlMappingNode data, EntityPrototype prototype, IComponent currentComponent, IComponentFactory factory)
{
var component = factory.GetComponent(name);
prototype.CurrentDeserializingComponent = name;
ObjectSerializer ser = YamlObjectSerializer.NewReader(data, new PrototypeSerializationContext(prototype));
component.ExposeData(ser);
return component == (Component) currentComponent;
}
if (ID != entity.Prototype?.ID)
{
Logger.Error($"Reloaded prototype used to update entity did not match entity's existing prototype: Expected '{ID}', got '{entity.Prototype?.ID}'");
@@ -503,8 +224,7 @@ namespace Robust.Shared.Prototypes
// Find components to be removed, and remove them
foreach (var (name, type) in oldPrototypeComponents.Except(newPrototypeComponents))
{
if (!HasBeenModified(name, oldPrototype.Components[name], oldPrototype, entity.GetComponent(type),
factory) && Components.Keys.Contains(name))
if (Components.Keys.Contains(name))
{
ignoredComponents.Add(name);
continue;
@@ -515,15 +235,16 @@ namespace Robust.Shared.Prototypes
componentManager.CullRemovedComponents();
var componentDependencyManager = IoCManager.Resolve<IComponentDependencyManager>();
// Add new components
foreach (var (name, type) in newPrototypeComponents.Where(t => !ignoredComponents.Contains(t.name)).Except(oldPrototypeComponents))
{
var data = Components[name];
var component = (Component) factory.GetComponent(name);
ObjectSerializer ser = YamlObjectSerializer.NewReader(data, new PrototypeSerializationContext(this));
CurrentDeserializingComponent = name;
component.Owner = entity;
component.ExposeData(ser);
componentDependencyManager.OnComponentAdd(entity, component);
entity.AddComponent(component);
}
@@ -531,30 +252,25 @@ namespace Robust.Shared.Prototypes
entity.MetaData.EntityPrototype = this;
}
internal static void LoadEntity(EntityPrototype? prototype, Entity entity, IComponentFactory factory, IEntityLoadContext? context)
internal static void LoadEntity(EntityPrototype? prototype, Entity entity, IComponentFactory factory, IEntityLoadContext? context) //yeah officer this method right here
{
YamlObjectSerializer.Context? defaultContext = null;
/*YamlObjectSerializer.Context? defaultContext = null;
if (context == null)
{
defaultContext = new PrototypeSerializationContext(prototype);
}
}*/
if (prototype != null)
{
foreach (var (name, data) in prototype.Components)
{
ObjectSerializer ser;
var fullData = data;
if (context != null)
{
ser = context.GetComponentSerializer(name, data);
}
else
{
prototype.CurrentDeserializingComponent = name;
ser = YamlObjectSerializer.NewReader(data, defaultContext);
fullData = context.GetComponentData(name, data);
}
EnsureCompExistsAndDeserialize(entity, factory, name, ser);
EnsureCompExistsAndDeserialize(entity, factory, name, fullData, context as ISerializationContext);
}
}
@@ -570,17 +286,16 @@ namespace Robust.Shared.Prototypes
continue;
}
var ser = context.GetComponentSerializer(name, null);
var ser = context.GetComponentData(name, null);
EnsureCompExistsAndDeserialize(entity, factory, name, ser);
EnsureCompExistsAndDeserialize(entity, factory, name, ser, context as ISerializationContext);
}
}
}
private static void EnsureCompExistsAndDeserialize(Entity entity, IComponentFactory factory, string compName, ObjectSerializer ser)
private static void EnsureCompExistsAndDeserialize(Entity entity, IComponentFactory factory, string compName, IComponent data, ISerializationContext? context)
{
var compType = factory.GetRegistration(compName).Type;
ser.CurrentType = compType;
if (!entity.TryGetComponent(compType, out var component))
{
@@ -590,39 +305,8 @@ namespace Robust.Shared.Prototypes
component = newComponent;
}
component.ExposeData(ser);
}
private void ReadComponent(YamlMappingNode mapping, IComponentFactory factory)
{
string type = mapping.GetNode("type").AsString();
// See if type exists to detect errors.
switch (factory.GetComponentAvailability(type))
{
case ComponentAvailability.Available:
break;
case ComponentAvailability.Ignore:
return;
case ComponentAvailability.Unknown:
Log.Logger.Error($"Unknown component '{type}' in prototype {ID}!");
return;
}
// Has this type already been added?
if (Components.Keys.Contains(type))
{
Log.Logger.Error($"Component of type '{type}' defined twice in prototype {ID}!");
return;
}
var copy = new YamlMappingNode(mapping.AsEnumerable());
// TODO: figure out a better way to exclude the type node.
// Also maybe deep copy this? Right now it's pretty error prone.
copy.Children.Remove(new YamlScalarNode("type"));
Components[type] = copy;
// TODO use this value to support struct components
_ = IoCManager.Resolve<ISerializationManager>().Copy(data, component, context);
}
public override string ToString()
@@ -630,7 +314,64 @@ namespace Robust.Shared.Prototypes
return $"EntityPrototype({ID})";
}
private class PrototypeSerializationContext : YamlObjectSerializer.Context
public class ComponentRegistry : Dictionary<string, IComponent>
{
public ComponentRegistry()
{
}
public ComponentRegistry(Dictionary<string, IComponent> components) : base(components)
{
}
}
[DataDefinition]
public class EntityPlacementProperties
{
public bool PlacementOverriden { get; private set; }
public bool SnapOverriden { get; private set; }
private string _placementMode = "PlaceFree";
private Vector2i _placementOffset;
[DataField("mode")]
public string PlacementMode
{
get => _placementMode;
set
{
PlacementOverriden = true;
_placementMode = value;
}
}
[DataField("offset")]
public Vector2i PlacementOffset
{
get => _placementOffset;
set
{
PlacementOverriden = true;
_placementOffset = value;
}
}
[DataField("nodes")] public List<int>? MountingPoints;
[DataField("range")] public int PlacementRange = DEFAULT_RANGE;
private HashSet<string> _snapFlags = new ();
[DataField("snap")]
public HashSet<string> SnapFlags
{
get => _snapFlags;
set
{
SnapOverriden = true;
_snapFlags = value;
}
}
}
/*private class PrototypeSerializationContext : YamlObjectSerializer.Context
{
readonly EntityPrototype? prototype;
@@ -696,6 +437,6 @@ namespace Robust.Shared.Prototypes
return prototype.DataCache.TryGetValue(field, out value);
}
}
}*/
}
}

View File

@@ -17,31 +17,6 @@ namespace Robust.Shared.Prototypes
/// </summary>
string ID { get; }
/// <summary>
/// Load data from the YAML mappings in the prototype files.
/// </summary>
void LoadFrom(YamlMappingNode mapping);
}
/// <summary>
/// Extension on <see cref="IPrototype"/> that allows "syncing" between prototypes after all prototypes have done initial loading.
/// To resolve reference like the entity prototype parenting.
/// </summary>
public interface ISyncingPrototype
{
void Reset();
/// <summary>
/// Sync and update cross-referencing data.
/// Syncing works in stages, each time it will be called with the stage it's currently on.
/// Each prototype will be called in a stage, then the stage count goes up.
/// </summary>
/// <remarks>
/// The order of syncing is in no way guaranteed to be consistent across stages.
/// This means that on stage 1 prototype A might sync first, but on stage 2 prototype B might.
/// </remarks>
/// <param name="stage">The current sync stage.</param>
/// <returns>Whether or not the prototype will be included in the next sync stage</returns>
bool Sync(IPrototypeManager manager, int stage);
string? Parent { get; }
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
namespace Robust.Shared.Prototypes
{
public class PrototypeInheritanceTree
{
private Dictionary<string, HashSet<string>> _nodes = new();
private Dictionary<string, HashSet<string>> _pendingParent = new();
private HashSet<string> _baseNodes = new();
private Dictionary<string, string> _parents = new();
public IReadOnlySet<string> BaseNodes => _baseNodes;
public IReadOnlySet<string> Children(string id)
{
if (!_nodes.ContainsKey(id))
throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id));
return _nodes[id];
}
public string GetBaseNode(string id)
{
if (!_nodes.ContainsKey(id))
throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id));
var parent = id;
while (_parents.TryGetValue(parent, out var nextParent))
{
parent = nextParent;
}
return parent;
}
public string? GetParent(string id)
{
return _parents.GetValueOrDefault(id);
}
public void AddId(string id, string? parent, bool overwrite = false)
{
if (overwrite && HasId(id))
{
RemoveId(id);
}
if (_nodes.ContainsKey(id))
throw new ArgumentException($"ID {id} already present in InheritanceTree", nameof(id));
if (parent != null)
{
_parents.Add(id, parent);
if (_nodes.TryGetValue(parent, out var parentsChildren))
{
parentsChildren.Add(id);
}
else
{
if (!_pendingParent.TryGetValue(parent, out _))
{
_pendingParent[parent] = new HashSet<string>();
}
_pendingParent[parent].Add(id);
}
//cycle detection
var currentParent = parent;
while (currentParent != null)
{
if (currentParent == id)
throw new InvalidOperationException(
$"Cycle detected when trying to add id {id} with parent {parent}");
_parents.TryGetValue(currentParent, out currentParent);
}
}
else
{
_baseNodes.Add(id);
}
if (!_pendingParent.TryGetValue(id, out var ourChildren))
ourChildren = new HashSet<string>();
_nodes.Add(id, ourChildren);
}
public bool HasId(string id)
{
return _nodes.ContainsKey(id);
}
public void RemoveId(string id)
{
if (!_nodes.ContainsKey(id))
throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id));
_nodes.Remove(id);
foreach (var (_, children) in _pendingParent)
{
children.Remove(id);
}
_baseNodes.Remove(id);
_parents.Remove(id);
}
}
}

View File

@@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Robust.Shared.Asynchronous;
using Robust.Shared.ContentPack;
@@ -15,6 +14,11 @@ using Robust.Shared.IoC.Exceptions;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
@@ -65,7 +69,9 @@ namespace Robust.Shared.Prototypes
/// <summary>
/// Load prototypes from files in a directory, recursively.
/// </summary>
Task<List<IPrototype>> LoadDirectory(ResourcePath path);
List<IPrototype> LoadDirectory(ResourcePath path);
Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path);
List<IPrototype> LoadFromStream(TextReader stream);
@@ -108,13 +114,16 @@ namespace Robust.Shared.Prototypes
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[BaseTypeRequired(typeof(IPrototype))]
[MeansImplicitUse]
[MeansDataDefinition]
public class PrototypeAttribute : Attribute
{
private readonly string type;
public string Type => type;
public PrototypeAttribute(string type)
public readonly int LoadPriority = 1;
public PrototypeAttribute(string type, int loadPriority = 1)
{
this.type = type;
LoadPriority = loadPriority;
}
}
@@ -126,8 +135,10 @@ namespace Robust.Shared.Prototypes
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] public readonly ITaskManager TaskManager = default!;
[Dependency] public readonly INetManager NetManager = default!;
[Dependency] private readonly ISerializationManager _serializationManager = default!;
private readonly Dictionary<string, Type> prototypeTypes = new();
private readonly Dictionary<Type, int> prototypePriorities = new();
private bool _initialized;
private bool _hasEverBeenReloaded;
@@ -135,6 +146,8 @@ namespace Robust.Shared.Prototypes
#region IPrototypeManager members
private readonly Dictionary<Type, Dictionary<string, IPrototype>> prototypes = new();
private readonly Dictionary<Type, Dictionary<string, DeserializationResult>> _prototypeResults = new();
private readonly Dictionary<Type, PrototypeInheritanceTree> _inheritanceTrees = new();
private readonly HashSet<string> IgnoredPrototypeTypes = new();
@@ -198,24 +211,68 @@ namespace Robust.Shared.Prototypes
{
prototypeTypes.Clear();
prototypes.Clear();
_prototypeResults.Clear();
_inheritanceTrees.Clear();
}
public virtual async void ReloadPrototypes(ResourcePath file)
private int SortPrototypesByPriority(Type a, Type b)
{
return prototypePriorities[b].CompareTo(prototypePriorities[a]);
}
public virtual void ReloadPrototypes(ResourcePath file)
{
#if !FULL_RELEASE
var changed = await LoadFile(file.ToRootedPath(), true);
Resync();
var changed = LoadFile(file.ToRootedPath(), true).ToList();
changed.Sort((prototype, prototype1) => SortPrototypesByPriority(prototype.GetType(), prototype1.GetType()));
var pushed = new Dictionary<Type, HashSet<string>>();
foreach (var prototype in changed)
{
if (prototype is not EntityPrototype entityPrototype)
var type = prototype.GetType();
if (!pushed.ContainsKey(type)) pushed[type] = new HashSet<string>();
var baseNode = prototype.ID;
if (pushed[type].Contains(baseNode))
{
continue;
}
foreach (var entity in _entityManager.GetEntities(new PredicateEntityQuery(e => e.Prototype != null && e.Prototype.ID == entityPrototype.ID)))
var tree = _inheritanceTrees[type];
var currentNode = prototype.Parent;
if (currentNode == null)
{
entityPrototype.UpdateEntity((Entity) entity);
PushInheritance(type, baseNode, null, pushed[type]);
continue;
}
while (true)
{
var parent = tree.GetParent(currentNode);
if (parent == null)
{
break;
}
baseNode = currentNode;
currentNode = parent;
}
PushInheritance(type, currentNode, baseNode, null, pushed[type]);
}
// TODO filter by entity prototypes changed
if (!pushed.ContainsKey(typeof(EntityPrototype))) return;
var entityPrototypes = prototypes[typeof(EntityPrototype)];
foreach (var prototype in pushed[typeof(EntityPrototype)])
{
foreach (var entity in _entityManager.GetEntities(new PredicateEntityQuery(e => e.Prototype != null && e.Prototype.ID == prototype)))
{
((EntityPrototype) entityPrototypes[prototype]).UpdateEntity((Entity) entity);
}
}
#endif
@@ -223,66 +280,51 @@ namespace Robust.Shared.Prototypes
public void Resync()
{
// TODO Make this smarter and only resync changed prototypes
if (_hasEverResynced)
var trees = _inheritanceTrees.Keys.ToList();
trees.Sort(SortPrototypesByPriority);
foreach (var type in trees)
{
foreach (var prototypeList in prototypes.Values)
var tree = _inheritanceTrees[type];
foreach (var baseNode in tree.BaseNodes)
{
foreach (var prototype in prototypeList.Values)
{
if (prototype is ISyncingPrototype syncing)
{
syncing.Reset();
}
}
PushInheritance(type, baseNode, null, new HashSet<string>());
}
}
}
foreach (Type type in prototypeTypes.Values.Where(t => typeof(ISyncingPrototype).IsAssignableFrom(t)))
public void PushInheritance(Type type, string id, string child, DeserializationResult? baseResult, HashSet<string> changed)
{
changed.Add(id);
var myRes = _prototypeResults[type][id];
var newResult = baseResult != null ? myRes.PushInheritanceFrom(baseResult) : myRes;
PushInheritance(type, child, newResult, changed);
newResult.CallAfterDeserializationHook();
var populatedRes = _serializationManager.PopulateDataDefinition(prototypes[type][id], (IDeserializedDefinition)newResult);
prototypes[type][id] = (IPrototype) populatedRes.RawValue!;
}
public void PushInheritance(Type type, string id, DeserializationResult? baseResult, HashSet<string> changed)
{
changed.Add(id);
var myRes = _prototypeResults[type][id];
var newResult = baseResult != null ? myRes.PushInheritanceFrom(baseResult) : myRes;
foreach (var childID in _inheritanceTrees[type].Children(id))
{
// This list is the list of prototypes we're syncing.
// Iterate using indices.
// IF the prototype wants to NOT by synced again,
// Swap remove it with the one at the end of the list,
// and do the whole thing again with the one formerly at the end of the list
// otherwise keep it and move up an index
// When we get to the end, do the whole thing again!
// Yes this is ridiculously overengineered BUT IT PERFORMS WELL.
// I hope.
List<ISyncingPrototype> currentRun = prototypes[type].Values.Select(p => (ISyncingPrototype) p).ToList();
var stage = 0;
// Outer loop to iterate stages.
while (currentRun.Count > 0)
{
// Increase positions to iterate over list.
// If we need to stick, i gets reduced down below.
for (var i = 0; i < currentRun.Count; i++)
{
ISyncingPrototype prototype = currentRun[i];
var result = prototype.Sync(this, stage);
// Keep prototype and move on to next one if it returns true.
// Thus it stays in the list for next stage.
if (result)
{
continue;
}
// Move the last element in the list to where we are currently.
// Since we don't break we'll do this one next, as i stays the same.
// (for loop cancels out decrement here)
currentRun.RemoveSwap(i);
i--;
}
stage++;
}
PushInheritance(type, childID, newResult, changed);
}
_hasEverResynced = true;
newResult.CallAfterDeserializationHook();
var populatedRes = _serializationManager.PopulateDataDefinition(prototypes[type][id], (IDeserializedDefinition)newResult);
prototypes[type][id] = (IPrototype) populatedRes.RawValue!;
}
/// <inheritdoc />
public async Task<List<IPrototype>> LoadDirectory(ResourcePath path)
public List<IPrototype> LoadDirectory(ResourcePath path)
{
var changedPrototypes = new List<IPrototype>();
@@ -292,14 +334,64 @@ namespace Robust.Shared.Prototypes
foreach (var resourcePath in streams)
{
var filePrototypes = await LoadFile(resourcePath);
var filePrototypes = LoadFile(resourcePath);
changedPrototypes.AddRange(filePrototypes);
}
return changedPrototypes;
}
private Task<StreamReader?> ReadFile(ResourcePath file, bool @throw = true)
public Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path)
{
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
var dict = new Dictionary<string, HashSet<ErrorNode>>();
foreach (var resourcePath in streams)
{
using var reader = ReadFile(resourcePath);
if (reader == null)
{
continue;
}
var yamlStream = new YamlStream();
yamlStream.Load(reader);
for (var i = 0; i < yamlStream.Documents.Count; i++)
{
var rootNode = (YamlSequenceNode) yamlStream.Documents[i].RootNode;
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
{
var type = node.GetNode("type").AsString();
if (!prototypeTypes.ContainsKey(type))
{
if (IgnoredPrototypeTypes.Contains(type))
{
continue;
}
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
}
var mapping = node.ToDataNodeCast<MappingDataNode>();
mapping.RemoveNode("type");
var errorNodes = _serializationManager.ValidateNode(prototypeTypes[type], mapping).GetErrors().ToHashSet();
if (errorNodes.Count != 0)
{
if (!dict.TryGetValue(resourcePath.ToString(), out var hashSet))
dict[resourcePath.ToString()] = new HashSet<ErrorNode>();
dict[resourcePath.ToString()].UnionWith(errorNodes);
}
}
}
}
return dict;
}
private StreamReader? ReadFile(ResourcePath file, bool @throw = true)
{
var retries = 0;
@@ -309,8 +401,7 @@ namespace Robust.Shared.Prototypes
try
{
var reader = new StreamReader(Resources.ContentFileRead(file), EncodingHelpers.UTF8);
return Task.FromResult<StreamReader?>(reader);
return reader;
}
catch (IOException e)
{
@@ -322,7 +413,7 @@ namespace Robust.Shared.Prototypes
}
Logger.Error($"Error reloading prototypes in file {file}.", e);
return Task.FromResult<StreamReader?>(null);
return null;
}
retries++;
@@ -331,13 +422,13 @@ namespace Robust.Shared.Prototypes
}
}
public async Task<List<IPrototype>> LoadFile(ResourcePath file, bool overwrite = false)
public HashSet<IPrototype> LoadFile(ResourcePath file, bool overwrite = false)
{
var changedPrototypes = new List<IPrototype>();
var changedPrototypes = new HashSet<IPrototype>();
try
{
using var reader = await ReadFile(file, !overwrite);
using var reader = ReadFile(file, !overwrite);
if (reader == null)
{
@@ -354,7 +445,7 @@ namespace Robust.Shared.Prototypes
try
{
var documentPrototypes = LoadFromDocument(yamlStream.Documents[i], overwrite);
changedPrototypes.AddRange(documentPrototypes);
changedPrototypes.UnionWith(documentPrototypes);
}
catch (Exception e)
{
@@ -418,9 +509,9 @@ namespace Robust.Shared.Prototypes
}
}
private List<IPrototype> LoadFromDocument(YamlDocument document, bool overwrite = false)
private HashSet<IPrototype> LoadFromDocument(YamlDocument document, bool overwrite = false)
{
var changedPrototypes = new List<IPrototype>();
var changedPrototypes = new HashSet<IPrototype>();
var rootNode = (YamlSequenceNode) document.RootNode;
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
@@ -437,19 +528,19 @@ namespace Robust.Shared.Prototypes
}
var prototypeType = prototypeTypes[type];
var prototype = _dynamicTypeFactory.CreateInstanceUnchecked<IPrototype>(prototypeType);
var res = _serializationManager.Read(prototypeType, node.ToDataNode(), skipHook: true);
var prototype = (IPrototype) res.RawValue!;
prototype.LoadFrom(node);
changedPrototypes.Add(prototype);
var id = prototype.ID;
if (!overwrite && prototypes[prototypeType].ContainsKey(id))
if (!overwrite && prototypes[prototypeType].ContainsKey(prototype.ID))
{
throw new PrototypeLoadException($"Duplicate ID: '{id}'");
throw new PrototypeLoadException($"Duplicate ID: '{prototype.ID}'");
}
prototypes[prototypeType][id] = prototype;
_prototypeResults[prototypeType][prototype.ID] = res;
_inheritanceTrees[prototypeType].AddId(prototype.ID, prototype.Parent, true);
prototypes[prototypeType][prototype.ID] = prototype;
changedPrototypes.Add(prototype);
}
return changedPrototypes;
@@ -503,10 +594,13 @@ namespace Robust.Shared.Prototypes
}
prototypeTypes[attribute.Type] = type;
prototypePriorities[type] = attribute.LoadPriority;
if (typeof(IPrototype).IsAssignableFrom(type))
{
prototypes[type] = new Dictionary<string, IPrototype>();
_prototypeResults[type] = new Dictionary<string, DeserializationResult>();
_inheritanceTrees[type] = new PrototypeInheritanceTree();
}
}

View File

@@ -71,6 +71,13 @@ namespace Robust.Shared.Reflection
/// <returns>Enumeration of all types with the specified attribute.</returns>
IEnumerable<Type> FindTypesWithAttribute<T>() where T : Attribute;
/// <summary>
/// Finds all Types in all Assemblies that have a specific Attribute.
/// </summary>
/// <param name="attributeType">Attribute to search for.</param>
/// <returns>Enumeration of all types with the specified attribute.</returns>
IEnumerable<Type> FindTypesWithAttribute(Type attributeType);
/// <summary>
/// Loads assemblies into the manager and get all the types.
/// </summary>

View File

@@ -160,12 +160,18 @@ namespace Robust.Shared.Reflection
/// <inheritdoc />
public IEnumerable<Type> FindTypesWithAttribute<T>() where T : Attribute
{
return FindTypesWithAttribute(typeof(T));
}
/// <inheritdoc />
public IEnumerable<Type> FindTypesWithAttribute(Type attributeType)
{
var types = new List<Type>();
foreach (var assembly in Assemblies)
{
types.AddRange(assembly.GetTypes().Where(type => Attribute.IsDefined(type, typeof(T))));
types.AddRange(assembly.GetTypes().Where(type => Attribute.IsDefined(type, attributeType)));
}
return types;

View File

@@ -0,0 +1,34 @@
using System;
namespace Robust.Shared.Serialization
{
/// <summary>
/// Attribute for marking an enum type as being the constant representation for a field.
///
/// Some fields are arbitrary ints, but it's helpful for readability to have them be
/// named constants instead. This allows for that.
///
/// NB: AllowMultiple is <c>true</c> - don't assume the same representation cannot
/// be reused between multiple fields.
/// </summary>
[AttributeUsage(AttributeTargets.Enum, AllowMultiple = true, Inherited = false)]
public class ConstantsForAttribute : Attribute
{
private readonly Type _tag;
public Type Tag => _tag;
// NB: This is not generic because C# does not allow generic attributes
/// <summary>
/// An attribute with tag type <paramref name="tag"/>
/// </summary>
/// <param name="tag">
/// An arbitrary tag type used for coordinating between the data field and the
/// representation. Not actually used for serialization/deserialization.
/// </param>
public ConstantsForAttribute(Type tag)
{
_tag = tag;
}
}
}

View File

@@ -1,317 +0,0 @@
using Robust.Shared.IoC;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Robust.Shared.Reflection;
namespace Robust.Shared.Serialization
{
/// <inheritdoc cref="ICustomFormatManager"/>
public class CustomFormatManager : ICustomFormatManager
{
private Dictionary<Type, WithFormat<int>> _flagFormatters = new();
private Dictionary<Type, WithFormat<int>> _constantFormatters = new();
public WithFormat<int> FlagFormat<T>()
{
if (!_flagFormatters.TryGetValue(typeof(T), out var formatter))
{
formatter = new WithFlagRepresentation(GetFlag<T>());
_flagFormatters.Add(typeof(T), formatter);
}
return formatter;
}
public WithFormat<int> ConstantFormat<T>()
{
if (!_constantFormatters.TryGetValue(typeof(T), out var formatter))
{
formatter = new WithConstantRepresentation(GetConstants<T>());
_constantFormatters.Add(typeof(T), formatter);
}
return formatter;
}
/// <summary>
/// Get the enum flag type for the given tag <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">
/// The tag type to use for finding the flag representation. To learn more,
/// see the <see cref="FlagsForAttribute"/>.
/// </typeparam>
/// <exception cref="FlagSerializerException">
/// Thrown if:
/// <list type="bullet">
/// <item>
/// <description>The tag type corresponds to no enum flag representation.</description>
/// </item>
/// <item>
/// <description>The tag type corresponds to more than one enum flag representation.</description>
/// </item>
/// <item>
/// <description>The tag type corresponds to a non-enum representation.</description>
/// </item>
/// <item>
/// <description>The tag type corresponds to a non-int enum representation.</description>
/// </item>
/// <item>
/// <description>The tag type corresponds to a non-bitflag int enum representation.</description>
/// </item>
/// </list>
/// </exception>
/// <returns>
/// The unique int-backed bitflag enum type for the given tag.
/// </returns>
private Type GetFlag<T>()
{
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
Type? flagType = null;
foreach (Type bitflagType in reflectionManager.FindTypesWithAttribute<FlagsForAttribute>())
{
foreach (var flagsforAttribute in bitflagType.GetCustomAttributes<FlagsForAttribute>(true))
{
if (typeof(T) == flagsforAttribute.Tag)
{
if (flagType != null)
{
throw new NotSupportedException($"Multiple bitflag enums declared for the tag {flagsforAttribute.Tag}.");
}
if (!bitflagType.IsEnum)
{
throw new FlagSerializerException($"Could not create FlagSerializer for non-enum {bitflagType}.");
}
if (Enum.GetUnderlyingType(bitflagType) != typeof(int))
{
throw new FlagSerializerException($"Could not create FlagSerializer for non-int enum {bitflagType}.");
}
if (!bitflagType.GetCustomAttributes<FlagsAttribute>(false).Any())
{
throw new FlagSerializerException($"Could not create FlagSerializer for non-bitflag enum {bitflagType}.");
}
flagType = bitflagType;
}
}
}
if (flagType == null)
{
throw new FlagSerializerException($"Found no type marked with a `FlagsForAttribute(typeof({typeof(T)}))`.");
}
return flagType;
}
/// <summary>
/// Get the constant type for the given tag <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">
/// The tag type to use for finding the constant representation. To learn more,
/// see the <see cref="ConstantsForAttribute"/>.
/// </typeparam>
/// <exception cref="ConstantsSerializerException">
/// Thrown if:
/// <list type="bullet">
/// <item>
/// <description>The tag type corresponds to no constant representation.</description>
/// </item>
/// <item>
/// <description>The tag type corresponds to more than one constant representation.</description>
/// </item>
/// <item>
/// <description>The tag type corresponds to a non-enum representation.</description>
/// </item>
/// <item>
/// <description>The tag type corresponds to a non-int enum representation.</description>
/// </item>
/// </list>
/// </exception>
/// <returns>
/// The unique int-backed enum constant type for the given tag.
/// </returns>
private Type GetConstants<T>()
{
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
Type? constantType = null;
foreach (Type enumConstantType in reflectionManager.FindTypesWithAttribute<ConstantsForAttribute>())
{
foreach (var constantsForAttribute in enumConstantType.GetCustomAttributes<ConstantsForAttribute>(true))
{
if (typeof(T) == constantsForAttribute.Tag)
{
if (constantType != null)
{
throw new NotSupportedException($"Multiple constant enums declared for the tag {constantsForAttribute.Tag}.");
}
if (!enumConstantType.IsEnum)
{
throw new ConstantSerializerException($"Could not create ConstantSerializer for non-enum {enumConstantType}.");
}
if (Enum.GetUnderlyingType(enumConstantType) != typeof(int))
{
throw new ConstantSerializerException($"Could not create ConstantSerializer for non-int enum {enumConstantType}.");
}
constantType = enumConstantType;
}
}
}
if (constantType == null)
{
throw new FlagSerializerException($"Found no type marked with a `ConstantsForAttribute(typeof({typeof(T)}))`.");
}
return constantType;
}
}
/// <summary>
/// <c>int</c> representation in terms of some enum flag type.
/// </summary>
public class WithFlagRepresentation : WithFormat<int>
{
private Type _flagType;
public Type FlagType => _flagType;
private YamlFlagSerializer _serializer;
public WithFlagRepresentation(Type flagType)
{
_flagType = flagType;
_serializer = new YamlFlagSerializer(_flagType, this);
}
public override YamlObjectSerializer.TypeSerializer GetYamlSerializer()
{
return _serializer;
}
public override Type Format => typeof(List<string>);
public override int FromCustomFormat(object obj)
{
var flagNames = (List<string>)obj;
var flags = 0;
foreach (var flagName in flagNames)
{
flags |= (int)Enum.Parse(_flagType, flagName);
}
return flags;
}
public override object ToCustomFormat([NotNull] int flags)
{
var flagNames = new List<string>();
// Assumption: a bitflag enum has a constructor for every bit value such that
// that bit is set in some other constructor i.e. if a 1 appears somewhere in
// the bits of one of the enum constructors, there is an enum constructor which
// is 1 just in that position.
//
// Otherwise, this code may throw an exception
var maxFlagValue = ((int[])Enum.GetValues(_flagType)).Max();
for (var bitIndex = 1; bitIndex <= maxFlagValue; bitIndex = bitIndex << 1)
{
if ((bitIndex & flags) == bitIndex)
{
var flagName = Enum.GetName(_flagType, bitIndex);
if (flagName == null)
{
throw new FlagSerializerException($"No bitflag corresponding to bit {bitIndex} in {_flagType}, but it was set anyways.");
}
flagNames.Add(flagName);
}
}
return flagNames;
}
}
internal sealed class FlagSerializerException : Exception
{
public FlagSerializerException(string message) : base(message)
{
}
}
/// <summary>
/// <c>int</c> representation in terms of some constant enum constructors.
/// </summary>
public class WithConstantRepresentation : WithFormat<int>
{
private Type _constantType;
public Type ConstantType => _constantType;
private YamlConstantSerializer _serializer;
public WithConstantRepresentation(Type constantType)
{
_constantType = constantType;
_serializer = new YamlConstantSerializer(_constantType, this);
}
public override YamlObjectSerializer.TypeSerializer GetYamlSerializer()
{
return _serializer;
}
public override Type Format => typeof(string);
public override int FromCustomFormat(object obj)
{
return (int)Enum.Parse(_constantType, (string)obj);
}
public int FromCustomFormatText(string text)
{
if (Enum.TryParse(_constantType, text, out var val))
{
return (int) val!;
}
return int.Parse(text, CultureInfo.InvariantCulture);
}
public override object ToCustomFormat([NotNull] int value)
{
var constantName = Enum.GetName(_constantType, value);
if (constantName == null)
{
throw new ConstantSerializerException($"No constant corresponding to value {value} in {_constantType}.");
}
return constantName;
}
}
internal sealed class ConstantSerializerException : Exception
{
public ConstantSerializerException(string message) : base(message)
{
}
}
}

View File

@@ -1,66 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Robust.Shared.Serialization
{
public sealed class DefaultValueSerializer : ObjectSerializer
{
public static DefaultValueSerializer Reader()
{
return new()
{
Reading = true,
};
}
private DefaultValueSerializer() {}
public override void DataField<T>(ref T value, string name, T defaultValue, WithFormat<T> withFormat, bool alwaysWrite = false)
{
if (Reading)
{
if (EqualityComparer<T>.Default.Equals(value, default))
value = defaultValue;
}
}
public override T ReadDataField<T>(string name, T defaultValue)
{
return defaultValue;
}
public override bool TryReadDataField<T>(string name, WithFormat<T> format, [MaybeNullWhen(false)] out T value)
{
value = default;
return false;
}
public override void DataField<TTarget, TSource>(
ref TTarget value,
string name,
TTarget defaultValue,
ReadConvertFunc<TTarget, TSource> ReadConvertFunc,
WriteConvertFunc<TTarget, TSource>? WriteConvertFunc = null,
bool alwaysWrite = false
)
{
if (Reading)
{
if (EqualityComparer<TTarget>.Default.Equals(value, default))
value = defaultValue;
}
}
public override void DataReadFunction<T>(string name, T defaultValue, ReadFunctionDelegate<T> func)
{
if (Reading)
{
func(defaultValue);
}
}
public override void DataWriteFunction<T>(string name, T defaultValue, WriteFunctionDelegate<T> func, bool alwaysWrite = false)
{
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
namespace Robust.Shared.Serialization
{
/// <summary>
/// Attribute for marking an enum type as being the bitflag representation for a field.
///
/// Some int values in the engine are bitflags, but the actual bitflag definitions
/// are reserved for the content layer. This means that serialization/deserialization
/// of those flags into readable YAML is impossible, unless the engine is notified
/// that a certain representation should be used. That's the role of this attribute.
///
/// NB: AllowMultiple is <c>true</c> - don't assume the same representation cannot
/// be reused between multiple fields.
/// </summary>
[AttributeUsage(AttributeTargets.Enum, AllowMultiple = true, Inherited = false)]
public class FlagsForAttribute : Attribute
{
private readonly Type _tag;
public Type Tag => _tag;
// NB: This is not generic because C# does not allow generic attributes
/// <summary>
/// An attribute with tag type <paramref name="tag"/>
/// </summary>
/// <param name="tag">
/// An arbitrary tag type used for coordinating between the data field and the
/// representation. Not actually used for serialization/deserialization.
/// </param>
public FlagsForAttribute(Type tag)
{
_tag = tag;
}
}
}

View File

@@ -1,32 +0,0 @@
namespace Robust.Shared.Serialization
{
/// <summary>
/// Provides information about custom serialization formats used by certain fields.
/// </summary>
public interface ICustomFormatManager
{
/// <summary>
/// Get a custom <c>int</c> format in terms of enum flags, chosen by a tag type.
/// </summary>
/// <typeparam name="T">
/// The tag type to select the representation with. To understand more about how
/// tag types are used, see the <see cref="FlagsForAttribute"/>.
/// </typeparam>
/// <returns>
/// A custom serialization format for int values, chosen by the tag type.
/// </returns>
public WithFormat<int> FlagFormat<T>();
/// <summary>
/// Get a custom <c>int</c> format in terms of enum constants, chosen by a tag type.
/// </summary>
/// <typeparam name="T">
/// The tag type to select the representation with. To understand more about how
/// tag types are used, see the <see cref="ConstantsForAttribute"/>.
/// </typeparam>
/// <returns>
/// A custom serialization format for int values, chosen by the tag type.
/// </returns>
public WithFormat<int> ConstantFormat<T>();
}
}

View File

@@ -1,23 +0,0 @@
using Robust.Shared.Analyzers;
namespace Robust.Shared.Serialization
{
/// <summary>
/// Interface for the "expose data" system, which is basically our method of handling data serialization.
/// </summary>
[RequiresExplicitImplementation]
public interface IExposeData
{
/// <summary>
/// Will get called to either make this object read data from or provide data to write to/from a serialization format.
/// This method should only rely on <paramref name="serializer" /> for reading data.
/// External data (i.e. current time) is unreliable as this method will get called to get a representation of this object from some data,
/// and may not be called again later.
/// </summary>
/// <param name="serializer">
/// A serializer that data can be read/written from using its various methods.
/// Tell it everything you want to preserve, even your dirtiest secrets.
/// </param>
void ExposeData(ObjectSerializer serializer);
}
}

View File

@@ -1,4 +1,4 @@
namespace Robust.Shared.Serialization
namespace Robust.Shared.Serialization
{
public interface ISelfSerialize
{

View File

@@ -0,0 +1,21 @@
using Robust.Shared.Analyzers;
namespace Robust.Shared.Serialization
{
/// <summary>
/// Provides a method that gets executed after deserialization is complete and a method that gets executed before serialization
/// </summary>
[RequiresExplicitImplementation]
public interface ISerializationHooks
{
/// <summary>
/// Gets executed after deserialization is complete
/// </summary>
void AfterDeserialization() {}
/// <summary>
/// Gets executed before serialization
/// </summary>
void BeforeSerialization() {}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace Robust.Shared.Serialization
{
public class InvalidMappingException : Exception
{
public InvalidMappingException(string msg) : base(msg)
{
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Runtime.CompilerServices;
namespace Robust.Shared.Serialization.Manager.Attributes
{
//todo paul find a way to constrain this to datafields only & make exclusive w/ neverpush
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class AlwaysPushInheritanceAttribute : Attribute { }
}

View File

@@ -0,0 +1,7 @@
using System;
namespace Robust.Shared.Serialization.Manager.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface, Inherited = false)]
public class CopyByRefAttribute : Attribute {}
}

View File

@@ -0,0 +1,12 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes
{
[MeansDataDefinition]
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
public class DataDefinition : Attribute
{
}
}

View File

@@ -0,0 +1,32 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[MeansImplicitAssignment]
public class DataFieldAttribute : Attribute
{
public readonly string Tag;
public readonly int Priority;
public readonly bool ReadOnly;
/// <summary>
/// Whether or not this field being mapped is required for the component to function.
/// This will not guarantee that the field is mapped when the program is run,
/// it is meant to be used as metadata information.
/// </summary>
public readonly bool Required;
public readonly bool ServerOnly;
public DataFieldAttribute([NotNull] string tag, bool readOnly = false, int priority = 1, bool required = false, bool serverOnly = false)
{
Tag = tag;
Priority = priority;
ReadOnly = readOnly;
Required = required;
ServerOnly = serverOnly;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[MeansImplicitAssignment]
public class DataFieldWithConstantAttribute : DataFieldAttribute
{
public readonly Type ConstantTag;
public DataFieldWithConstantAttribute([NotNull] string tag, Type constantTag, bool readOnly = false, int priority = 1, bool required = false, bool serverOnly = false) : base(tag, readOnly, priority, required, serverOnly)
{
ConstantTag = constantTag;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[MeansImplicitAssignment]
public class DataFieldWithFlagAttribute : DataFieldAttribute
{
public readonly Type FlagTag;
public DataFieldWithFlagAttribute([NotNull] string tag, Type flagTag, bool readOnly = false, int priority = 1, bool required = false, bool serverOnly = false) : base(tag, readOnly, priority, required, serverOnly)
{
FlagTag = flagTag;
}
}
}

View File

@@ -0,0 +1,7 @@
using System;
namespace Robust.Shared.Serialization.Manager.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class ImplicitDataDefinitionForInheritorsAttribute : Attribute { }
}

View File

@@ -0,0 +1,9 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes
{
[BaseTypeRequired(typeof(Attribute))]
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class MeansDataDefinition : Attribute { }
}

Some files were not shown because too many files have changed in this diff Show More