Compare commits

..

30 Commits

Author SHA1 Message Date
DrSmugleaf
1eb7393a60 Revert "pvsrange vec2 + eyezoom (#2676)" (#2703)
This reverts commit 582d8a5587.
2022-04-09 19:41:11 +02:00
Vera Aguilera Puerto
4cf88507c2 Version: 0.11.0.1 2022-04-09 13:21:38 +02:00
Vera Aguilera Puerto
3565d8b321 Fix synth state getting reset every MIDI player loop. 2022-04-09 13:21:09 +02:00
Vera Aguilera Puerto
7094c29b2e Version: 0.11.0.0 2022-04-08 16:07:42 +02:00
Vera Aguilera Puerto
63004b270f Cleans up and improves MIDI code significantly. (#2666) 2022-04-08 15:58:15 +02:00
metalgearsloth
6714a99b38 EntityLookup anchor flag test (#2699) 2022-04-08 13:36:24 +10:00
metalgearsloth
4c3b8df1e7 Fix anchor query (#2697) 2022-04-08 09:56:30 +10:00
metalgearsloth
4bb695121f Version: 0.10.0.0 2022-04-06 19:34:47 +10:00
metalgearsloth
09fd47c421 Don't store contained entities on entitylookup (#2662) 2022-04-06 19:31:34 +10:00
Paul
fd1e25c584 Version: 0.9.3.2 2022-04-05 18:51:03 +02:00
Paul Ritter
6bb66ae70e fixes loc (#2685) 2022-04-05 18:50:27 +02:00
Paul
cc82d6b1d9 Version: 0.9.3.1 2022-04-05 18:36:19 +02:00
Paul
956be749b6 fix prototype reload 2022-04-05 18:36:09 +02:00
Paul Ritter
6585a00608 readds expandpvsevent (#2684) 2022-04-05 19:47:14 +10:00
metalgearsloth
c0525f710f Sprite subscription shutdown (#2688) 2022-04-05 19:45:23 +10:00
Vera Aguilera Puerto
d3672807d2 Improves SpriteSystem.GetPrototypeIcon method significantly. (#2680) 2022-04-05 16:36:16 +10:00
ElectroJr
60f18d5f36 Version: 0.9.3.0 2022-04-05 18:06:43 +12:00
mirrorcult
e72d3de256 Local event for BUI opening (#2687) 2022-04-05 15:52:02 +10:00
Moony
ba9846b9c4 Fix vector2 CVars (#2686) 2022-04-05 15:03:10 +10:00
Paul
09586284dc Version: 0.9.2.0 2022-04-04 20:28:53 +02:00
Paul
a1ee4374b2 add a check for major version == 0 2022-04-04 20:28:26 +02:00
Paul Ritter
4de6f25f11 updates version-script to use the new version format (#2683) 2022-04-04 20:25:12 +02:00
Paul Ritter
582d8a5587 pvsrange vec2 + eyezoom (#2676)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2022-04-04 20:20:13 +02:00
Leon Friedrich
ec53b04f99 Add NotNullWhen attribute to TryComp (#2681) 2022-04-04 11:29:02 +02:00
metalgearsloth
950fc94408 Physicsmap tweaks (#2663) 2022-04-04 17:10:15 +10:00
Leon Friedrich
58d12e6e09 Remove Component.OnAdd() (#2660) 2022-04-04 16:11:47 +10:00
ElectroJr
94323005c4 Version: 0.9.1 2022-04-04 17:43:01 +12:00
metalgearsloth
4989842057 Remove IgnorePause (#2649) 2022-04-04 15:41:38 +10:00
Paul
80172636a8 version 0.9 - serv3 refactor 2022-04-03 02:00:41 +02:00
Paul Ritter
8491f7be24 New Serv3 api just dropped (#2605)
Co-authored-by: Paul Ritter <ritter.paul1@gmail.com>
2022-04-03 01:59:48 +02:00
163 changed files with 4066 additions and 3731 deletions

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<PropertyGroup><Version>0.8.86</Version></PropertyGroup>
<PropertyGroup><Version>0.11.0.1</Version></PropertyGroup>
</Project>

View File

@@ -1,7 +1,6 @@
using System.Globalization;
using Robust.Shared.IoC;
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.Markdown.Value;
@@ -19,10 +18,10 @@ namespace Robust.Benchmarks.Serialization
: new ErrorNode(node, $"Failed parsing int value: {node.Value}");
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public int Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, int _ = default)
{
return new DeserializedValue<int>(int.Parse(node.Value, CultureInfo.InvariantCulture));
return int.Parse(node.Value, CultureInfo.InvariantCulture);
}
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,

View File

@@ -28,7 +28,7 @@ namespace Robust.Benchmarks.Serialization.Copy
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
Seed = SerializationManager.Read<SeedDataDefinition>(seedMapping);
}
private const string String = "ABC";

View File

@@ -36,7 +36,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
Max: 10
PotencyDivisor: 10";
[DataField("id", required: true)] public string ID { get; set; } = default!;
[IdDataFieldAttribute] public string ID { get; set; } = default!;
#region Tracking
[DataField("name")] public string Name { get; set; } = string.Empty;

View File

@@ -2,7 +2,6 @@
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
@@ -42,32 +41,32 @@ namespace Robust.Benchmarks.Serialization.Read
private ValueDataNode FlagThirtyOne { get; } = new("ThirtyOne");
[Benchmark]
public string? ReadString()
public string ReadString()
{
return SerializationManager.ReadValue<string>(StringNode);
return SerializationManager.Read<string>(StringNode);
}
[Benchmark]
public int? ReadInteger()
public int ReadInteger()
{
return SerializationManager.ReadValue<int>(IntNode);
return SerializationManager.Read<int>(IntNode);
}
[Benchmark]
public DataDefinitionWithString? ReadDataDefinitionWithString()
public DataDefinitionWithString ReadDataDefinitionWithString()
{
return SerializationManager.ReadValue<DataDefinitionWithString>(StringDataDefNode);
return SerializationManager.Read<DataDefinitionWithString>(StringDataDefNode);
}
[Benchmark]
public SeedDataDefinition? ReadSeedDataDefinition()
public SeedDataDefinition ReadSeedDataDefinition()
{
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
return SerializationManager.Read<SeedDataDefinition>(SeedNode);
}
[Benchmark]
[BenchmarkCategory("flag")]
public DeserializationResult ReadFlagZero()
public object? ReadFlagZero()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),
@@ -77,7 +76,7 @@ namespace Robust.Benchmarks.Serialization.Read
[Benchmark]
[BenchmarkCategory("flag")]
public DeserializationResult ReadThirtyOne()
public object? ReadThirtyOne()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),
@@ -87,7 +86,7 @@ namespace Robust.Benchmarks.Serialization.Read
[Benchmark]
[BenchmarkCategory("customTypeSerializer")]
public DeserializationResult ReadIntegerCustomSerializer()
public object? ReadIntegerCustomSerializer()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),

View File

@@ -46,84 +46,84 @@ namespace Robust.Benchmarks.Serialization
[BenchmarkCategory("read")]
public string[]? ReadEmptyString()
{
return SerializationManager.ReadValue<string[]>(EmptyNode);
return SerializationManager.Read<string[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadOneString()
{
return SerializationManager.ReadValue<string[]>(OneIntNode);
return SerializationManager.Read<string[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadTenStrings()
{
return SerializationManager.ReadValue<string[]>(TenIntsNode);
return SerializationManager.Read<string[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadEmptyInt()
{
return SerializationManager.ReadValue<int[]>(EmptyNode);
return SerializationManager.Read<int[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadOneInt()
{
return SerializationManager.ReadValue<int[]>(OneIntNode);
return SerializationManager.Read<int[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadTenInts()
{
return SerializationManager.ReadValue<int[]>(TenIntsNode);
return SerializationManager.Read<int[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadEmptyStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(EmptyNode);
return SerializationManager.Read<DataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadOneStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(OneStringDefNode);
return SerializationManager.Read<DataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadTenStringDataDefs()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(TenStringDefsNode);
return SerializationManager.Read<DataDefinitionWithString[]>(TenStringDefsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadEmptySealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(EmptyNode);
return SerializationManager.Read<SealedDataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadOneSealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(OneStringDefNode);
return SerializationManager.Read<SealedDataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadTenSealedStringDataDefs()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(TenStringDefsNode);
return SerializationManager.Read<SealedDataDefinitionWithString[]>(TenStringDefsNode);
}
}
}

View File

@@ -28,7 +28,7 @@ namespace Robust.Benchmarks.Serialization.Write
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
Seed = SerializationManager.Read<SeedDataDefinition>(seedMapping);
}
private const string String = "ABC";

View File

@@ -0,0 +1,56 @@
using NFluidsynth;
using Robust.Shared.Audio.Midi;
namespace Robust.Client.Audio.Midi;
public interface IMidiManager
{
/// <summary>
/// If true, MIDI support is available.
/// </summary>
bool IsAvailable { get; }
/// <summary>
/// Volume, in db.
/// </summary>
float Volume { get; set; }
public int OcclusionCollisionMask { get; set; }
/// <summary>
/// This method tries to return a midi renderer ready to be used.
/// You only need to set the <see cref="IMidiRenderer.MidiProgram"/> afterwards.
/// </summary>
/// <remarks>
/// This method can fail if MIDI support is not available.
/// </remarks>
/// <returns>
/// <c>null</c> if MIDI support is not available.
/// </returns>
IMidiRenderer? GetNewRenderer(bool mono = true);
/// <summary>
/// Creates a <see cref="RobustMidiEvent"/> given a <see cref="MidiEvent"/> and a sequencer tick.
/// </summary>
RobustMidiEvent FromFluidEvent(MidiEvent midiEvent, uint tick);
/// <summary>
/// Creates a <see cref="SequencerEvent"/> given a <see cref="RobustMidiEvent"/>.
/// Be sure to dispose of the result after you've used it.
/// </summary>
SequencerEvent ToSequencerEvent(RobustMidiEvent midiEvent);
/// <summary>
/// Creates a <see cref="RobustMidiEvent"/> given a <see cref="SequencerEvent"/> and a sequencer tick.
/// </summary>
RobustMidiEvent FromSequencerEvent(SequencerEvent midiEvent, uint tick);
/// <summary>
/// Method called every frame.
/// Should be used to update positional audio.
/// </summary>
/// <param name="frameTime"></param>
void FrameUpdate(float frameTime);
void Shutdown();
}

View File

@@ -0,0 +1,174 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Audio.Midi;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Robust.Client.Audio.Midi;
public enum MidiRendererStatus : byte
{
None,
Input,
File,
}
public interface IMidiRenderer : IDisposable
{
/// <summary>
/// The buffered audio source of this renderer.
/// </summary>
internal IClydeBufferedAudioSource Source { get; }
/// <summary>
/// Whether this renderer has been disposed or not.
/// </summary>
bool Disposed { get; }
/// <summary>
/// This controls whether the midi file being played will loop or not.
/// </summary>
bool LoopMidi { get; set; }
/// <summary>
/// This increases all note on velocities to 127.
/// </summary>
bool VolumeBoost { get; set; }
/// <summary>
/// The midi program (instrument) the renderer is using.
/// </summary>
byte MidiProgram { get; set; }
/// <summary>
/// The instrument bank the renderer is using.
/// </summary>
byte MidiBank { get; set; }
/// <summary>
/// The soundfont currently selected by the renderer.
/// </summary>
uint MidiSoundfont { get; set; }
/// <summary>
/// The current status of the renderer.
/// "None" if the renderer isn't playing from input or a midi file.
/// "Input" if the renderer is playing from midi input.
/// "File" if the renderer is playing from a midi file.
/// </summary>
MidiRendererStatus Status { get; }
/// <summary>
/// Whether the sound will play in stereo or mono.
/// </summary>
bool Mono { get; set; }
/// <summary>
/// Whether to drop messages on the percussion channel.
/// </summary>
bool DisablePercussionChannel { get; set; }
/// <summary>
/// Whether to drop messages for program change events.
/// </summary>
bool DisableProgramChangeEvent { get; set; }
/// <summary>
/// Gets the total number of ticks possible for the MIDI player.
/// </summary>
int PlayerTotalTick { get; }
/// <summary>
/// Gets or sets (seeks) the current tick of the MIDI player.
/// </summary>
int PlayerTick { get; set; }
/// <summary>
/// Gets the current tick of the sequencer.
/// </summary>
uint SequencerTick { get; }
/// <summary>
/// Gets the Time Scale of the sequencer in ticks per second. Default is 1000 for 1 tick per millisecond.
/// </summary>
double SequencerTimeScale { get; }
/// <summary>
/// Start listening for midi input.
/// </summary>
bool OpenInput();
/// <summary>
/// Start playing a midi file.
/// </summary>
/// <param name="buffer">Bytes of the midi file</param>
bool OpenMidi(ReadOnlySpan<byte> buffer);
/// <summary>
/// Stops listening for midi input.
/// </summary>
bool CloseInput();
/// <summary>
/// Stops playing midi files.
/// </summary>
bool CloseMidi();
/// <summary>
/// Stops all notes being played currently.
/// </summary>
void StopAllNotes();
/// <summary>
/// Render and play MIDI to the audio source.
/// </summary>
internal void Render();
/// <summary>
/// Loads a new soundfont into the renderer.
/// </summary>
void LoadSoundfont(string filename, bool resetPresets = false);
/// <summary>
/// Invoked whenever a new midi event is registered.
/// </summary>
event Action<RobustMidiEvent> OnMidiEvent;
/// <summary>
/// Invoked when the midi player finishes playing a song.
/// </summary>
event Action OnMidiPlayerFinished;
/// <summary>
/// The entity whose position will be used for positional audio.
/// This is only used if <see cref="Mono"/> is set to True.
/// </summary>
EntityUid? TrackingEntity { get; set; }
/// <summary>
/// The position that will be used for positional audio.
/// This is only used if <see cref="Mono"/> is set to True
/// and <see cref="TrackingEntity"/> is null.
/// </summary>
EntityCoordinates? TrackingCoordinates { get; set; }
/// <summary>
/// Send a midi event for the renderer to play.
/// </summary>
/// <param name="midiEvent">The midi event to be played</param>
void SendMidiEvent(RobustMidiEvent midiEvent);
/// <summary>
/// Schedule a MIDI event to be played at a later time.
/// </summary>
/// <param name="midiEvent">the midi event in question</param>
/// <param name="time"></param>
/// <param name="absolute"></param>
void ScheduleMidiEvent(RobustMidiEvent midiEvent, uint time, bool absolute);
/// <summary>
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.
/// </summary>
internal void InternalDispose();
}

View File

@@ -0,0 +1,155 @@
using NFluidsynth;
using Robust.Shared.Audio.Midi;
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiManager
{
public RobustMidiEvent FromFluidEvent(MidiEvent midiEvent, uint tick)
{
var status = RobustMidiEvent.MakeStatus((byte) midiEvent.Channel, (byte) midiEvent.Type);
// Control is always the first data byte. Value is always the second data byte. Fluidsynth's API ain't great.
var data1 = (byte) midiEvent.Control;
var data2 = (byte) midiEvent.Value;
// PitchBend is handled specially.
if (midiEvent.Type == (int) RobustMidiCommand.PitchBend)
{
// We pack pitch into both data values.
var pitch = (ushort) midiEvent.Pitch;
data1 = (byte) pitch;
data2 = (byte) (pitch >> 8);
}
return new RobustMidiEvent(status, data1, data2, tick);
}
public SequencerEvent ToSequencerEvent(RobustMidiEvent midiEvent)
{
var sequencerEvent = new SequencerEvent();
switch (midiEvent.MidiCommand)
{
case RobustMidiCommand.NoteOff:
sequencerEvent.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
case RobustMidiCommand.NoteOn:
sequencerEvent.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
case RobustMidiCommand.AfterTouch:
sequencerEvent.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
break;
case RobustMidiCommand.ControlChange:
sequencerEvent.ControlChange(midiEvent.Channel, midiEvent.Control, midiEvent.Value);
break;
case RobustMidiCommand.ProgramChange:
sequencerEvent.ProgramChange(midiEvent.Channel, midiEvent.Program);
break;
case RobustMidiCommand.ChannelPressure:
sequencerEvent.ChannelPressure(midiEvent.Channel, midiEvent.Value);
break;
case RobustMidiCommand.PitchBend:
sequencerEvent.PitchBend(midiEvent.Channel, midiEvent.Pitch);
break;
case RobustMidiCommand.SystemMessage:
switch (midiEvent.Control)
{
case 0x0 when midiEvent.Status == 0xFF:
sequencerEvent.SystemReset();
break;
case 0x0B:
sequencerEvent.AllNotesOff(midiEvent.Channel);
break;
default:
_midiSawmill.Warning($"Tried to convert unsupported event to sequencer event:\n{midiEvent}");
break;
}
break;
default:
_midiSawmill.Warning($"Tried to convert unsupported event to sequencer event:\n{midiEvent}");
break;
}
return sequencerEvent;
}
public RobustMidiEvent FromSequencerEvent(SequencerEvent midiEvent, uint tick)
{
byte channel = (byte) midiEvent.Channel;
RobustMidiCommand command = 0x0;
byte data1 = 0;
byte data2 = 0;
switch (midiEvent.Type)
{
case FluidSequencerEventType.NoteOn:
command = RobustMidiCommand.NoteOn;
data1 = (byte) midiEvent.Key;
data2 = (byte) midiEvent.Velocity;
break;
case FluidSequencerEventType.NoteOff:
command = RobustMidiCommand.NoteOff;
data1 = (byte) midiEvent.Key;
break;
case FluidSequencerEventType.PitchBend:
command = RobustMidiCommand.PitchBend;
// We pack pitch into both data values
var pitch = (ushort) midiEvent.Pitch;
data1 = (byte) pitch;
data2 = (byte) (pitch >> 8);
break;
case FluidSequencerEventType.ProgramChange:
command = RobustMidiCommand.ProgramChange;
data1 = (byte) midiEvent.Program;
break;
case FluidSequencerEventType.KeyPressure:
command = RobustMidiCommand.AfterTouch;
data1 = (byte) midiEvent.Key;
data2 = (byte) midiEvent.Value;
break;
case FluidSequencerEventType.ControlChange:
command = RobustMidiCommand.ControlChange;
data1 = (byte) midiEvent.Control;
data2 = (byte) midiEvent.Value;
break;
case FluidSequencerEventType.ChannelPressure:
command = RobustMidiCommand.ChannelPressure;
data1 = (byte) midiEvent.Value;
break;
case FluidSequencerEventType.AllNotesOff:
command = RobustMidiCommand.SystemMessage;
data1 = 0x0B;
break;
case FluidSequencerEventType.SystemReset:
command = RobustMidiCommand.SystemMessage;
channel = 0x0F;
break;
default:
_midiSawmill.Error($"Unsupported Sequencer Event: {tick:D8}: {SequencerEventToString(midiEvent)}");
break;
}
return new RobustMidiEvent(RobustMidiEvent.MakeStatus(channel, (byte)command), data1, data2, tick);
}
}

View File

@@ -7,6 +7,7 @@ using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
@@ -15,479 +16,466 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Logger = Robust.Shared.Log.Logger;
namespace Robust.Client.Audio.Midi
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiManager : IMidiManager
{
public interface IMidiManager
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ILogManager _logger = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
{
/// <summary>
/// This method tries to return a midi renderer ready to be used.
/// You only need to set the <see cref="IMidiRenderer.MidiProgram"/> afterwards.
/// </summary>
/// <remarks>
/// This method can fail if MIDI support is not available.
/// </remarks>
/// <returns>
/// <c>null</c> if MIDI support is not available.
/// </returns>
IMidiRenderer? GetNewRenderer();
get
{
InitializeFluidsynth();
/// <summary>
/// Method called every frame.
/// Should be used to update positional audio.
/// </summary>
/// <param name="frameTime"></param>
void FrameUpdate(float frameTime);
/// <summary>
/// Volume, in db.
/// </summary>
float Volume { get; set; }
/// <summary>
/// If true, MIDI support is available.
/// </summary>
bool IsAvailable { get; }
public int OcclusionCollisionMask { get; set; }
void Shutdown();
return FluidsynthInitialized;
}
}
internal sealed class MidiManager : IMidiManager
[ViewVariables]
private readonly List<IMidiRenderer> _renderers = new();
private bool _alive = true;
private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
private bool _volumeDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
get => _volume;
set
{
get
{
InitializeFluidsynth();
if (MathHelper.CloseToPercent(_volume, value))
return;
return FluidsynthInitialized;
}
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volumeDirty = true;
}
}
private static readonly string[] LinuxSoundfonts =
{
"/usr/share/soundfonts/default.sf2",
"/usr/share/soundfonts/FluidR3_GM.sf2",
"/usr/share/soundfonts/freepats-general-midi.sf2",
"/usr/share/sounds/sf2/default.sf2",
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
"/usr/share/sounds/sf2/TimGM6mb.sf2",
};
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
private const string FallbackSoundfont = "/Midi/fallback.sf2";
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
private bool FluidsynthInitialized;
private bool _failedInitialize;
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _sawmill = default!;
[ViewVariables(VVAccess.ReadWrite)]
public int OcclusionCollisionMask { get; set; }
private void InitializeFluidsynth()
{
if (FluidsynthInitialized || _failedInitialize) return;
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
_volumeDirty = true;
}, true);
_midiSawmill = _logger.GetSawmill("midi");
_midiSawmill.Level = LogLevel.Info;
_sawmill = _logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
try
{
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
_settings = new Settings();
_settings["synth.sample-rate"].DoubleValue = 44100;
_settings["player.timing-source"].StringValue = "sample";
_settings["synth.lock-memory"].IntValue = 0;
_settings["synth.threadsafe-api"].IntValue = 1;
_settings["synth.gain"].DoubleValue = 1.0d;
_settings["synth.polyphony"].IntValue = 1024;
_settings["synth.cpu-cores"].IntValue = 2;
_settings["synth.midi-channels"].IntValue = 16;
_settings["synth.overflow.age"].DoubleValue = 3000;
_settings["audio.driver"].StringValue = "file";
_settings["audio.periods"].IntValue = 8;
_settings["audio.period-size"].IntValue = 4096;
_settings["midi.autoconnect"].IntValue = 1;
_settings["player.reset-synth"].IntValue = 0;
_settings["synth.midi-bank-select"].StringValue = "gm";
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
}
catch (Exception e)
{
_midiSawmill.Warning(
"Failed to initialize fluidsynth due to exception, disabling MIDI support:\n{0}", e);
_failedInitialize = true;
return;
}
[ViewVariables]
private readonly List<IMidiRenderer> _renderers = new();
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
private bool _alive = true;
private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
private bool _volumeDirty = true;
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
FluidsynthInitialized = true;
}
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
{
get => _volume;
set
{
if (MathHelper.CloseToPercent(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volumeDirty = true;
}
}
private static readonly string[] LinuxSoundfonts =
{
"/usr/share/soundfonts/default.sf2",
"/usr/share/soundfonts/FluidR3_GM.sf2",
"/usr/share/soundfonts/freepats-general-midi.sf2",
"/usr/share/sounds/sf2/default.sf2",
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
"/usr/share/sounds/sf2/TimGM6mb.sf2",
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
{
var rLevel = level switch {
NFluidsynth.Logger.LogLevel.Panic => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Error => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Warning => LogLevel.Warning,
NFluidsynth.Logger.LogLevel.Information => LogLevel.Info,
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
_ => LogLevel.Debug
};
_sawmill.Log(rLevel, message);
}
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
private const string FallbackSoundfont = "/Midi/fallback.sf2";
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
private bool FluidsynthInitialized;
private bool _failedInitialize;
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _sawmill = default!;
[ViewVariables(VVAccess.ReadWrite)]
public int OcclusionCollisionMask { get; set; }
private void InitializeFluidsynth()
public IMidiRenderer? GetNewRenderer(bool mono = true)
{
if (!FluidsynthInitialized)
{
if (FluidsynthInitialized || _failedInitialize) return;
InitializeFluidsynth();
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
if (!FluidsynthInitialized) // init failed
{
_volume = value;
_volumeDirty = true;
}, true);
_midiSawmill = Logger.GetSawmill("midi");
_sawmill = Logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
try
{
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
_settings = new Settings();
_settings["synth.sample-rate"].DoubleValue = 44100;
_settings["player.timing-source"].StringValue = "sample";
_settings["synth.lock-memory"].IntValue = 0;
_settings["synth.threadsafe-api"].IntValue = 1;
_settings["synth.gain"].DoubleValue = 1.0d;
_settings["synth.polyphony"].IntValue = 1024;
_settings["synth.cpu-cores"].IntValue = 2;
_settings["synth.overflow.age"].DoubleValue = 3000;
_settings["audio.driver"].StringValue = "file";
_settings["audio.periods"].IntValue = 8;
_settings["audio.period-size"].IntValue = 4096;
_settings["midi.autoconnect"].IntValue = 1;
_settings["player.reset-synth"].IntValue = 0;
_settings["synth.midi-bank-select"].StringValue = "gm";
}
catch (Exception e)
{
Logger.WarningS("midi",
"Failed to initialize fluidsynth due to exception, disabling MIDI support:\n{0}", e);
_failedInitialize = true;
return;
}
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
FluidsynthInitialized = true;
}
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
{
var rLevel = level switch {
NFluidsynth.Logger.LogLevel.Panic => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Error => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Warning => LogLevel.Warning,
NFluidsynth.Logger.LogLevel.Information => LogLevel.Info,
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
_ => LogLevel.Debug
};
_sawmill.Log(rLevel, message);
}
public IMidiRenderer? GetNewRenderer()
{
if (!FluidsynthInitialized)
{
InitializeFluidsynth();
if (!FluidsynthInitialized) // init failed
{
return null;
}
}
var soundfontLoader = SoundFontLoader.NewDefaultSoundFontLoader(_settings);
// Just making double sure these don't get GC'd.
// They shouldn't, MidiRenderer keeps a ref, but making sure...
var handle = GCHandle.Alloc(soundfontLoader);
try
{
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
var renderer = new MidiRenderer(_settings!, soundfontLoader);
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
{
if (file.Extension != "sf2" && file.Extension != "dls") continue;
renderer.LoadSoundfont(file.ToString());
}
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (OperatingSystem.IsLinux())
{
foreach (var filepath in LinuxSoundfonts)
{
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath)) continue;
try
{
renderer.LoadSoundfont(filepath, true);
}
catch (Exception)
{
continue;
}
break;
}
}
else if (OperatingSystem.IsMacOS())
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
renderer.LoadSoundfont(OsxSoundfont, true);
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
renderer.LoadSoundfont(WindowsSoundfont, true);
}
renderer.Source.SetVolume(Volume);
lock (_renderers)
{
_renderers.Add(renderer);
}
return renderer;
}
finally
{
handle.Free();
return null;
}
}
public void FrameUpdate(float frameTime)
var soundfontLoader = SoundFontLoader.NewDefaultSoundFontLoader(_settings);
// Just making double sure these don't get GC'd.
// They shouldn't, MidiRenderer keeps a ref, but making sure...
var handle = GCHandle.Alloc(soundfontLoader);
try
{
if (!FluidsynthInitialized)
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill);
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
{
return;
if (file.Extension != "sf2" && file.Extension != "dls") continue;
renderer.LoadSoundfont(file.ToString());
}
// Update positions of streams every frame.
foreach (var renderer in _renderers)
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (OperatingSystem.IsLinux())
{
if (renderer.Disposed)
continue;
if(_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
foreach (var filepath in LinuxSoundfonts)
{
renderer.Source.SetGlobal();
continue;
}
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath)) continue;
MapCoordinates? mapPos = null;
var trackingEntity = renderer.TrackingEntity != null && !_entityManager.Deleted(renderer.TrackingEntity);
if (trackingEntity)
{
renderer.TrackingCoordinates = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity!.Value).Coordinates;
}
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
if (mapPos != null && mapPos.Value.MapId == _eyeManager.CurrentMap)
{
var pos = mapPos.Value;
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
try
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
renderer.LoadSoundfont(filepath, true);
}
catch (Exception)
{
continue;
}
renderer.Source.SetOcclusion(occlusion);
if (!renderer.Source.SetPosition(pos.Position))
{
return;
}
if (trackingEntity)
{
renderer.Source.SetVelocity(renderer.TrackingEntity!.Value.GlobalLinearVelocity());
}
}
else
{
renderer.Source.SetOcclusion(float.MaxValue);
break;
}
}
_volumeDirty = false;
}
/// <summary>
/// Main method for the thread rendering the midi audio.
/// </summary>
private void ThreadUpdate()
{
while (_alive)
else if (OperatingSystem.IsMacOS())
{
lock (_renderers)
{
for (var i = 0; i < _renderers.Count; i++)
{
var renderer = _renderers[i];
if (!renderer.Disposed)
renderer.Render();
else
{
renderer.InternalDispose();
_renderers.Remove(renderer);
}
}
}
Thread.Sleep(1);
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
renderer.LoadSoundfont(OsxSoundfont, true);
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
renderer.LoadSoundfont(WindowsSoundfont, true);
}
}
public void Shutdown()
{
_alive = false;
_midiThread?.Join();
_settings?.Dispose();
renderer.Source.SetVolume(Volume);
lock (_renderers)
{
foreach (var renderer in _renderers)
{
renderer?.Dispose();
}
_renderers.Add(renderer);
}
return renderer;
}
finally
{
handle.Free();
}
}
public void FrameUpdate(float frameTime)
{
if (!FluidsynthInitialized)
{
return;
}
// Update positions of streams every frame.
foreach (var renderer in _renderers)
{
if (renderer.Disposed)
continue;
if(_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
{
renderer.Source.SetGlobal();
continue;
}
if (FluidsynthInitialized && !_failedInitialize)
MapCoordinates? mapPos = null;
var trackingEntity = renderer.TrackingEntity != null && !_entityManager.Deleted(renderer.TrackingEntity);
if (trackingEntity)
{
NFluidsynth.Logger.SetLoggerMethod(null);
renderer.TrackingCoordinates = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity!.Value).Coordinates;
}
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
if (mapPos != null && mapPos.Value.MapId == _eyeManager.CurrentMap)
{
var pos = mapPos.Value;
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
if (!renderer.Source.SetPosition(pos.Position))
{
return;
}
if (trackingEntity)
{
renderer.Source.SetVelocity(renderer.TrackingEntity!.Value.GlobalLinearVelocity());
}
}
else
{
renderer.Source.SetOcclusion(float.MaxValue);
}
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
_volumeDirty = false;
}
/// <summary>
/// Main method for the thread rendering the midi audio.
/// </summary>
private void ThreadUpdate()
{
while (_alive)
{
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public override IntPtr Open(string filename)
lock (_renderers)
{
if (string.IsNullOrEmpty(filename))
for (var i = 0; i < _renderers.Count; i++)
{
var renderer = _renderers[i];
if (!renderer.Disposed)
renderer.Render();
else
{
renderer.InternalDispose();
_renderers.Remove(renderer);
}
}
}
Thread.Sleep(1);
}
}
public void Shutdown()
{
_alive = false;
_midiThread?.Join();
_settings?.Dispose();
lock (_renderers)
{
foreach (var renderer in _renderers)
{
renderer?.Dispose();
}
}
if (FluidsynthInitialized && !_failedInitialize)
{
NFluidsynth.Logger.SetLoggerMethod(null);
}
}
/// <summary>
/// Internal method to get a human-readable representation of a <see cref="SequencerEvent"/>.
/// </summary>
internal static string SequencerEventToString(SequencerEvent midiEvent)
{
// ReSharper disable once UseStringInterpolation
return string.Format(
"{0} chan:{1:D2} key:{2:D5} bank:{3:D2} ctrl:{4:D5} dur:{5:D5} pitch:{6:D5} prog:{7:D3} val:{8:D5} vel:{9:D5}",
midiEvent.Type.ToString().PadLeft(22),
midiEvent.Channel,
midiEvent.Key,
midiEvent.Bank,
midiEvent.Control,
midiEvent.Duration,
midiEvent.Pitch,
midiEvent.Program,
midiEvent.Value,
midiEvent.Velocity);
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
{
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public override IntPtr Open(string filename)
{
if (string.IsNullOrEmpty(filename))
{
return IntPtr.Zero;
}
Stream? stream;
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resourcePath = new ResourcePath(filename);
if (resourcePath.IsRooted && resourceCache.ContentFileExists(filename))
{
if (!resourceCache.TryContentFileRead(filename, out stream))
return IntPtr.Zero;
}
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
}
else
{
return IntPtr.Zero;
}
Stream? stream;
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resourcePath = new ResourcePath(filename);
var id = _nextStreamId++;
if (resourcePath.IsRooted && resourceCache.ContentFileExists(filename))
_openStreams.Add(id, stream);
return (IntPtr) id;
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
if (!resourceCache.TryContentFileRead(filename, out stream))
return IntPtr.Zero;
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
stream.ReadExact(buffer);
buffer.CopyTo(span);
}
else
{
return IntPtr.Zero;
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
var id = _nextStreamId++;
_openStreams.Add(id, stream);
return (IntPtr) id;
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
catch (EndOfStreamException)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
stream.ReadExact(buffer);
buffer.CopyTo(span);
}
else
{
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
}
catch (EndOfStreamException)
{
return -1;
}
return 0;
return -1;
}
public override int Seek(IntPtr sfHandle, int offset, SeekOrigin origin)
{
var stream = _openStreams[(int) sfHandle];
return 0;
}
stream.Seek(offset, origin);
public override int Seek(IntPtr sfHandle, int offset, SeekOrigin origin)
{
var stream = _openStreams[(int) sfHandle];
return 0;
}
stream.Seek(offset, origin);
public override int Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
return 0;
}
return (int) stream.Position;
}
public override int Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
return (int) stream.Position;
}
stream.Dispose();
return 0;
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
return 0;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -132,7 +132,7 @@ namespace Robust.Client
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(Options.PrototypeDirectory);
_prototypeManager.Resync();
_prototypeManager.ResolveResults();
_entityManager.Initialize();
_mapManager.Initialize();
_gameStateManager.Initialize();

View File

@@ -1,3 +1,4 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
@@ -32,11 +33,13 @@ namespace Robust.Client.GameObjects
public const string LogCategory = "go.comp.icon";
const string SerializationCache = "icon";
[Obsolete("Use SpriteSystem instead.")]
private static IRsiStateLike TextureForConfig(IconComponent compData, IResourceCache resourceCache)
{
return compData.Icon?.Default ?? resourceCache.GetFallback<TextureResource>().Texture;
}
[Obsolete("Use SpriteSystem instead.")]
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
if (!prototype.Components.TryGetValue("Icon", out var compData))

View File

@@ -1520,6 +1520,7 @@ namespace Robust.Client.GameObjects
}
}
[Obsolete("Use SpriteSystem instead.")]
internal static RSI.State GetFallbackState(IResourceCache cache)
{
var rsi = cache.GetResource<RSIResource>("/Textures/error.rsi").RSI;

View File

@@ -75,6 +75,10 @@ namespace Robust.Client.GameObjects
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey});
boundInterface.Open();
_openInterfaces[wrapped.UiKey] = boundInterface;
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
_entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIOpenedEvent(wrapped.UiKey, Owner, playerSession));
}
internal void Close(object uiKey, bool remoteCall)

View File

@@ -0,0 +1,105 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
using Color = Robust.Shared.Maths.Color;
namespace Robust.Client.GameObjects;
public sealed class DebugEntityLookupCommand : IConsoleCommand
{
public string Command => "togglelookup";
public string Description => "Shows / hides entitylookup bounds via an overlay";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugEntityLookupSystem>().Enabled ^= true;
}
}
public sealed class DebugEntityLookupSystem : EntitySystem
{
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
IoCManager.Resolve<IOverlayManager>().AddOverlay(
new EntityLookupOverlay(
EntityManager,
Get<EntityLookupSystem>()));
}
else
{
IoCManager.Resolve<IOverlayManager>().RemoveOverlay<EntityLookupOverlay>();
}
}
}
private bool _enabled;
}
public sealed class EntityLookupOverlay : Overlay
{
private IEntityManager _entityManager = default!;
private EntityLookupSystem _lookup = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup)
{
_entityManager = entManager;
_lookup = lookup;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var lookup in _lookup.FindLookupsIntersecting(args.MapId, args.WorldBounds))
{
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var (_, rotation, matrix, invMatrix) = lookupXform.GetWorldPositionRotationMatrixWithInv();
worldHandle.SetTransform(matrix);
var lookupAABB = invMatrix.TransformBox(args.WorldBounds);
var ents = new List<EntityUid>();
// Gonna allocate a lot but debug overlay sooo
lookup.Tree._b2Tree.FastQuery(ref lookupAABB, (ref EntityUid data) =>
{
ents.Add(data);
});
foreach (var ent in ents)
{
if (_entityManager.Deleted(ent)) continue;
var xform = xformQuery.GetComponent(ent);
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
var (entPos, entRot) = xform.GetWorldPositionRotation();
var lookupPos = invMatrix.Transform(entPos);
var lookupRot = entRot - rotation;
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, xformQuery);
worldHandle.DrawRect(aabb, Color.Blue.WithAlpha(0.2f));
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -17,13 +18,13 @@ public sealed partial class SpriteSystem
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Pure]
private readonly Dictionary<string, IRsiStateLike> _cachedPrototypeIcons = new();
public Texture Frame0(SpriteSpecifier specifier)
{
return RsiStateLike(specifier).Default;
}
[Pure]
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
{
switch (specifier)
@@ -35,38 +36,71 @@ public sealed partial class SpriteSystem
return GetState(rsi);
case SpriteSpecifier.EntityPrototype prototypeIcon:
if (!_proto.TryIndex<EntityPrototype>(prototypeIcon.EntityPrototypeId, out var prototype))
{
Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId);
return SpriteComponent.GetFallbackState(_resourceCache);
}
return SpriteComponent.GetPrototypeIcon(prototype, _resourceCache);
return GetPrototypeIcon(prototypeIcon.EntityPrototypeId);
default:
throw new NotSupportedException();
}
}
[Pure]
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
/// <summary>
/// Returns an icon for a given <see cref="EntityPrototype"/> ID, or a fallback in case of an error.
/// This method caches the result based on the prototype identifier.
/// </summary>
public IRsiStateLike GetPrototypeIcon(string prototype)
{
var icon = IconComponent.GetPrototypeIcon(prototype, _resourceCache);
if (icon != null) return icon;
// Check if this prototype has been cached before, and if so return the result.
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
return cachedResult;
if (!prototype.Components.ContainsKey("Sprite"))
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
{
return SpriteComponent.GetFallbackState(resourceCache);
// The specified prototype doesn't exist, return the fallback "error" sprite.
Logger.Error("Failed to load PrototypeIcon {0}", prototype);
return GetFallbackState();
}
// Generate the icon and cache it in case it's ever needed again.
var result = GetPrototypeIcon(entityPrototype);
_cachedPrototypeIcons[prototype] = result;
return result;
}
/// <summary>
/// Returns an icon for a given <see cref="EntityPrototype"/> ID, or a fallback in case of an error.
/// This method does NOT cache the result.
/// </summary>
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype)
{
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
if (prototype.Components.TryGetValue("Icon", out var compData)
&& compData is IconComponent {Icon: {} icon})
{
return icon.Default;
}
// If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback.
if (!prototype.Components.ContainsKey("Sprite"))
{
return GetFallbackState();
}
// Finally, we use spawn a dummy entity to get its icon.
var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
var result = spriteComponent.Icon ?? SpriteComponent.GetFallbackState(resourceCache);
var result = spriteComponent.Icon ?? GetFallbackState();
Del(dummy);
return result;
}
[Pure]
public RSI.State GetFallbackState()
{
return _resourceCache.GetFallback<RSIResource>().RSI["error"];
}
[Pure]
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
{
@@ -79,6 +113,20 @@ public sealed partial class SpriteSystem
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
return SpriteComponent.GetFallbackState(_resourceCache);
return GetFallbackState();
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs protoReloaded)
{
// Check if any EntityPrototype has been changed.
if (!protoReloaded.ByType.TryGetValue(typeof(EntityPrototype), out var changedSet))
return;
// Remove all changed prototypes from the cache, if they're there.
foreach (var (prototype, _) in changedSet.Modified)
{
// Let's be lazy and not regenerate them until something needs them again.
_cachedPrototypeIcons.Remove(prototype);
}
}
}

View File

@@ -23,9 +23,16 @@ namespace Robust.Client.GameObjects
{
base.Initialize();
_proto.PrototypesReloaded += OnPrototypesReloaded;
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
}
public override void Shutdown()
{
base.Shutdown();
_proto.PrototypesReloaded -= OnPrototypesReloaded;
}
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
{
_inertUpdateQueue.Enqueue(ev.Sprite);

View File

@@ -144,7 +144,7 @@ namespace Robust.Client.Graphics.Clyde
var worldAABB = CalcWorldAABB(vp);
var worldBounds = CalcWorldBounds(vp);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, worldAABB, worldBounds);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, vp.Eye!.Position.MapId, worldAABB, worldBounds);
foreach (var overlay in list)
{

View File

@@ -98,7 +98,7 @@ namespace Robust.Client.Graphics
handle = renderHandle.DrawingHandleWorld;
}
var args = new OverlayDrawArgs(currentSpace, vpControl, vp, handle, screenBox, worldBox, worldBounds);
var args = new OverlayDrawArgs(currentSpace, vpControl, vp, handle, screenBox, vp.Eye!.Position.MapId, worldBox, worldBounds);
Draw(args);
}

View File

@@ -1,6 +1,7 @@
using JetBrains.Annotations;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -38,6 +39,11 @@ namespace Robust.Client.Graphics
/// </summary>
public readonly UIBox2i ViewportBounds;
/// <summary>
/// <see cref="MapId"/> of the viewport's eye.
/// </summary>
public readonly MapId MapId;
/// <summary>
/// AABB enclosing the area visible in the viewport.
/// </summary>
@@ -57,6 +63,7 @@ namespace Robust.Client.Graphics
IClydeViewport viewport,
DrawingHandleBase drawingHandle,
in UIBox2i viewportBounds,
in MapId mapId,
in Box2 worldAabb,
in Box2Rotated worldBounds)
{
@@ -65,6 +72,7 @@ namespace Robust.Client.Graphics
Viewport = viewport;
DrawingHandle = drawingHandle;
ViewportBounds = viewportBounds;
MapId = mapId;
WorldAABB = worldAabb;
WorldBounds = worldBounds;
}

View File

@@ -19,7 +19,7 @@ namespace Robust.Client.Graphics
[Dependency] private readonly IResourceCache _resourceCache = default!;
[ViewVariables]
[DataField("id", required: true)]
[IdDataFieldAttribute]
public string ID { get; } = default!;
private ShaderKind Kind;

View File

@@ -497,7 +497,7 @@ namespace Robust.Client.Input
if (robustMapping.TryGet("binds", out var BaseKeyRegsNode))
{
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
var baseKeyRegs = serializationManager.Read<KeyBindingRegistration[]>(BaseKeyRegsNode);
foreach (var reg in baseKeyRegs)
{
@@ -526,7 +526,7 @@ namespace Robust.Client.Input
if (userData && robustMapping.TryGet("leaveEmpty", out var node))
{
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);
var leaveEmpty = serializationManager.Read<BoundKeyFunction[]>(node);
if (leaveEmpty.Length > 0)
{

View File

@@ -22,6 +22,8 @@ namespace Robust.Client.ResourceManagement
{
private static readonly float[] OneArray = {1};
public override ResourcePath? Fallback => new("/Textures/error.rsi");
private static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{

View File

@@ -1,10 +1,10 @@
using System;
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.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -16,26 +16,40 @@ namespace Robust.Client.Serialization
[TypeSerializer]
public sealed class AppearanceVisualizerSerializer : ITypeSerializer<AppearanceVisualizer, MappingDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
public AppearanceVisualizer Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
ISerializationContext? context = null, AppearanceVisualizer? value = null)
{
Type? type = null;
if (!node.TryGet("type", out var typeNode))
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
{
if (value == null)
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!");
type = value.GetType();
}
else
{
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!");
type = IoCManager.Resolve<IReflectionManager>()
.YamlTypeTagLookup(typeof(AppearanceVisualizer), typeValueDataNode.Value);
if (type == null)
throw new InvalidMappingException(
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
if(value != null && !type.IsInstanceOfType(value))
{
throw new InvalidMappingException(
$"Specified Type does not match type of provided Value for AppearanceVisualizer! (TypeOfValue: {value.GetType()}, ProvidedValue: {type})");
}
}
var newNode = node.Copy();
newNode.Remove("type");
return serializationManager.Read(type, newNode, context, skipHook);
return (AppearanceVisualizer) serializationManager.Read(type, newNode, context, skipHook, value)!;
}
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,

View File

@@ -228,7 +228,7 @@ namespace Robust.Client.UserInterface.CustomControls
foreach (var prototype in prototypeManager.EnumeratePrototypes<EntityPrototype>())
{
if (prototype.Abstract)
if (prototype.NoSpawn || prototype.Abstract)
{
continue;
}

View File

@@ -338,7 +338,7 @@ namespace Robust.Server
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadDirectory(Options.PrototypeDirectory);
prototypeManager.Resync();
prototypeManager.ResolveResults();
_consoleHost.Initialize();
_entityManager.Startup();

View File

@@ -312,15 +312,20 @@ namespace Robust.Server.Bql
{
var radius = (float)(double)arguments[0];
var entityLookup = EntitySystem.Get<EntityLookupSystem>();
var xformQuery = IoCManager.Resolve<IEntityManager>().GetEntityQuery<TransformComponent>();
var distinct = new HashSet<EntityUid>();
foreach (var uid in input)
{
foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates,
radius))
{
if (!distinct.Add(near)) continue;
yield return near;
}
}
// TODO: Make this a foreach and reduce LINQ chain because it'll allocate a LOT
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
return input.Where(entityManager.HasComponent<TransformComponent>)
.SelectMany(e =>
entityLookup.GetEntitiesInRange(entityManager.GetComponent<TransformComponent>(e).Coordinates,
radius))
.Select(x => x) // Sloth's fault.
.Distinct();
}
}

View File

@@ -184,6 +184,7 @@ namespace Robust.Server.GameObjects
}
_subscribedSessions.Add(session);
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner.Owner, new BoundUIOpenedEvent(UiKey, Owner.Owner, session));
SendMessage(new OpenBoundInterfaceMessage(), session);
if (_lastState != null)
{
@@ -236,11 +237,11 @@ namespace Robust.Server.GameObjects
public void CloseShared(IPlayerSession session)
{
var owner = Owner.Owner;
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(owner, new BoundUIClosedEvent(UiKey, owner, session));
OnClosed?.Invoke(session);
_subscribedSessions.Remove(session);
_playerStateOverrides.Remove(session);
session.PlayerStatusChanged -= OnSessionOnPlayerStatusChanged;
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(owner, new BoundUIClosedEvent(UiKey, owner, session));
if (_subscribedSessions.Count == 0)
{

View File

@@ -601,6 +601,14 @@ internal sealed partial class PVSSystem : EntitySystem
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
}
var expandEvent = new ExpandPvsEvent(session, new List<EntityUid>());
RaiseLocalEvent(ref expandEvent);
foreach (var entityUid in expandEvent.Entities)
{
RecursivelyAddOverride(in entityUid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
}
var entityStates = new List<EntityState>();
foreach (var (entityUid, visiblity) in visibleEnts)

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Server.Maps
{
[TypeSerializer]
internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
throw new NotImplementedException();
}
public MapChunk Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, MapChunk? chunk = null)
{
var tileNode = (ValueDataNode)node["tiles"];
var tileBytes = Convert.FromBase64String(tileNode.Value);
using var stream = new MemoryStream(tileBytes);
using var reader = new BinaryReader(stream);
var mapManager = dependencies.Resolve<IMapManager>();
mapManager.SuppressOnTileChanged = true;
if (chunk == null)
{
throw new InvalidOperationException(
$"Someone tried deserializing a gridchunk without passing a value.");
}
if (context is not MapLoader.MapContext mapContext)
{
throw new InvalidOperationException(
$"Someone tried serializing a gridchunk without passing {nameof(MapLoader.MapContext)} as context.");
}
var tileMap = mapContext.TileMap;
if (tileMap == null)
{
throw new InvalidOperationException(
$"Someone tried deserializing a gridchunk before deserializing the tileMap.");
}
chunk.SuppressCollisionRegeneration = true;
var tileDefinitionManager = dependencies.Resolve<ITileDefinitionManager>();
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = reader.ReadUInt16();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
}
}
chunk.SuppressCollisionRegeneration = false;
mapManager.SuppressOnTileChanged = false;
return chunk;
}
public DataNode Write(ISerializationManager serializationManager, MapChunk value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var root = new MappingDataNode();
var ind = new ValueDataNode($"{value.X},{value.Y}");
root.Add("ind", ind);
var gridNode = new ValueDataNode();
root.Add("tiles", gridNode);
gridNode.Value = SerializeTiles(value);
return root;
}
private static string SerializeTiles(MapChunk chunk)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 4;
var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
var barr = new byte[nTiles];
using (var stream = new MemoryStream(barr))
using (var writer = new BinaryWriter(stream))
{
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var tile = chunk.GetTile(x, y);
writer.Write(tile.TypeId);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
}
}
}
return Convert.ToBase64String(barr);
}
public MapChunk Copy(ISerializationManager serializationManager, MapChunk source, MapChunk target, bool skipHook,
ISerializationContext? context = null)
{
throw new NotImplementedException();
}
}
//todo paul make this be used
[TypeSerializer]
internal sealed class GridSerializer : ITypeSerializer<MapGrid, MappingDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
throw new NotImplementedException();
}
public MapGrid Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, MapGrid? grid = null)
{
var info = node.Get<MappingDataNode>("settings");
var chunks = node.Get<SequenceDataNode>("chunks");
ushort csz = 0;
ushort tsz = 0;
float sgsz = 0.0f;
foreach (var kvInfo in info.Cast<KeyValuePair<ValueDataNode, ValueDataNode>>())
{
var key = kvInfo.Key.Value;
var val = kvInfo.Value.Value;
if (key == "chunksize")
csz = ushort.Parse(val);
else if (key == "tilesize")
tsz = ushort.Parse(val);
else if (key == "snapsize")
sgsz = float.Parse(val, CultureInfo.InvariantCulture);
}
//TODO: Pass in options
if (context is not MapLoader.MapContext mapContext)
{
throw new InvalidOperationException(
$"Someone tried serializing a mapgrid without passing {nameof(MapLoader.MapContext)} as context.");
}
if (grid == null) throw new NotImplementedException();
//todo paul grid ??= dependencies.Resolve<MapManager>().CreateUnboundGrid(mapContext.TargetMap);
foreach (var chunkNode in chunks.Cast<MappingDataNode>())
{
var (chunkOffsetX, chunkOffsetY) =
serializationManager.Read<Vector2i>(chunkNode["ind"], context, skipHook);
var chunk = grid.GetChunk(chunkOffsetX, chunkOffsetY);
serializationManager.Read(typeof(MapChunkSerializer), chunkNode, context, skipHook, chunk);
}
return grid;
}
public DataNode Write(ISerializationManager serializationManager, MapGrid value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var gridn = new MappingDataNode();
var info = new MappingDataNode();
var chunkSeq = new SequenceDataNode();
gridn.Add("settings", info);
gridn.Add("chunks", chunkSeq);
info.Add("chunksize", value.ChunkSize.ToString(CultureInfo.InvariantCulture));
info.Add("tilesize", value.TileSize.ToString(CultureInfo.InvariantCulture));
var chunks = value.GetMapChunks();
foreach (var chunk in chunks)
{
var chunkNode = serializationManager.WriteValue(chunk.Value);
chunkSeq.Add(chunkNode);
}
return gridn;
}
public MapGrid Copy(ISerializationManager serializationManager, MapGrid source, MapGrid target, bool skipHook,
ISerializationContext? context = null)
{
throw new NotImplementedException();
}
}
}

View File

@@ -13,14 +13,15 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics;
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.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
@@ -44,6 +45,7 @@ namespace Robust.Server.Maps
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IServerEntityManagerInternal _serverEntityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISerializationManager _serializationManager = default!;
public event Action<YamlStream, string>? LoadedMapData;
@@ -52,7 +54,7 @@ namespace Robust.Server.Maps
{
var grid = _mapManager.GetGrid(gridId);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager, _serializationManager);
context.RegisterGrid(grid);
var root = context.Serialize();
var document = new YamlDocument(root);
@@ -99,7 +101,7 @@ namespace Robust.Server.Maps
}
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
_prototypeManager, _serializationManager, data.RootNode.ToDataNodeCast<MappingDataNode>(), mapId, options);
context.Deserialize();
grid = context.Grids[0];
@@ -139,7 +141,7 @@ namespace Robust.Server.Maps
public void SaveMap(MapId mapId, string yamlPath)
{
Logger.InfoS("map", $"Saving map {mapId} to {yamlPath}");
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager, _serializationManager);
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
{
context.RegisterGrid(grid);
@@ -206,7 +208,7 @@ namespace Robust.Server.Maps
LoadedMapData?.Invoke(data.Stream, resPath.ToString());
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
_prototypeManager, _serializationManager, data.RootNode.ToDataNodeCast<MappingDataNode>(), mapId, options);
context.Deserialize();
PostDeserialize(mapId, context);
@@ -216,7 +218,7 @@ namespace Robust.Server.Maps
/// <summary>
/// Handles the primary bulk of state during the map serialization process.
/// </summary>
private sealed class MapContext : ISerializationContext, IEntityLoadContext,
internal sealed class MapContext : ISerializationContext, IEntityLoadContext,
ITypeSerializer<GridId, ValueDataNode>,
ITypeSerializer<EntityUid, ValueDataNode>,
ITypeReaderWriter<EntityUid, ValueDataNode>
@@ -225,6 +227,7 @@ namespace Robust.Server.Maps
private readonly ITileDefinitionManager _tileDefinitionManager;
private readonly IServerEntityManagerInternal _serverEntityManager;
private readonly IPrototypeManager _prototypeManager;
private readonly ISerializationManager _serializationManager;
private readonly MapLoadOptions? _loadOptions;
private readonly Dictionary<GridId, int> GridIDMap = new();
@@ -235,19 +238,20 @@ namespace Robust.Server.Maps
private readonly Dictionary<int, EntityUid> UidEntityMap = new();
public readonly List<EntityUid> Entities = new();
private readonly List<(EntityUid, YamlMappingNode)> _entitiesToDeserialize
private readonly List<(EntityUid, MappingDataNode)> _entitiesToDeserialize
= new();
private bool IsBlueprintMode => GridIDMap.Count == 1;
private readonly YamlMappingNode RootNode;
private readonly MapId TargetMap;
private readonly MappingDataNode RootNode;
public readonly MapId TargetMap;
private Dictionary<string, YamlMappingNode>? CurrentReadingEntityComponents;
private Dictionary<string, MappingDataNode>? CurrentReadingEntityComponents;
private string? CurrentWritingComponent;
private EntityUid? CurrentWritingEntity;
public IReadOnlyDictionary<ushort, string>? TileMap => _tileMap;
private Dictionary<ushort, string>? _tileMap;
public Dictionary<(Type, Type), object> TypeReaders { get; }
@@ -258,14 +262,16 @@ namespace Robust.Server.Maps
public bool MapIsPostInit { get; private set; }
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities, IPrototypeManager prototypeManager)
IServerEntityManagerInternal entities, IPrototypeManager prototypeManager,
ISerializationManager serializationManager)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
_serverEntityManager = entities;
_prototypeManager = prototypeManager;
_serializationManager = serializationManager;
RootNode = new YamlMappingNode();
RootNode = new MappingDataNode();
TypeWriters = new Dictionary<Type, object>()
{
{typeof(GridId), this},
@@ -281,12 +287,14 @@ namespace Robust.Server.Maps
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities,
IPrototypeManager prototypeManager,
YamlMappingNode node, MapId targetMapId, MapLoadOptions options)
ISerializationManager serializationManager,
MappingDataNode node, MapId targetMapId, MapLoadOptions options)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
_serverEntityManager = entities;
_loadOptions = options;
_serializationManager = serializationManager;
RootNode = node;
TargetMap = targetMapId;
@@ -355,13 +363,13 @@ namespace Robust.Server.Maps
private void VerifyEntitiesExist()
{
var fail = false;
var entities = RootNode.GetNode<YamlSequenceNode>("entities");
var entities = RootNode.Get<SequenceDataNode>("entities");
var reportedError = new HashSet<string>();
foreach (var entityDef in entities.Cast<YamlMappingNode>())
foreach (var entityDef in entities.Cast<MappingDataNode>())
{
if (entityDef.TryGetNode("type", out var typeNode))
if (entityDef.TryGet<ValueDataNode>("type", out var typeNode))
{
var type = typeNode.AsString();
var type = typeNode.Value;
if (!_prototypeManager.HasIndex<EntityPrototype>(type) && !reportedError.Contains(type))
{
Logger.Error("Missing prototype for map: {0}", type);
@@ -384,7 +392,7 @@ namespace Robust.Server.Maps
foreach (var (entity, data) in _entitiesToDeserialize)
{
if (!data.TryGetNode("components", out YamlSequenceNode? componentList))
if (!data.TryGet("components", out SequenceDataNode? componentList))
{
continue;
}
@@ -399,7 +407,7 @@ namespace Robust.Server.Maps
var castComp = (Component) component;
var compName = compFactory.GetComponentName(castComp.GetType());
if (componentList.Any(p => p["type"].AsString() == compName))
if (componentList.Cast<MappingDataNode>().Any(p => ((ValueDataNode)p["type"]).Value == compName))
{
if (prototype.Components.ContainsKey(compName))
{
@@ -487,7 +495,7 @@ namespace Robust.Server.Maps
// Now we need to actually bind the MapGrids to their components so that you can resolve GridId -> EntityUid
// After doing this, it should be 100% safe to use the MapManager API like normal.
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
var yamlGrids = RootNode.Get<SequenceDataNode>("grids");
// get ents that the grids will bind to
var gridComps = new Dictionary<GridId, MapGridComponent>(_readGridIndices.Count);
@@ -511,28 +519,30 @@ namespace Robust.Server.Maps
{
// Here is where the implicit index pairing magic happens from the yaml.
var gridIndex = _readGridIndices[index];
var yamlGrid = (YamlMappingNode)yamlGrids.Children[index];
var yamlGrid = (MappingDataNode)yamlGrids[index];
// designed to throw if something is broken, every grid must map to an ent
var gridComp = gridComps[gridIndex];
DebugTools.Assert(gridComp.GridIndex == gridIndex);
YamlMappingNode yamlGridInfo = (YamlMappingNode)yamlGrid["settings"];
YamlSequenceNode yamlGridChunks = (YamlSequenceNode)yamlGrid["chunks"];
MappingDataNode yamlGridInfo = (MappingDataNode)yamlGrid["settings"];
SequenceDataNode yamlGridChunks = (SequenceDataNode)yamlGrid["chunks"];
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
foreach (var chunkNode in yamlGridChunks.Cast<YamlMappingNode>())
foreach (var chunkNode in yamlGridChunks.Cast<MappingDataNode>())
{
YamlGridSerializer.DeserializeChunk(_mapManager, grid, chunkNode, _tileMap!, _tileDefinitionManager);
var (chunkOffsetX, chunkOffsetY) = _serializationManager.Read<Vector2i>(chunkNode["ind"]);
var chunk = grid.GetChunk(chunkOffsetX, chunkOffsetY);
_serializationManager.Read(chunkNode, this, value: chunk);
}
Grids.Add(grid); // Grids are kept in index order
}
}
private static MapGrid AllocateMapGrid(MapGridComponent gridComp, YamlMappingNode yamlGridInfo)
private static MapGrid AllocateMapGrid(MapGridComponent gridComp, MappingDataNode yamlGridInfo)
{
// sane defaults
ushort csz = 16;
@@ -540,8 +550,8 @@ namespace Robust.Server.Maps
foreach (var kvInfo in yamlGridInfo)
{
var key = kvInfo.Key.ToString();
var val = kvInfo.Value.ToString();
var key = ((ValueDataNode)kvInfo.Key).Value;
var val = ((ValueDataNode)kvInfo.Value).Value;
if (key == "chunksize")
csz = ushort.Parse(val);
else if (key == "tilesize")
@@ -590,14 +600,14 @@ namespace Robust.Server.Maps
private void ReadMetaSection()
{
var meta = RootNode.GetNode<YamlMappingNode>("meta");
var ver = meta.GetNode("format").AsInt();
var meta = RootNode.Get<MappingDataNode>("meta");
var ver = meta.Get<ValueDataNode>("format").AsInt();
if (ver != MapFormatVersion)
{
throw new InvalidDataException("Cannot handle this map file version.");
}
if (meta.TryGetNode("postmapinit", out var mapInitNode))
if (meta.TryGet<ValueDataNode>("postmapinit", out var mapInitNode))
{
MapIsPostInit = mapInitNode.AsBool();
}
@@ -612,11 +622,11 @@ namespace Robust.Server.Maps
// Load tile mapping so that we can map the stored tile IDs into the ones actually used at runtime.
_tileMap = new Dictionary<ushort, string>();
var tileMap = RootNode.GetNode<YamlMappingNode>("tilemap");
foreach (var (key, value) in tileMap)
var tileMap = RootNode.Get<MappingDataNode>("tilemap");
foreach (var (key, value) in tileMap.Children)
{
var tileId = (ushort) key.AsInt();
var tileDefName = value.AsString();
var tileId = (ushort) ((ValueDataNode)key).AsInt();
var tileDefName = ((ValueDataNode)value).Value;
_tileMap.Add(tileId, tileDefName);
}
}
@@ -625,9 +635,9 @@ namespace Robust.Server.Maps
{
// sets up the mapping so the serializer can properly deserialize GridIds.
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
var yamlGrids = RootNode.Get<SequenceDataNode>("grids");
for (var i = 0; i < yamlGrids.Children.Count; i++)
for (var i = 0; i < yamlGrids.Count; i++)
{
_readGridIndices.Add(_mapManager.GenerateGridId(null));
}
@@ -652,17 +662,17 @@ namespace Robust.Server.Maps
private void AllocEntities()
{
var entities = RootNode.GetNode<YamlSequenceNode>("entities");
foreach (var entityDef in entities.Cast<YamlMappingNode>())
var entities = RootNode.Get<SequenceDataNode>("entities");
foreach (var entityDef in entities.Cast<MappingDataNode>())
{
string? type = null;
if (entityDef.TryGetNode("type", out var typeNode))
if (entityDef.TryGet<ValueDataNode>("type", out var typeNode))
{
type = typeNode.AsString();
type = typeNode.Value;
}
var uid = Entities.Count;
if (entityDef.TryGetNode("uid", out var uidNode))
if (entityDef.TryGet<ValueDataNode>("uid", out var uidNode))
{
uid = uidNode.AsInt();
}
@@ -684,15 +694,14 @@ namespace Robust.Server.Maps
{
foreach (var (entity, data) in _entitiesToDeserialize)
{
CurrentReadingEntityComponents = new Dictionary<string, YamlMappingNode>();
if (data.TryGetNode("components", out YamlSequenceNode? componentList))
CurrentReadingEntityComponents = new Dictionary<string, MappingDataNode>();
if (data.TryGet("components", out SequenceDataNode? componentList))
{
foreach (var compData in componentList)
foreach (var compData in componentList.Cast<MappingDataNode>())
{
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;
var datanode = compData.Copy();
datanode.Remove("type");
CurrentReadingEntityComponents[((ValueDataNode)compData["type"]).Value] = datanode;
}
}
@@ -737,12 +746,12 @@ namespace Robust.Server.Maps
PopulateEntityList();
WriteEntitySection();
return RootNode;
return RootNode.ToYaml();
}
private void WriteMetaSection()
{
var meta = new YamlMappingNode();
var meta = new MappingDataNode();
RootNode.Add("meta", meta);
meta.Add("format", MapFormatVersion.ToString(CultureInfo.InvariantCulture));
// TODO: Make these values configurable.
@@ -764,7 +773,7 @@ namespace Robust.Server.Maps
private void WriteTileMapSection()
{
var tileMap = new YamlMappingNode();
var tileMap = new MappingDataNode();
RootNode.Add("tilemap", tileMap);
foreach (var tileDefinition in _tileDefinitionManager)
{
@@ -774,12 +783,12 @@ namespace Robust.Server.Maps
private void WriteGridSection()
{
var grids = new YamlSequenceNode();
var grids = new SequenceDataNode();
RootNode.Add("grids", grids);
foreach (var grid in Grids)
{
var entry = YamlGridSerializer.SerializeGrid(grid);
var entry = _serializationManager.WriteValue(grid, context: this);
grids.Add(entry);
}
}
@@ -843,14 +852,14 @@ namespace Robust.Server.Maps
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var metaQuery = _serverEntityManager.GetEntityQuery<MetaDataComponent>();
var entities = new YamlSequenceNode();
var entities = new SequenceDataNode();
RootNode.Add("entities", entities);
var prototypeCompCache = new Dictionary<string, Dictionary<string, MappingDataNode>>();
foreach (var (saveId, entityUid) in UidEntityMap.OrderBy(e=>e.Key))
{
CurrentWritingEntity = entityUid;
var mapping = new YamlMappingNode
var mapping = new MappingDataNode
{
{"uid", saveId.ToString(CultureInfo.InvariantCulture)}
};
@@ -870,7 +879,7 @@ namespace Robust.Server.Maps
}
}
var components = new YamlSequenceNode();
var components = new SequenceDataNode();
// See engine#636 for why the Distinct() call.
foreach (var component in _serverEntityManager.GetComponents(entityUid))
@@ -894,11 +903,11 @@ namespace Robust.Server.Maps
{
compMapping.Add("type", new ValueDataNode(compName));
// Something actually got written!
components.Add(compMapping.ToYamlNode());
components.Add(compMapping);
}
}
if (components.Children.Count != 0)
if (components.Count != 0)
{
mapping.Add("components", components);
}
@@ -908,8 +917,8 @@ namespace Robust.Server.Maps
}
// Create custom object serializers that will correctly allow data to be overriden by the map file.
IComponent IEntityLoadContext.GetComponentData(string componentName,
IComponent? protoData)
MappingDataNode IEntityLoadContext.GetComponentData(string componentName,
MappingDataNode? protoData)
{
if (CurrentReadingEntityComponents == null)
{
@@ -919,20 +928,15 @@ namespace Robust.Server.Maps
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))
{
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) return mapping.Copy();
return serializationManager.PushCompositionWithGenericNode(
factory.GetRegistration(componentName).Type, new[] { protoData }, mapping, this);
}
return data;
return protoData ?? new MappingDataNode();
}
public IEnumerable<string> GetExtraComponentTypes()
@@ -947,10 +951,10 @@ namespace Robust.Server.Maps
: base(message) { }
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
public GridId Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
ISerializationContext? context = null, GridId _ = default)
{
// This is the code that deserializes the Grids index into the GridId. This has to happen between Grid allocation
// and when grids are bound to their entities.
@@ -966,7 +970,7 @@ namespace Robust.Server.Maps
throw new MapLoadException($"Error in map file: found local grid ID '{val}' which does not exist.");
}
return new DeserializedValue<GridId>(_readGridIndices[val]);
return _readGridIndices[val];
}
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
@@ -1032,15 +1036,15 @@ namespace Robust.Server.Maps
}
}
DeserializationResult ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager serializationManager,
EntityUid ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager serializationManager,
ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
ISerializationContext? context, EntityUid _)
{
if (node.Value == "null")
{
return new DeserializedValue<EntityUid>(EntityUid.Invalid);
return EntityUid.Invalid;
}
var val = int.Parse(node.Value);
@@ -1048,11 +1052,11 @@ namespace Robust.Server.Maps
if (val >= Entities.Count || !UidEntityMap.ContainsKey(val) || !Entities.TryFirstOrNull(e => e == UidEntityMap[val], out var entity))
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
return null!;
return EntityUid.Invalid;
}
else
{
return new DeserializedValue<EntityUid>(entity!.Value);
return entity!.Value;
}
}

View File

@@ -1,120 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Robust.Shared.Map;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace Robust.Server.Maps
{
internal static class YamlGridSerializer
{
public static YamlMappingNode SerializeGrid(IMapGrid mapGrid)
{
var grid = (IMapGridInternal) mapGrid;
var gridn = new YamlMappingNode();
var info = new YamlMappingNode();
var chunkSeq = new YamlSequenceNode();
gridn.Add("settings", info);
gridn.Add("chunks", chunkSeq);
info.Add("chunksize", grid.ChunkSize.ToString(CultureInfo.InvariantCulture));
info.Add("tilesize", grid.TileSize.ToString(CultureInfo.InvariantCulture));
var chunks = grid.GetMapChunks();
foreach (var chunk in chunks)
{
var chunkNode = SerializeChunk(chunk.Value);
chunkSeq.Add(chunkNode);
}
return gridn;
}
private static YamlNode SerializeChunk(MapChunk chunk)
{
var root = new YamlMappingNode();
var value = new YamlScalarNode($"{chunk.X},{chunk.Y}");
value.Style = ScalarStyle.DoubleQuoted;
root.Add("ind", value);
var gridNode = new YamlScalarNode();
root.Add("tiles", gridNode);
gridNode.Value = SerializeTiles(chunk);
return root;
}
private static string SerializeTiles(MapChunk chunk)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 4;
var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
var barr = new byte[nTiles];
using (var stream = new MemoryStream(barr))
using (var writer = new BinaryWriter(stream))
{
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var tile = chunk.GetTile(x, y);
writer.Write(tile.TypeId);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
}
}
}
return Convert.ToBase64String(barr);
}
public static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid,
YamlMappingNode chunkData,
IReadOnlyDictionary<ushort, string> tileDefMapping,
ITileDefinitionManager tileDefinitionManager)
{
var indNode = chunkData["ind"];
var tileNode = chunkData["tiles"];
var (chunkOffsetX, chunkOffsetY) = indNode.AsVector2i();
var tileBytes = Convert.FromBase64String(tileNode.ToString());
using var stream = new MemoryStream(tileBytes);
using var reader = new BinaryReader(stream);
mapMan.SuppressOnTileChanged = true;
var chunk = grid.GetChunk(chunkOffsetX, chunkOffsetY);
chunk.SuppressCollisionRegeneration = true;
for (ushort y = 0; y < grid.ChunkSize; y++)
{
for (ushort x = 0; x < grid.ChunkSize; x++)
{
var id = reader.ReadUInt16();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
var defName = tileDefMapping[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
}
}
chunk.SuppressCollisionRegeneration = false;
mapMan.SuppressOnTileChanged = false;
}
}
}

View File

@@ -1,118 +0,0 @@
using System;
using NFluidsynth;
using Robust.Shared.Serialization;
namespace Robust.Shared.Audio.Midi
{
/// <summary>
/// This class is a data representation of a Midi Event.
/// It's 'compatible' with NFluidsynth's own MidiEvent class.
/// </summary>
[Serializable, NetSerializable]
public struct MidiEvent
{
public byte Type { get; set; }
public byte Channel { get; set; }
public byte Key { get; set; }
public byte Velocity { get; set; }
public int Control { get; set; }
public byte Value { get; set; }
public byte Program { get; set; }
public short Pitch { get; set; } // needs range from 0 to 16383
public uint Tick { get; set; }
public static explicit operator MidiEvent(NFluidsynth.MidiEvent midiEvent)
{
return new()
{
Type = (byte) midiEvent.Type,
Channel = (byte) midiEvent.Channel,
Control = midiEvent.Control,
Key = (byte) midiEvent.Key,
Pitch = (short) midiEvent.Pitch,
Program = (byte) midiEvent.Program,
Value = (byte) midiEvent.Value,
Velocity = (byte) midiEvent.Velocity,
};
}
public static implicit operator NFluidsynth.MidiEvent(MidiEvent midiEvent)
{
return new()
{
Type = midiEvent.Type,
Channel = midiEvent.Channel,
Control = midiEvent.Control,
Key = midiEvent.Key,
Pitch = midiEvent.Pitch,
Program = midiEvent.Program,
Value = midiEvent.Value,
Velocity = midiEvent.Velocity,
};
}
public override string ToString()
{
return $"{base.ToString()} >> TYPE: {Type} || CHANNEL: {Channel} || CONTROL: {Control} || KEY: {Key} || VELOCITY: {Velocity} || PITCH: {Pitch} || PROGRAM: {Program} || VALUE: {Value} <<";
}
public static implicit operator SequencerEvent(MidiEvent midiEvent)
{
var @event = new SequencerEvent();
switch (midiEvent.Type)
{
// NoteOff
case 128:
@event.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
// NoteOn
case 144:
if(midiEvent.Velocity != 0)
@event.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
else
@event.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
// After Touch
case 160:
@event.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
break;
// CC
case 176:
@event.ControlChange(midiEvent.Channel, (short)midiEvent.Control, midiEvent.Value);
break;
// Program Change
case 192:
@event.ProgramChange(midiEvent.Channel, midiEvent.Program);
break;
// Channel Pressure
case 208:
@event.ChannelPressure(midiEvent.Channel, midiEvent.Value);
break;
// Pitch Bend
case 224:
@event.PitchBend(midiEvent.Channel, midiEvent.Pitch);
break;
}
return @event;
}
}
}

View File

@@ -0,0 +1,64 @@
namespace Robust.Shared.Audio.Midi;
/// <summary>
/// Helper enum that keeps track of all MIDI commands that Robust currently supports.
/// </summary>
public enum RobustMidiCommand : byte
{
/// <summary>
/// NoteOff event. <br/>
/// data1 - Key, <br/>
/// data2 - undefined
/// </summary>
NoteOff = 0x80,
/// <summary>
/// NoteOn event. <br/>
/// data1 - Key, <br/>
/// data2 - Velocity
/// </summary>
NoteOn = 0x90,
/// <summary>
/// AfterTouch event. <br/>
/// data1 - Key, <br/>
/// data2 - Value
/// </summary>
/// <remarks>Also known as "KeyPressure".</remarks>
AfterTouch = 0xA0,
/// <summary>
/// ControlChange (CC) event. <br/>
/// data1 - Control, <br/>
/// data2 - Value
/// </summary>
ControlChange = 0xB0,
/// <summary>
/// ProgramChange event. <br/>
/// data1 - Program, <br/>
/// data2 - undefined
/// </summary>
ProgramChange = 0xC0,
/// <summary>
/// ChannelPressure event. <br/>
/// data1 - Value, <br/>
/// data2 - undefined
/// </summary>
ChannelPressure = 0xD0,
/// <summary>
/// PitchBend event. <br/>
/// data1 - Lower Pitch Nibble, <br/>
/// data2 - Higher Pitch Nibble
/// </summary>
PitchBend = 0xE0,
/// <summary>
/// SystemMessage event. <br/>
/// data1 - Control <br/>
/// data2 - undefined
/// </summary>
SystemMessage = 0xF0,
}

View File

@@ -0,0 +1,94 @@
using System;
using Robust.Shared.Serialization;
namespace Robust.Shared.Audio.Midi
{
/// <summary>
/// This class is a lightweight data representation of a Midi Event.
/// </summary>
[Serializable, NetSerializable]
public readonly struct RobustMidiEvent
{
#region Data
/// <summary>
/// Byte that stores both Command Type and Channel.
/// </summary>
public readonly byte Status;
public readonly byte Data1;
public readonly byte Data2;
/// <summary>
/// Sequencer tick to schedule this event at.
/// </summary>
public readonly uint Tick;
#endregion
#region Properties
public int Channel => Status & 0x0F; // Low nibble.
public int Command => Status & 0xF0; // High nibble.
public RobustMidiCommand MidiCommand => (RobustMidiCommand) Command;
public byte Key => Data1;
public byte Velocity => Data2;
public byte Control => Data1;
public byte Value => Data2;
public int Pitch => (Data2 << 8) | Data1;
public byte Pressure => Data1;
public byte Program => Data1;
#endregion
public RobustMidiEvent(byte status, byte data1, byte data2, uint tick)
{
Status = status;
Data1 = data1;
Data2 = data2;
Tick = tick;
}
public override string ToString()
{
return $"{base.ToString()} >> CHANNEL: 0x{Channel:X} || COMMAND: 0x{Command:X} {MidiCommand} || DATA1: 0x{Data1:X} || DATA2: 0x{Data2:X} || TICK: {Tick} <<";
}
#region Static Methods
/// <summary>
/// Returns a status byte given a channel byte and a command type byte.
/// </summary>
public static byte MakeStatus(byte channel, byte command)
{
return (byte) (command | channel);
}
/// <summary>
/// Creates and returns an event to turn all notes off on a given channel.
/// </summary>
public static RobustMidiEvent AllNotesOff(byte channel, uint tick)
{
return new RobustMidiEvent(MakeStatus(channel, (byte)RobustMidiCommand.SystemMessage), 0x0B, 0x0, tick);
}
/// <summary>
/// Creates and returns an event to reset all controllers.
/// </summary>
public static RobustMidiEvent ResetAllControllers(uint tick)
{
return new RobustMidiEvent((byte)RobustMidiCommand.ControlChange, 0x79, 0x0, tick);
}
/// <summary>
/// Creates and returns a system reset event.
/// </summary>
public static RobustMidiEvent SystemReset(uint tick)
{
return new RobustMidiEvent((byte) RobustMidiCommand.SystemMessage | 0x0F, 0x0, 0x0, tick);
}
#endregion
}
}

View File

@@ -35,7 +35,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.PreAdd" /> to <see cref="ComponentLifeStage.Added" />,
/// calling <see cref="OnAdd" />.
/// after raising a <see cref="ComponentAdd"/> event.
/// </summary>
internal void LifeAddToEntity(IEntityManager entManager)
{
@@ -44,14 +44,7 @@ namespace Robust.Shared.GameObjects
LifeStage = ComponentLifeStage.Adding;
CreationTick = entManager.CurrentTick;
entManager.EventBus.RaiseComponentEvent(this, CompAddInstance);
OnAdd();
#if DEBUG
if (LifeStage != ComponentLifeStage.Added)
{
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(OnAdd)} in derived method.");
}
#endif
LifeStage = ComponentLifeStage.Added;
}
/// <summary>
@@ -166,14 +159,6 @@ namespace Robust.Shared.GameObjects
private static readonly ComponentShutdown CompShutdownInstance = new();
private static readonly ComponentRemove CompRemoveInstance = new();
/// <summary>
/// Called when the component gets added to an entity.
/// </summary>
protected virtual void OnAdd()
{
LifeStage = ComponentLifeStage.Added;
}
/// <summary>
/// Called when all of the entity's other components have been added and are available,
/// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct.

View File

@@ -87,6 +87,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
internal LinkedList<Contact> Contacts = new();
[DataField("ignorePaused"), ViewVariables(VVAccess.ReadWrite)]
public bool IgnorePaused { get; set; }
internal SharedPhysicsMapComponent? PhysicsMap { get; set; }
@@ -113,7 +114,8 @@ namespace Robust.Shared.GameObjects
_angularVelocity = 0.0f;
// SynchronizeFixtures(); TODO: When CCD
}
else
// Even if it's dynamic if it can't collide then don't force it awake.
else if (_canCollide)
{
SetAwake(true);
}

View File

@@ -1,5 +1,3 @@
using System;
namespace Robust.Shared.GameObjects
{
// If you wanna use these, add it to some random prototype.
@@ -11,24 +9,15 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Throws an exception in <see cref="OnAdd" />.
/// </summary>
public sealed class DebugExceptionOnAddComponent : Component
{
protected override void OnAdd() => throw new NotSupportedException();
}
public sealed class DebugExceptionOnAddComponent : Component { }
/// <summary>
/// Throws an exception in <see cref="Initialize" />.
/// </summary>
public sealed class DebugExceptionInitializeComponent : Component
{
protected override void Initialize() => throw new NotSupportedException();
}
public sealed class DebugExceptionInitializeComponent : Component { }
/// <summary>
/// Throws an exception in <see cref="Startup" />.
/// </summary>
public sealed class DebugExceptionStartupComponent : Component
{
protected override void Startup() => throw new NotSupportedException();
}
public sealed class DebugExceptionStartupComponent : Component { }
}

View File

@@ -1,25 +0,0 @@
using Robust.Shared.IoC;
using Robust.Shared.Map;
namespace Robust.Shared.GameObjects
{
[RegisterComponent]
public sealed class IgnorePauseComponent : Component
{
protected override void OnAdd()
{
base.OnAdd();
IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(Owner).EntityPaused = false;
}
protected override void OnRemove()
{
base.OnRemove();
var entMan = IoCManager.Resolve<IEntityManager>();
if (IoCManager.Resolve<IMapManager>().IsMapPaused(entMan.GetComponent<TransformComponent>(Owner).MapID))
{
entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = true;
}
}
}
}

View File

@@ -151,12 +151,8 @@ namespace Robust.Shared.GameObjects
if (_entityPaused == value)
return;
var entMan = IoCManager.Resolve<IEntityManager>();
if (value && entMan.HasComponent<IgnorePauseComponent>(Owner))
return;
_entityPaused = value;
entMan.EventBus.RaiseLocalEvent(Owner, new EntityPausedEvent(Owner, value));
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, new EntityPausedEvent(Owner, value));
}
}

View File

@@ -135,6 +135,16 @@ namespace Robust.Shared.GameObjects
}
}
public sealed class BoundUIOpenedEvent : BoundUserInterfaceMessage
{
public BoundUIOpenedEvent(object uiKey, EntityUid uid, ICommonSession session)
{
UiKey = uiKey;
Entity = uid;
Session = session;
}
}
public sealed class BoundUIClosedEvent : BoundUserInterfaceMessage
{
public BoundUIClosedEvent(object uiKey, EntityUid uid, ICommonSession session)

View File

@@ -417,7 +417,7 @@ namespace Robust.Shared.GameObjects
var entity = AllocEntity(prototypeName, uid);
try
{
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, null);
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, PrototypeManager, this, _serManager, null);
return entity;
}
catch (Exception e)
@@ -431,7 +431,7 @@ namespace Robust.Shared.GameObjects
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
{
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, PrototypeManager, this, _serManager, context);
}
private void InitializeAndStartEntity(EntityUid entity, MapId mapId)

View File

@@ -410,7 +410,7 @@ public partial class EntitySystem
/// <inheritdoc cref="IEntityManager.TryGetComponent&lt;T&gt;(EntityUid?, out T)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool TryComp<T>(EntityUid? uid, [NotNullWhen(true)] out T? comp)
protected bool TryComp<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? comp)
{
if (!uid.HasValue)
{

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown.Mapping;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.GameObjects
@@ -14,7 +15,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Gets the serializer used to ExposeData a specific component.
/// </summary>
IComponent GetComponentData(string componentName, IComponent? protoData);
MappingDataNode GetComponentData(string componentName, MappingDataNode? protoData);
/// <summary>
/// Gets extra component names that must also be instantiated on top of the ones defined in the prototype,

View File

@@ -0,0 +1,15 @@
using System;
namespace Robust.Shared.GameObjects;
public sealed class DebugExceptionSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DebugExceptionOnAddComponent, ComponentAdd>((_, _, _) => throw new NotSupportedException());
SubscribeLocalEvent<DebugExceptionInitializeComponent, ComponentInit>((_, _, _) => throw new NotSupportedException());
SubscribeLocalEvent<DebugExceptionStartupComponent, ComponentStartup>((_, _, _) => throw new NotSupportedException());
}
}

View File

@@ -1,476 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public sealed partial class EntityLookupSystem
{
// TODO: Need to nuke most of the below and cleanup when entitylookup gets optimised some more (physics + containers).
private LookupsEnumerator GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
{
_mapManager.FindGridsIntersectingEnumerator(mapId, worldAABB, out var gridEnumerator, true);
return new LookupsEnumerator(EntityManager, _mapManager, mapId, gridEnumerator);
}
private struct LookupsEnumerator
{
private IEntityManager EntityManager;
private IMapManager _mapManager;
private MapId _mapId;
private FindGridsEnumerator _enumerator;
private bool _final;
public LookupsEnumerator(IEntityManager entityManager, IMapManager mapManager, MapId mapId, FindGridsEnumerator enumerator)
{
EntityManager = entityManager;
_mapManager = mapManager;
_mapId = mapId;
_enumerator = enumerator;
_final = false;
}
public bool MoveNext([NotNullWhen(true)] out EntityLookupComponent? component)
{
if (!_enumerator.MoveNext(out var grid))
{
if (_final || _mapId == MapId.Nullspace)
{
component = null;
return false;
}
_final = true;
EntityUid mapUid = _mapManager.GetMapEntityIdOrThrow(_mapId);
component = EntityManager.GetComponent<EntityLookupComponent>(mapUid);
return true;
}
// TODO: Recursive and all that.
component = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
return true;
}
}
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2 worldAABB, LookupFlags flags)
{
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (!EntityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2Rotated worldBounds, LookupFlags flags)
{
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
foreach (var uid in grid.GetAnchoredEntities(worldBounds))
{
if (!EntityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
/// <inheritdoc />
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var found = false;
var enumerator = GetLookupsIntersecting(mapId, box);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(box);
lookup.Tree.QueryAabb(ref found, (ref bool found, in EntityUid ent) =>
{
if (EntityManager.Deleted(ent))
return true;
found = true;
return false;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
if (!found)
{
foreach (var _ in GetAnchored(mapId, box, flags))
{
return true;
}
}
return found;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityUidQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref EntityUid data) => callback(data));
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (!EntityManager.EntityExists(uid)) continue;
callback(uid);
}
}
}
}
/// <inheritdoc />
public void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityUidQueryCallback callback)
{
lookup.Tree._b2Tree.FastQuery(ref localAABB, (ref EntityUid data) => callback(data));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!EntityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(mapId, worldAABB, flags))
{
list.Add(ent);
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var worldAABB = worldBounds.CalcBoundingBox();
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldBounds);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!EntityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(mapId, worldBounds, flags))
{
list.Add(ent);
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
var list = new List<EntityUid>();
var state = (list, position);
var enumerator = GetLookupsIntersecting(mapId, aabb);
while (enumerator.MoveNext(out var lookup))
{
var localPoint = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
{
if (Intersecting(ent, state.position))
{
state.list.Add(ent);
}
return true;
}, localPoint, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0 &&
_mapManager.TryFindGridAt(mapId, position, out var grid) &&
grid.TryGetTileRef(position, out var tile))
{
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
{
if (!EntityManager.EntityExists(uid)) continue;
state.list.Add(uid);
}
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
return GetEntitiesIntersecting(position.MapId, position.Position, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var mapPos = position.ToMap(EntityManager);
return GetEntitiesIntersecting(mapPos.MapId, mapPos.Position, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityUid entity, float enlarged = 0f, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var worldAABB = GetWorldAABB(entity);
var xform = EntityManager.GetComponent<TransformComponent>(entity);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var enumerator = GetLookupsIntersecting(xform.MapID, worldAABB);
var list = new List<EntityUid>();
while (enumerator.MoveNext(out var lookup))
{
// To get the tightest bounds possible we'll re-calculate it for each lookup.
var localBounds = GetLookupBounds(entity, lookup, worldPos, worldRot, enlarged);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!EntityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, localBounds, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(xform.MapID, worldAABB, flags))
{
list.Add(ent);
}
return list;
}
private Box2 GetLookupBounds(EntityUid uid, EntityLookupComponent lookup, Vector2 worldPos, Angle worldRot, float enlarged)
{
var (_, lookupRot, lookupInvWorldMatrix) = EntityManager.GetComponent<TransformComponent>(lookup.Owner).GetWorldPositionRotationInvMatrix();
var localPos = lookupInvWorldMatrix.Transform(worldPos);
var localRot = worldRot - lookupRot;
if (EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
var transform = new Transform(localPos, localRot);
Box2? aabb = null;
foreach (var (_, fixture) in manager.Fixtures)
{
if (!fixture.Hard) continue;
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
aabb = aabb?.Union(fixture.Shape.ComputeAABB(transform, i)) ?? fixture.Shape.ComputeAABB(transform, i);
}
}
if (aabb != null)
{
return aabb.Value.Enlarged(enlarged);
}
}
// So IsEmpty checks don't get triggered
return new Box2(localPos - float.Epsilon, localPos + float.Epsilon);
}
/// <inheritdoc />
public bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo)
{
var position = EntityManager.GetComponent<TransformComponent>(entityOne).MapPosition.Position;
return Intersecting(entityTwo, position);
}
private bool Intersecting(EntityUid entity, Vector2 mapPosition)
{
if (EntityManager.TryGetComponent(entity, out IPhysBody? component))
{
if (component.GetWorldAABB().Contains(mapPosition))
return true;
}
else
{
var transform = EntityManager.GetComponent<TransformComponent>(entity);
var entPos = transform.WorldPosition;
if (MathHelper.CloseToPercent(entPos.X, mapPosition.X)
&& MathHelper.CloseToPercent(entPos.Y, mapPosition.Y))
{
return true;
}
}
return false;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var mapCoordinates = position.ToMap(EntityManager);
var mapPosition = mapCoordinates.Position;
var aabb = new Box2(mapPosition - new Vector2(range, range),
mapPosition + new Vector2(range, range));
return GetEntitiesIntersecting(mapCoordinates.MapId, aabb, flags);
// TODO: Use a circle shape here mate
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var aabb = box.Enlarged(range);
return GetEntitiesIntersecting(mapId, aabb, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 point, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var aabb = new Box2(point, point).Enlarged(range);
return GetEntitiesIntersecting(mapId, aabb, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(EntityUid entity, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var worldAABB = GetWorldAABB(entity);
return GetEntitiesInRange(EntityManager.GetComponent<TransformComponent>(entity).MapID, worldAABB, range, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var position = coordinates.ToMap(EntityManager).Position;
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
{
var angle = new Angle(EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - position);
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
angle.Degrees > direction.Degrees - arcWidth / 2)
yield return entity;
}
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInMap(MapId mapId, LookupFlags flags = LookupFlags.IncludeAnchored)
{
DebugTools.Assert((flags & LookupFlags.Approximate) == 0x0);
foreach (EntityLookupComponent comp in EntityManager.EntityQuery<EntityLookupComponent>(true))
{
if (EntityManager.GetComponent<TransformComponent>(comp.Owner).MapID != mapId) continue;
foreach (var entity in comp.Tree)
{
if (EntityManager.Deleted(entity)) continue;
yield return entity;
}
}
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
{
foreach (var tile in grid.GetAllTiles())
{
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
{
if (!EntityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesAt(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var state = (list, position);
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
var enumerator = GetLookupsIntersecting(mapId, aabb);
while (enumerator.MoveNext(out var lookup))
{
var offsetPos = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
{
state.list.Add(ent);
return true;
}, offsetPos, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, aabb))
{
foreach (var uid in grid.GetAnchoredEntities(aabb))
{
if (!EntityManager.EntityExists(uid)) continue;
list.Add(uid);
}
}
}
return list;
}
}

View File

@@ -1,13 +1,452 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.Shared.GameObjects;
public sealed partial class EntityLookupSystem
{
/*
* There is no GetEntitiesInMap method as this should be avoided; anyone that really needs it can implement it themselves
*/
// Internal API messy for now but mainly want external to be fairly stable for a while and optimise it later.
#region Private
private void AddEntitiesIntersecting(
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<EntityLookupComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldAABB);
foreach (var ent in lookup.Tree.QueryAabb(localAABB, (flags & LookupFlags.Approximate) != 0x0))
{
intersecting.Add(ent);
}
}
private void AddEntitiesIntersecting(
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<EntityLookupComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldBounds);
foreach (var ent in lookup.Tree.QueryAabb(localAABB, (flags & LookupFlags.Approximate) != 0x0))
{
intersecting.Add(ent);
}
}
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<EntityLookupComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldAABB);
foreach (var uid in lookup.Tree.QueryAabb(localAABB))
{
if (uid == ignored) continue;
return true;
}
if (!_mapManager.TryGetGrid(lookupUid, out var grid)) return false;
// TODO: Need a method that takes in local AABB.
if ((flags & LookupFlags.Anchored) != 0x0)
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (uid == ignored) continue;
return true;
}
}
return false;
}
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<EntityLookupComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldBounds);
var found = false;
lookup.Tree._b2Tree.Query(ref found, static (ref bool state, DynamicTree.Proxy _) =>
{
state = true;
return false;
}, localAABB);
if (found)
return true;
if (_mapManager.TryGetGrid(lookupUid, out var grid))
{
// TODO: Need a method that takes in local AABB.
if ((flags & LookupFlags.Anchored) != 0x0)
{
foreach (var _ in grid.GetAnchoredEntities(worldBounds))
{
return true;
}
}
}
return found;
}
private void RecursiveAdd(EntityUid uid, List<EntityUid> toAdd, EntityQuery<TransformComponent> xformQuery)
{
var childEnumerator = xformQuery.GetComponent(uid).ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
toAdd.Add(child.Value);
RecursiveAdd(child.Value, toAdd, xformQuery);
}
}
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags, EntityQuery<TransformComponent> xformQuery)
{
if ((flags & LookupFlags.Contained) == 0x0) return;
var conQuery = GetEntityQuery<ContainerManagerComponent>();
var toAdd = new List<EntityUid>();
foreach (var uid in intersecting)
{
if (!conQuery.TryGetComponent(uid, out var conManager)) continue;
foreach (var con in conManager.GetAllContainers())
{
foreach (var contained in con.ContainedEntities)
{
toAdd.Add(contained);
RecursiveAdd(contained, toAdd, xformQuery);
}
}
}
foreach (var uid in toAdd)
{
intersecting.Add(uid);
}
}
#endregion
#region Arc
public IEnumerable<EntityUid> GetEntitiesInArc(
EntityCoordinates coordinates,
float range,
Angle direction,
float arcWidth,
LookupFlags flags = DefaultFlags)
{
var position = coordinates.ToMap(EntityManager);
return GetEntitiesInArc(position, range, direction, arcWidth, flags);
}
public IEnumerable<EntityUid> GetEntitiesInArc(
MapCoordinates coordinates,
float range,
Angle direction,
float arcWidth,
LookupFlags flags = DefaultFlags)
{
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
{
var angle = new Angle(xformQuery.GetComponent(entity).WorldPosition - coordinates.Position);
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
angle.Degrees > direction.Degrees - arcWidth / 2)
yield return entity;
}
}
#endregion
#region Box2
public bool AnyEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return false;
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
if (AnyEntitiesIntersecting(grid.GridEntityId, worldAABB, flags, lookupQuery, xformQuery)) return true;
}
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
AddEntitiesIntersecting(grid.GridEntityId, intersecting, worldAABB, flags, lookupQuery, xformQuery);
if ((flags & LookupFlags.Anchored) != 0x0)
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (Deleted(uid)) continue;
intersecting.Add(uid);
}
}
}
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
return intersecting;
}
#endregion
#region Box2Rotated
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
{
if (AnyEntitiesIntersecting(grid.GridEntityId, worldBounds, flags, lookupQuery, xformQuery)) return true;
}
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags, lookupQuery, xformQuery);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
{
AddEntitiesIntersecting(grid.GridEntityId, intersecting, worldBounds, flags, lookupQuery, xformQuery);
if ((flags & LookupFlags.Anchored) != 0x0)
{
foreach (var uid in grid.GetAnchoredEntities(worldBounds))
{
if (Deleted(uid)) continue;
intersecting.Add(uid);
}
}
}
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
return intersecting;
}
#endregion
#region Entity
// TODO: Bit of duplication between here and the other methods. Was a bit lazy passing around predicates for speed too.
public bool AnyEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var worldAABB = GetWorldAABB(uid);
var mapID = Transform(uid).MapID;
if (mapID == MapId.Nullspace) return false;
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var grid in _mapManager.FindGridsIntersecting(mapID, worldAABB))
{
if (AnyEntitiesIntersecting(grid.GridEntityId, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
var mapUid = _mapManager.GetMapEntityId(mapID);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
}
public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return false;
var worldAABB = new Box2(mapPos.Position - range, mapPos.Position + range);
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var grid in _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB))
{
if (AnyEntitiesIntersecting(grid.GridEntityId, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
}
public HashSet<EntityUid> GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return new HashSet<EntityUid>();
var intersecting = GetEntitiesInRange(mapPos, range, flags).ToHashSet();
intersecting.Remove(uid);
return intersecting;
}
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return new HashSet<EntityUid>();
var intersecting = GetEntitiesIntersecting(mapPos, flags).ToHashSet();
intersecting.Remove(uid);
return intersecting;
}
#endregion
#region EntityCoordinates
public bool AnyEntitiesIntersecting(EntityCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
var mapPos = coordinates.ToMap(EntityManager);
return AnyEntitiesIntersecting(mapPos, flags);
}
public bool AnyEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
var mapPos = coordinates.ToMap(EntityManager);
return AnyEntitiesInRange(mapPos, range, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(EntityCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
var mapPos = coordinates.ToMap(EntityManager);
return GetEntitiesIntersecting(mapPos, flags);
}
public HashSet<EntityUid> GetEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = coordinates.ToMap(EntityManager);
return GetEntitiesInRange(mapPos, range, flags);
}
#endregion
#region MapCoordinates
public bool AnyEntitiesIntersecting(MapCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
if (coordinates.MapId == MapId.Nullspace) return false;
var worldAABB = new Box2(coordinates.Position - float.Epsilon, coordinates.Position + float.Epsilon);
return AnyEntitiesIntersecting(coordinates.MapId, worldAABB, flags);
}
public bool AnyEntitiesInRange(MapCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
// TODO: Actual circles
if (coordinates.MapId == MapId.Nullspace) return false;
var worldAABB = new Box2(coordinates.Position - range, coordinates.Position + range);
return AnyEntitiesIntersecting(coordinates.MapId, worldAABB, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
if (coordinates.MapId == MapId.Nullspace) return new HashSet<EntityUid>();
var worldAABB = new Box2(coordinates.Position - float.Epsilon, coordinates.Position + float.Epsilon);
return GetEntitiesIntersecting(coordinates.MapId, worldAABB, flags);
}
public HashSet<EntityUid> GetEntitiesInRange(MapCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
return GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, flags);
}
#endregion
#region MapId
public HashSet<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 worldPos, float range,
LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
// TODO: Actual circles
var worldAABB = new Box2(worldPos - range, worldPos + range);
return GetEntitiesIntersecting(mapId, worldAABB, flags);
}
#endregion
#region Grid Methods
/// <summary>
@@ -16,13 +455,13 @@ public sealed partial class EntityLookupSystem
/// <param name="gridId"></param>
/// <param name="gridIndices"></param>
/// <returns></returns>
public IEnumerable<EntityUid> GetEntitiesIntersecting(GridId gridId, IEnumerable<Vector2i> gridIndices)
public HashSet<EntityUid> GetEntitiesIntersecting(GridId gridId, IEnumerable<Vector2i> gridIndices, LookupFlags flags = DefaultFlags)
{
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return Enumerable.Empty<EntityUid>();
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookup = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
var results = new HashSet<EntityUid>();
var lookup = Comp<EntityLookupComponent>(grid.GridEntityId);
var intersecting = new HashSet<EntityUid>();
var tileSize = grid.TileSize;
// TODO: You can probably decompose the indices into larger areas if you take in a hashset instead.
@@ -32,54 +471,198 @@ public sealed partial class EntityLookupSystem
lookup.Tree._b2Tree.FastQuery(ref aabb, (ref EntityUid data) =>
{
if (EntityManager.Deleted(data)) return;
results.Add(data);
intersecting.Add(data);
});
foreach (var ent in grid.GetAnchoredEntities(index))
if ((flags & LookupFlags.Anchored) != 0x0)
{
if (EntityManager.Deleted(ent)) continue;
results.Add(ent);
foreach (var ent in grid.GetAnchoredEntities(index))
{
intersecting.Add(ent);
}
}
}
return results;
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
return intersecting;
}
public IEnumerable<EntityUid> GetEntitiesIntersecting(GridId gridId, Vector2i gridIndices)
public HashSet<EntityUid> GetEntitiesIntersecting(GridId gridId, Vector2i gridIndices, LookupFlags flags = DefaultFlags)
{
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return Enumerable.Empty<EntityUid>();
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookup = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
var lookup = Comp<EntityLookupComponent>(grid.GridEntityId);
var tileSize = grid.TileSize;
var aabb = GetLocalBounds(gridIndices, tileSize);
var results = new HashSet<EntityUid>();
var intersecting = new HashSet<EntityUid>();
lookup.Tree._b2Tree.FastQuery(ref aabb, (ref EntityUid data) =>
{
if (EntityManager.Deleted(data)) return;
results.Add(data);
intersecting.Add(data);
});
foreach (var ent in grid.GetAnchoredEntities(gridIndices))
if ((flags & LookupFlags.Anchored) != 0x0)
{
if (EntityManager.Deleted(ent)) continue;
results.Add(ent);
foreach (var ent in grid.GetAnchoredEntities(gridIndices))
{
intersecting.Add(ent);
}
}
return results;
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
return intersecting;
}
public HashSet<EntityUid> GetEntitiesIntersecting(GridId gridId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(grid.GridEntityId, intersecting, worldAABB, flags, lookupQuery, xformQuery);
if ((flags & LookupFlags.Anchored) != 0x0)
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
intersecting.Add(uid);
}
}
AddContained(intersecting, flags, xformQuery);
return intersecting;
}
public HashSet<EntityUid> GetEntitiesIntersecting(GridId gridId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(grid.GridEntityId, intersecting, worldBounds, flags, lookupQuery, xformQuery);
if ((flags & LookupFlags.Anchored) != 0x0)
{
foreach (var uid in grid.GetAnchoredEntities(worldBounds))
{
if (Deleted(uid)) continue;
intersecting.Add(uid);
}
}
AddContained(intersecting, flags, xformQuery);
return intersecting;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerable<EntityUid> GetEntitiesIntersecting(TileRef tileRef)
public IEnumerable<EntityUid> GetEntitiesIntersecting(TileRef tileRef, LookupFlags flags = DefaultFlags)
{
return GetEntitiesIntersecting(tileRef.GridIndex, tileRef.GridIndices);
return GetEntitiesIntersecting(tileRef.GridIndex, tileRef.GridIndices, flags);
}
#endregion
#region Lookup Query
public HashSet<EntityUid> GetEntitiesIntersecting(EntityLookupComponent component, ref Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet<EntityUid>();
var xformQuery = GetEntityQuery<TransformComponent>();
var localAABB = xformQuery.GetComponent(component.Owner).InvWorldMatrix.TransformBox(worldAABB);
foreach (var uid in component.Tree.QueryAabb(localAABB))
{
intersecting.Add(uid);
}
if ((flags & LookupFlags.Anchored) != 0x0 && _mapManager.IsGrid(component.Owner))
{
foreach (var uid in _mapManager.GetGrid(component.Owner).GetLocalAnchoredEntities(localAABB))
{
intersecting.Add(uid);
}
}
AddContained(intersecting, flags, xformQuery);
return intersecting;
}
public HashSet<EntityUid> GetLocalEntitiesIntersecting(EntityLookupComponent component, ref Box2 localAABB, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet<EntityUid>();
foreach (var uid in component.Tree.QueryAabb(localAABB))
{
intersecting.Add(uid);
}
if ((flags & LookupFlags.Anchored) != 0x0 && _mapManager.IsGrid(component.Owner))
{
foreach (var uid in _mapManager.GetGrid(component.Owner).GetLocalAnchoredEntities(localAABB))
{
intersecting.Add(uid);
}
}
AddContained(intersecting, flags, GetEntityQuery<TransformComponent>());
return intersecting;
}
#endregion
#region Lookups
/// <summary>
/// Gets the relevant <see cref="EntityLookupComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<EntityLookupComponent> FindLookupsIntersecting(MapId mapId, Box2 worldAABB)
{
if (mapId == MapId.Nullspace) yield break;
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return lookupQuery.GetComponent(grid.GridEntityId);
}
}
/// <summary>
/// Gets the relevant <see cref="EntityLookupComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<EntityLookupComponent> FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds)
{
if (mapId == MapId.Nullspace) yield break;
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
// Copy-paste with above but the query may differ slightly internally.
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
yield return lookupQuery.GetComponent(grid.GridEntityId);
}
}
#endregion
#region Bounds
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Box2 GetLocalBounds(Vector2i gridIndices, ushort tileSize)
{
@@ -98,7 +681,7 @@ public sealed partial class EntityLookupSystem
if (worldMatrix == null || angle == null)
{
var gridXform = EntityManager.GetComponent<TransformComponent>(grid.GridEntityId);
var gridXform = Transform(grid.GridEntityId);
var (_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix();
worldMatrix = wMat;
angle = wAng;
@@ -109,4 +692,6 @@ public sealed partial class EntityLookupSystem
return new Box2Rotated(translatedBox, -angle.Value, center);
}
#endregion
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
@@ -20,16 +16,35 @@ namespace Robust.Shared.GameObjects
public enum LookupFlags : byte
{
None = 0,
/// <summary>
/// Should we use the approximately intersecting entities or check tighter bounds.
/// </summary>
Approximate = 1 << 0,
IncludeAnchored = 1 << 1,
/// <summary>
/// Also return entities from an anchoring query.
/// </summary>
Anchored = 1 << 1,
/// <summary>
/// Include entities that are currently in containers.
/// </summary>
Contained = 1 << 2,
// IncludeGrids = 1 << 2,
// IncludePhysics (whenever it gets split off)
// Include maps
}
public sealed partial class EntityLookupSystem : EntitySystem
{
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
[IoC.Dependency] private readonly SharedContainerSystem _container = default!;
[IoC.Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
/// <summary>
/// Returns all non-grid entities. Consider using your own flags if you wish for a faster query.
/// </summary>
public const LookupFlags DefaultFlags = LookupFlags.Contained | LookupFlags.Anchored;
private const int GrowthRate = 256;
@@ -51,6 +66,8 @@ namespace Robust.Shared.GameObjects
SubscribeLocalEvent<RotateEvent>(OnRotate);
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
SubscribeLocalEvent<AnchorStateChangedEvent>(OnAnchored);
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(OnContainerInsert);
SubscribeLocalEvent<EntRemovedFromContainerMessage>(OnContainerRemove);
SubscribeLocalEvent<EntityLookupComponent, ComponentAdd>(OnLookupAdd);
SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(OnLookupShutdown);
@@ -77,7 +94,7 @@ namespace Robust.Shared.GameObjects
if (_container.IsEntityInContainer(uid, meta))
return;
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.Resolve(uid, ref xform) || xform.Anchored)
return;
@@ -86,13 +103,13 @@ namespace Robust.Shared.GameObjects
if (lookup == null) return;
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery);
// If we're contained then LocalRotation should be 0 anyway.
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform, xformQuery) - lookupRotation, xform, xformQuery);
// TODO: Only container children need updating so could manually do this slightly better.
AddToEntityTree(lookup, xform, aabb, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery, lookupRotation);
}
private void OnAnchored(ref AnchorStateChangedEvent args)
@@ -105,20 +122,20 @@ namespace Robust.Shared.GameObjects
}
else if (EntityManager.TryGetComponent(args.Entity, out MetaDataComponent? meta) && meta.EntityLifeStage < EntityLifeStage.Terminating)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(args.Entity);
var lookup = GetLookup(args.Entity, xform, xformQuery);
if (lookup == null)
throw new InvalidOperationException();
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery);
DebugTools.Assert(coordinates.EntityId == lookup.Owner);
// If we're contained then LocalRotation should be 0 anyway.
var aabb = GetAABB(args.Entity, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery);
var aabb = GetAABB(args.Entity, coordinates.Position, _transform.GetWorldRotation(xform, xformQuery) - lookupRotation, xform, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery, lookupRotation);
}
// else -> the entity is terminating. We can ignore this un-anchor event, as this entity will be removed by the tree via OnEntityDeleted.
}
@@ -169,7 +186,7 @@ namespace Robust.Shared.GameObjects
{
// TODO: Should feed in AABB to lookup so it's not enlarged unnecessarily
var aabb = GetWorldAABB(entity);
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var tree = GetLookup(entity, xformQuery);
if (tree == null)
@@ -189,11 +206,14 @@ namespace Robust.Shared.GameObjects
private void OnEntityInit(object? sender, EntityUid uid)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
if (_container.IsEntityInContainer(uid)) return;
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.Anchored ||
_mapManager.IsMap(uid) ||
xform.Anchored) return;
if (_mapManager.IsMap(uid) ||
_mapManager.IsGrid(uid)) return;
var lookup = GetLookup(uid, xform, xformQuery);
@@ -201,15 +221,15 @@ namespace Robust.Shared.GameObjects
// If nullspace or the likes.
if (lookup == null) return;
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
DebugTools.Assert(coordinates.EntityId == lookup.Owner);
var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery);
// If we're contained then LocalRotation should be 0 anyway.
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform, xformQuery) - lookupRotation, xform, xformQuery);
// Any child entities should be handled by their own OnEntityInit
AddToEntityTree(lookup, xform, aabb, xformQuery, false);
AddToEntityTree(lookup, xform, aabb, xformQuery, lookupRotation, false);
}
private void OnMove(ref MoveEvent args)
@@ -227,15 +247,15 @@ namespace Robust.Shared.GameObjects
// Even if the entity is contained it may have children that aren't so we still need to update.
if (!CanMoveUpdate(uid)) return;
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var lookup = GetLookup(uid, xform, xformQuery);
if (lookup == null) return;
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xformQuery.GetComponent(uid), xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery);
var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery);
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform) - lookupRotation, xform, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery, lookupRotation);
}
private bool CanMoveUpdate(EntityUid uid)
@@ -247,13 +267,17 @@ namespace Robust.Shared.GameObjects
private void OnParentChange(ref EntParentChangedMessage args)
{
if (_mapManager.IsMap(args.Entity) ||
_mapManager.IsGrid(args.Entity) ||
EntityManager.GetComponent<MetaDataComponent>(args.Entity).EntityLifeStage < EntityLifeStage.Initialized) return;
var meta = MetaData(args.Entity);
EntityLookupComponent? oldLookup = null;
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
// Parent change gets raised after container insert so we'll just drop it and let OnContainerInsert handle.
if (meta.EntityLifeStage < EntityLifeStage.Initialized ||
_container.IsEntityInContainer(args.Entity, meta) ||
_mapManager.IsGrid(args.Entity) ||
_mapManager.IsMap(args.Entity)) return;
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(args.Entity);
EntityLookupComponent? oldLookup = null;
if (args.OldParent != null)
{
@@ -268,21 +292,41 @@ namespace Robust.Shared.GameObjects
RemoveFromEntityTree(oldLookup, xform, xformQuery);
if (newLookup != null)
AddToEntityTree(newLookup, xform, xformQuery);
AddToEntityTree(newLookup, xform, xformQuery, _transform.GetWorldRotation(newLookup.Owner, xformQuery));
}
private void OnContainerRemove(EntRemovedFromContainerMessage ev)
{
// This gets handled before parent change so that should just early out from lookups matching.
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(ev.Entity);
var lookup = GetLookup(ev.Entity, xform, xformQuery);
if (lookup == null) return;
AddToEntityTree(lookup, xform, xformQuery, _transform.GetWorldRotation(lookup.Owner, xformQuery));
}
private void OnContainerInsert(EntInsertedIntoContainerMessage ev)
{
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(ev.Entity);
var lookup = GetLookup(ev.Entity, xform, xformQuery);
RemoveFromEntityTree(lookup, xform, xformQuery);
}
private void AddToEntityTree(
EntityLookupComponent lookup,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery,
bool recursive = true,
bool contained = false)
Angle lookupRotation,
bool recursive = true)
{
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
// If we're contained then LocalRotation should be 0 anyway.
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery, recursive, contained);
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform, xformQuery) - lookupRotation, xform, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery, lookupRotation, recursive);
}
private void AddToEntityTree(
@@ -290,8 +334,8 @@ namespace Robust.Shared.GameObjects
TransformComponent xform,
Box2 aabb,
EntityQuery<TransformComponent> xformQuery,
bool recursive = true,
bool contained = false)
Angle lookupRotation,
bool recursive = true)
{
// If entity is in nullspace then no point keeping track of data structure.
if (lookup == null) return;
@@ -303,54 +347,37 @@ namespace Robust.Shared.GameObjects
if (xform.ChildCount == 0 || !recursive) return;
// TODO: Pass this down instead son.
var lookupXform = xformQuery.GetComponent(lookup.Owner);
// TODO: Just don't store contained stuff, it's way too expensive for updates and makes the tree much bigger.
// If they're in a container then don't add to entitylookup due to the additional cost.
// It's cheaper to just query these components at runtime given PVS no longer uses EntityLookupSystem.
if (EntityManager.TryGetComponent<ContainerManagerComponent>(xform.Owner, out var conManager))
{
while (childEnumerator.MoveNext(out var child))
{
if (conManager.ContainsEntity(child.Value)) continue;
// Recursively update children.
if (contained)
{
// Just re-use the topmost AABB.
while (childEnumerator.MoveNext(out var child))
{
AddToEntityTree(lookup, xformQuery.GetComponent(child.Value), aabb, xformQuery, contained: true);
}
}
// If they're in a container then it just uses the parent's AABB.
else if (EntityManager.TryGetComponent<ContainerManagerComponent>(xform.Owner, out var conManager))
{
while (childEnumerator.MoveNext(out var child))
{
if (conManager.ContainsEntity(child.Value))
{
AddToEntityTree(lookup, xformQuery.GetComponent(child.Value), aabb, xformQuery, contained: true);
}
else
{
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var childXform = xformQuery.GetComponent(child.Value);
// TODO: If we have 0 position and not contained can optimise these further, but future problem.
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupXform.WorldRotation);
AddToEntityTree(lookup, childXform, childAABB, xformQuery);
}
var childXform = xformQuery.GetComponent(child.Value);
var coordinates = _transform.GetMoverCoordinates(childXform.Coordinates, xformQuery);
// TODO: If we have 0 position and not contained can optimise these further, but future problem.
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupRotation);
AddToEntityTree(lookup, childXform, childAABB, xformQuery, lookupRotation);
}
}
else
{
while (childEnumerator.MoveNext(out var child))
{
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var childXform = xformQuery.GetComponent(child.Value);
var coordinates = _transform.GetMoverCoordinates(childXform.Coordinates, xformQuery);
// TODO: If we have 0 position and not contained can optimise these further, but future problem.
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupXform.WorldRotation);
AddToEntityTree(lookup, childXform, childAABB, xformQuery);
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupRotation);
AddToEntityTree(lookup, childXform, childAABB, xformQuery, lookupRotation);
}
}
}
private void RemoveFromEntityTree(EntityUid uid, bool recursive = true)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
var lookup = GetLookup(uid, xform, xformQuery);
RemoveFromEntityTree(lookup, xform, xformQuery, recursive);
@@ -390,7 +417,7 @@ namespace Robust.Shared.GameObjects
return null;
var parent = xform.ParentUid;
var lookupQuery = EntityManager.GetEntityQuery<EntityLookupComponent>();
var lookupQuery = GetEntityQuery<EntityLookupComponent>();
// If we're querying a map / grid just return it directly.
if (lookupQuery.TryGetComponent(uid, out var lookup))
@@ -412,7 +439,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Get the AABB of an entity with the supplied position and angle. Tries to consider if the entity is in a container.
/// </summary>
private Box2 GetAABB(EntityUid uid, Vector2 position, Angle angle, TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
internal Box2 GetAABB(EntityUid uid, Vector2 position, Angle angle, TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
{
// If we're in a container then we just use the container's bounds.
if (_container.TryGetOuterContainer(uid, xform, out var container, xformQuery))
@@ -446,7 +473,7 @@ namespace Robust.Shared.GameObjects
public Box2 GetWorldAABB(EntityUid uid, TransformComponent? xform = null)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
xform ??= xformQuery.GetComponent(uid);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();

View File

@@ -30,16 +30,15 @@ public abstract partial class SharedPhysicsSystem
if (component.CanCollide)
{
if (component.Awake)
{
EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsWakeMessage(component));
}
if (!_containerSystem.IsEntityInContainer(uid))
{
// TODO: Probably a bad idea but ehh future sloth's problem; namely that we have to duplicate code between here and CanCollide.
EntityManager.EventBus.RaiseLocalEvent(uid, new CollisionChangeMessage(component, uid, component._canCollide));
}
else
{
component._canCollide = false;
}
}
else
{

View File

@@ -56,79 +56,71 @@ namespace Robust.Shared.Localization
string? suffix = null;
Dictionary<string, string>? attributes = null;
while (true)
_prototype.TryIndex<EntityPrototype>(prototypeId, out var prototype);
var locId = prototype?.CustomLocalizationID ?? $"ent-{prototypeId}";
if (TryGetMessage(locId, out var bundle, out var msg))
{
var prototype = _prototype.Index<EntityPrototype>(prototypeId);
var locId = prototype.CustomLocalizationID ?? $"ent-{prototypeId}";
// Localization override exists.
var msgAttrs = msg.Attributes;
if (TryGetMessage(locId, out var bundle, out var msg))
if (name == null && msg.Value != null)
{
// Localization override exists.
var msgAttrs = msg.Attributes;
if (name == null && msg.Value != null)
{
// Only set name if the value isn't empty.
// So that you can override *only* a desc/suffix.
name = bundle.FormatPattern(msg.Value, null, out var fmtErr);
WriteWarningForErrs(fmtErr, locId);
}
if (msgAttrs.Count != 0)
{
foreach (var (attrId, pattern) in msg.Attributes)
{
var attrib = attrId.ToString();
if (attrib.Equals("desc")
|| attrib.Equals("suffix"))
continue;
attributes ??= new Dictionary<string, string>();
if (!attributes.ContainsKey(attrib))
{
var value = bundle.FormatPattern(pattern, null, out var errors);
WriteWarningForErrs(errors, locId);
attributes[attrib] = value;
}
}
var allErrors = new List<FluentError>();
if (desc == null
&& bundle.TryGetMsg(locId, "desc", null, out var err1, out desc))
{
allErrors.AddRange(err1);
}
if (suffix == null
&& bundle.TryGetMsg(locId, "suffix", null, out var err, out suffix))
{
allErrors.AddRange(err);
}
WriteWarningForErrs(allErrors, locId);
}
// Only set name if the value isn't empty.
// So that you can override *only* a desc/suffix.
name = bundle.FormatPattern(msg.Value, null, out var fmtErr);
WriteWarningForErrs(fmtErr, locId);
}
name ??= prototype.SetName;
desc ??= prototype.SetDesc;
suffix ??= prototype.SetSuffix;
if (prototype.LocProperties.Count != 0)
if (msgAttrs.Count != 0)
{
foreach (var (attrib, value) in prototype.LocProperties)
foreach (var (attrId, pattern) in msg.Attributes)
{
var attrib = attrId.ToString();
if (attrib.Equals("desc")
|| attrib.Equals("suffix"))
continue;
attributes ??= new Dictionary<string, string>();
if (!attributes.ContainsKey(attrib))
{
var value = bundle.FormatPattern(pattern, null, out var errors);
WriteWarningForErrs(errors, locId);
attributes[attrib] = value;
}
}
var allErrors = new List<FluentError>();
if (desc == null
&& bundle.TryGetMsg(locId, "desc", null, out var err1, out desc))
{
allErrors.AddRange(err1);
}
if (suffix == null
&& bundle.TryGetMsg(locId, "suffix", null, out var err, out suffix))
{
allErrors.AddRange(err);
}
WriteWarningForErrs(allErrors, locId);
}
}
if (prototype.Parent == null)
break;
name ??= prototype?.SetName;
desc ??= prototype?.SetDesc;
suffix ??= prototype?.SetSuffix;
prototypeId = prototype.Parent;
if (prototype?.LocProperties != null && prototype.LocProperties.Count != 0)
{
foreach (var (attrib, value) in prototype.LocProperties)
{
attributes ??= new Dictionary<string, string>();
if (!attributes.ContainsKey(attrib))
{
attributes[attrib] = value;
}
}
}
return new EntityLocData(

View File

@@ -127,6 +127,7 @@ namespace Robust.Shared.Map
#region SnapGridAccess
IEnumerable<EntityUid> GetLocalAnchoredEntities(Box2 localAABB);
IEnumerable<EntityUid> GetAnchoredEntities(MapCoordinates coords);
IEnumerable<EntityUid> GetAnchoredEntities(EntityCoordinates coords);
IEnumerable<EntityUid> GetAnchoredEntities(Vector2i pos);

View File

@@ -531,6 +531,17 @@ namespace Robust.Shared.Map
new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
}
public IEnumerable<EntityUid> GetLocalAnchoredEntities(Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(localAABB, true, null))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Box2 worldAABB)
{

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Lidgren.Network;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Network.Messages
@@ -67,7 +68,7 @@ namespace Robust.Shared.Network.Messages
value = buffer.ReadDouble();
break;
default:
throw new ArgumentOutOfRangeException();
throw new ArgumentOutOfRangeException(nameof(value), valType, $"CVar {name} is not of a valid CVar type!");
}
NetworkedVars.Add((name, value));
@@ -117,7 +118,7 @@ namespace Robust.Shared.Network.Messages
buffer.Write(val);
break;
default:
throw new ArgumentOutOfRangeException();
throw new ArgumentOutOfRangeException(nameof(value), value.GetType(), $"CVar {name} is not of a valid CVar type!");
}
}
}

View File

@@ -22,6 +22,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -98,11 +99,6 @@ namespace Robust.Shared.Physics.Dynamics
/// </summary>
private HashSet<PhysicsComponent> _islandSet = new();
private HashSet<PhysicsComponent> _queuedWake = new();
private HashSet<PhysicsComponent> _queuedSleep = new();
private Queue<CollisionChangeMessage> _queuedCollisionMessages = new();
private List<PhysicsComponent> _islandBodies = new(64);
private List<Contact> _islandContacts = new(32);
private List<Joint> _islandJoints = new(8);
@@ -127,10 +123,8 @@ namespace Robust.Shared.Physics.Dynamics
if (Bodies.Contains(body)) return;
// TODO: Kinda dodgy with this and wake shit.
// Look at my note under ProcessWakeQueue
if (body.Awake && body.BodyType != BodyType.Static)
{
_queuedWake.Remove(body);
AwakeBodies.Add(body);
}
@@ -140,7 +134,20 @@ namespace Robust.Shared.Physics.Dynamics
public void AddAwakeBody(PhysicsComponent body)
{
_queuedWake.Add(body);
if (body.BodyType == BodyType.Static)
{
Logger.ErrorS("physics", $"Tried to add static body {_entityManager.ToPrettyString(body.Owner)} as an awake body to map!");
return;
}
DebugTools.Assert(Bodies.Contains(body));
if (!Bodies.Contains(body))
{
Logger.ErrorS("physics", $"Tried to add {_entityManager.ToPrettyString(body.Owner)} as an awake body to map when it's not contained on the map!");
return;
}
AwakeBodies.Add(body);
}
public void RemoveBody(PhysicsComponent body)
@@ -153,63 +160,11 @@ namespace Robust.Shared.Physics.Dynamics
public void RemoveSleepBody(PhysicsComponent body)
{
_queuedSleep.Add(body);
AwakeBodies.Remove(body);
}
#endregion
#region Queue
private void ProcessChanges()
{
ProcessBodyChanges();
ProcessWakeQueue();
ProcessSleepQueue();
}
private void ProcessBodyChanges()
{
while (_queuedCollisionMessages.Count > 0)
{
var message = _queuedCollisionMessages.Dequeue();
if (!message.Body.Deleted && message.Body.CanCollide)
{
AddBody(message.Body);
}
else
{
RemoveBody(message.Body);
}
}
}
private void ProcessWakeQueue()
{
foreach (var body in _queuedWake)
{
// Sloth note: So FPE doesn't seem to handle static bodies being woken gracefully as they never sleep
// (No static body's an island so can't increase their min sleep time).
// AFAIK not adding it to woken bodies shouldn't matter for anything tm...
if (!body.Awake || body.BodyType == BodyType.Static || !Bodies.Contains(body)) continue;
AwakeBodies.Add(body);
}
_queuedWake.Clear();
}
private void ProcessSleepQueue()
{
foreach (var body in _queuedSleep)
{
if (body.Awake) continue;
AwakeBodies.Remove(body);
}
_queuedSleep.Clear();
}
#endregion
/// <summary>
/// Where the magic happens.
/// </summary>
@@ -217,10 +172,6 @@ namespace Robust.Shared.Physics.Dynamics
/// <param name="prediction"></param>
public void Step(float frameTime, bool prediction)
{
// The original doesn't call ProcessChanges quite so much but stuff like collision behaviors
// can edit things during the solver so we'll just handle it as it comes up.
ProcessChanges();
// Box2D does this at the end of a step and also here when there's a fixture update.
// Given external stuff can move bodies we'll just do this here.
// Unfortunately this NEEDS to be predicted to make pushing remotely fucking good.
@@ -237,9 +188,6 @@ namespace Robust.Shared.Physics.Dynamics
if (!prediction)
ContactManager.PreSolve(frameTime);
// Remove all deleted entities etc.
ProcessChanges();
// Integrate velocities, solve velocity constraints, and do integration.
Solve(frameTime, dtRatio, invDt, prediction);

View File

@@ -10,6 +10,9 @@ using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.ViewVariables;
@@ -31,7 +34,6 @@ namespace Robust.Shared.Prototypes
private const int DEFAULT_RANGE = 200;
[NeverPushInheritance]
[DataField("loc")]
private Dictionary<string, string>? _locPropertiesSet;
@@ -39,7 +41,7 @@ namespace Robust.Shared.Prototypes
/// The "in code name" of the object. Must be unique.
/// </summary>
[ViewVariables]
[DataField("id")]
[IdDataFieldAttribute]
public string ID { get; private set; } = default!;
/// <summary>
@@ -48,17 +50,14 @@ namespace Robust.Shared.Prototypes
/// </summary>
/// <seealso cref="Name"/>
[ViewVariables]
[NeverPushInheritance]
[DataField("name")]
public string? SetName { get; private set; }
[ViewVariables]
[NeverPushInheritance]
[DataField("description")]
public string? SetDesc { get; private set; }
[ViewVariables]
[NeverPushInheritance]
[DataField("suffix")]
public string? SetSuffix { get; private set; }
@@ -89,7 +88,6 @@ namespace Robust.Shared.Prototypes
/// </summary>
[ViewVariables]
[DataField("localizationId")]
[NeverPushInheritance]
public string? CustomLocalizationID { get; private set; }
@@ -98,8 +96,8 @@ namespace Robust.Shared.Prototypes
/// </summary>
[ViewVariables]
[NeverPushInheritance]
[DataField("abstract")]
public bool Abstract { get; private set; }
[DataField("noSpawn")]
public bool NoSpawn { get; private set; }
[DataField("placement")] private EntityPlacementProperties PlacementProperties = new();
@@ -138,9 +136,14 @@ namespace Robust.Shared.Prototypes
/// The prototype we inherit from.
/// </summary>
[ViewVariables]
[DataField("parent", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
[ParentDataFieldAttribute(typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? Parent { get; private set; }
[ViewVariables]
[NeverPushInheritance]
[AbstractDataField]
public bool Abstract { get; }
/// <summary>
/// A dictionary mapping the component type list to the YAML mapping containing their settings.
/// </summary>
@@ -233,6 +236,7 @@ namespace Robust.Shared.Prototypes
EntityPrototype? prototype,
EntityUid entity,
IComponentFactory factory,
IPrototypeManager prototypeManager,
IEntityManager entityManager,
ISerializationManager serManager,
IEntityLoadContext? context) //yeah officer this method right here
@@ -245,12 +249,22 @@ namespace Robust.Shared.Prototypes
if (prototype != null)
{
foreach (var (name, data) in prototype.Components)
prototypeManager.TryGetMapping(typeof(EntityPrototype), prototype.ID, out var prototypeData);
foreach (var (name, _) in prototype.Components)
{
var fullData = data;
MappingDataNode? fullData = null;
if (prototypeData != null && prototypeData.TryGet<SequenceDataNode>("components", out var compList))
{
fullData = compList.Cast<MappingDataNode>().FirstOrDefault(x =>
x.TryGet<ValueDataNode>("type", out var typeNode) && typeNode.Value == name);
}
fullData ??= new MappingDataNode();
if (context != null)
{
fullData = context.GetComponentData(name, data);
fullData = context.GetComponentData(name, fullData);
}
EnsureCompExistsAndDeserialize(entity, factory, entityManager, serManager, name, fullData, context as ISerializationContext);
@@ -281,7 +295,7 @@ namespace Robust.Shared.Prototypes
IEntityManager entityManager,
ISerializationManager serManager,
string compName,
IComponent data, ISerializationContext? context)
MappingDataNode data, ISerializationContext? context)
{
var compType = factory.GetRegistration(compName).Type;
@@ -294,7 +308,7 @@ namespace Robust.Shared.Prototypes
}
// TODO use this value to support struct components
_ = serManager.Copy(data, component, context);
serManager.Read(compType, data, context, value: component);
}
public override string ToString()

View File

@@ -1,4 +1,10 @@
using Robust.Shared.ViewVariables;
using System;
using JetBrains.Annotations;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.ViewVariables;
using YamlDotNet.Core.Tokens;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Prototypes
@@ -25,4 +31,31 @@ namespace Robust.Shared.Prototypes
bool Abstract { get; }
}
public sealed class IdDataFieldAttribute : DataFieldAttribute
{
public const string Name = "id";
public IdDataFieldAttribute(int priority = 1, Type? customTypeSerializer = null) :
base(Name, false, priority, true, false, customTypeSerializer)
{
}
}
public sealed class ParentDataFieldAttribute : DataFieldAttribute
{
public const string Name = "parent";
public ParentDataFieldAttribute(Type prototypeIdSerializer, int priority = 1) :
base(Name, false, priority, false, false, prototypeIdSerializer)
{
}
}
public sealed class AbstractDataFieldAttribute : DataFieldAttribute
{
public const string Name = "abstract";
public AbstractDataFieldAttribute(int priority = 1) :
base(Name, false, priority, false, false, null)
{
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using JetBrains.Annotations;
using Prometheus;
using Robust.Shared.Asynchronous;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
@@ -15,10 +16,10 @@ using Robust.Shared.Log;
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.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
@@ -79,6 +80,9 @@ namespace Robust.Shared.Prototypes
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
bool TryIndex(Type type, string id, [NotNullWhen(true)] out IPrototype? prototype);
bool HasMapping<T>(string id);
bool TryGetMapping(Type type, string id, [NotNullWhen(true)] out MappingDataNode? mappings);
/// <summary>
/// Returns whether a prototype variant <param name="variant"/> exists.
/// </summary>
@@ -131,13 +135,13 @@ namespace Robust.Shared.Prototypes
/// <summary>
/// Load prototypes from files in a directory, recursively.
/// </summary>
List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false);
void LoadDirectory(ResourcePath path, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null);
Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path);
List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false);
void LoadFromStream(TextReader stream, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null);
List<IPrototype> LoadString(string str, bool overwrite = false);
void LoadString(string str, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null);
void RemoveString(string prototypes);
@@ -149,7 +153,7 @@ namespace Robust.Shared.Prototypes
/// <summary>
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
/// </summary>
void Resync();
void ResolveResults();
/// <summary>
/// Registers a specific prototype name to be ignored.
@@ -213,7 +217,7 @@ 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, Dictionary<string, MappingDataNode>> _prototypeResults = new();
private readonly Dictionary<Type, PrototypeInheritanceTree> _inheritanceTrees = new();
private readonly HashSet<string> _ignoredPrototypeTypes = new();
@@ -302,63 +306,62 @@ namespace Robust.Shared.Prototypes
protected void ReloadPrototypes(IEnumerable<ResourcePath> filePaths)
{
#if !FULL_RELEASE
var changed = filePaths.SelectMany(f => LoadFile(f.ToRootedPath(), true)).ToList();
var changed = new Dictionary<Type, HashSet<string>>();
foreach (var filePath in filePaths)
{
LoadFile(filePath.ToRootedPath(), true, changed);
}
ReloadPrototypes(changed);
#endif
}
internal void ReloadPrototypes(List<IPrototype> prototypes)
internal void ReloadPrototypes(Dictionary<Type, HashSet<string>> prototypes)
{
#if !FULL_RELEASE
prototypes.Sort((a, b) => SortPrototypesByPriority(a.GetType(), b.GetType()));
var prototypeTypeOrder = prototypes.Keys.ToList();
prototypeTypeOrder.Sort(SortPrototypesByPriority);
var pushed = new Dictionary<Type, HashSet<string>>();
foreach (var prototype in prototypes)
foreach (var type in prototypeTypeOrder)
{
if (prototype is not IInheritingPrototype inheritingPrototype) continue;
var type = prototype.GetType();
if (!pushed.ContainsKey(type)) pushed[type] = new HashSet<string>();
var baseNode = prototype.ID;
if (pushed[type].Contains(baseNode))
if (!type.IsAssignableTo(typeof(IInheritingPrototype)))
{
continue;
}
var tree = _inheritanceTrees[type];
var currentNode = inheritingPrototype.Parent;
if (currentNode == null)
{
PushInheritance(type, baseNode, null, pushed[type]);
continue;
}
while (true)
{
var parent = tree.GetParent(currentNode);
if (parent == null)
foreach (var id in prototypes[type])
{
break;
_prototypes[type][id] = (IPrototype) _serializationManager.Read(type, _prototypeResults[type][id])!;
}
continue;
}
foreach (var id in prototypes[type])
{
if (!pushed.ContainsKey(type))
pushed[type] = new HashSet<string>();
if (pushed[type].Contains(id))
{
continue;
}
baseNode = currentNode;
currentNode = parent;
var set = new HashSet<string>();
set.Add(id);
PushInheritance(type, id, _inheritanceTrees[type].GetParent(id), set);
foreach (var changedId in set)
{
TryReadPrototype(type, changedId, _prototypeResults[type][changedId]);
}
pushed[type].UnionWith(set);
}
PushInheritance(type, currentNode, baseNode, null, pushed[type]);
}
//todo paul i hate it but i am not opening that can of worms in this refactor
PrototypesReloaded?.Invoke(
new PrototypesReloadedEventArgs(
prototypes
.GroupBy(p => p.GetType())
.ToDictionary(
g => g.Key,
g => new PrototypesReloadedEventArgs.PrototypeChangeSet(
g.ToDictionary(a => a.ID, a => a)))));
g.Value.Where(x => _prototypes[g.Key].ContainsKey(x)).ToDictionary(a => a, a => _prototypes[g.Key][a])))));
// TODO filter by entity prototypes changed
if (!pushed.ContainsKey(typeof(EntityPrototype))) return;
@@ -379,90 +382,77 @@ namespace Robust.Shared.Prototypes
#endif
}
public void Resync()
/// <summary>
/// Resolves the mappings stored in memory to actual prototypeinstances.
/// </summary>
public void ResolveResults()
{
var trees = _inheritanceTrees.Keys.ToList();
trees.Sort(SortPrototypesByPriority);
foreach (var type in trees)
var types = _prototypeResults.Keys.ToList();
types.Sort(SortPrototypesByPriority);
foreach (var type in types)
{
var tree = _inheritanceTrees[type];
foreach (var baseNode in tree.BaseNodes)
{
PushInheritance(type, baseNode, null, new HashSet<string>());
}
// Go over all prototypes and double check that their parent actually exists.
var typePrototypes = _prototypes[type];
foreach (var (id, proto) in typePrototypes)
{
var iProto = (IInheritingPrototype) proto;
var parent = iProto.Parent;
if (parent != null && !typePrototypes.ContainsKey(parent!))
if(_inheritanceTrees.TryGetValue(type, out var tree)){
foreach (var baseNode in tree.BaseNodes)
{
Logger.ErrorS("Serv3", $"{iProto.GetType().Name} '{id}' has invalid parent: {parent}");
PushInheritance(type, baseNode);
}
}
foreach (var (id, mapping) in _prototypeResults[type])
{
TryReadPrototype(type, id, mapping);
}
}
}
public void PushInheritance(Type type, string id, string child, DeserializationResult? baseResult,
HashSet<string> changed)
private void TryReadPrototype(Type type, string id, MappingDataNode mapping)
{
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))
{
PushInheritance(type, childID, newResult, changed);
}
if (newResult.RawValue is not IInheritingPrototype inheritingPrototype)
{
Logger.ErrorS("Serv3", $"PushInheritance was called on non-inheriting prototype! ({type}, {id})");
if(mapping.TryGet<ValueDataNode>(AbstractDataFieldAttribute.Name, out var abstractNode) && abstractNode.AsBool())
return;
try
{
_prototypes[type][id] = (IPrototype) _serializationManager.Read(type, mapping)!;
}
catch (Exception e)
{
Logger.ErrorS("PROTO", $"Reading {type}({id}) threw the following exception: {e}");
}
}
private void PushInheritance(Type type, string id, string? parent = null, HashSet<string>? changed = null)
{
if (parent != null)
{
PushInheritanceWithoutRecursion(type, id, parent, changed);
}
if (!inheritingPrototype.Abstract)
newResult.CallAfterDeserializationHook();
var populatedRes =
_serializationManager.PopulateDataDefinition(_prototypes[type][id], (IDeserializedDefinition) newResult);
_prototypes[type][id] = (IPrototype) populatedRes.RawValue!;
if(!_inheritanceTrees[type].HasId(id)) return;
foreach (var child in _inheritanceTrees[type].Children(id))
{
PushInheritance(type, child, id, changed);
}
}
private void PushInheritanceWithoutRecursion(Type type, string id, string parent,
HashSet<string>? changed = null)
{
_prototypeResults[type][id] = _serializationManager.PushCompositionWithGenericNode(type,
new[] { _prototypeResults[type][parent] }, _prototypeResults[type][id]);
changed?.Add(id);
}
/// <inheritdoc />
public List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false)
public void LoadDirectory(ResourcePath path, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null)
{
var changedPrototypes = new List<IPrototype>();
_hasEverBeenReloaded = true;
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
foreach (var resourcePath in streams)
{
var filePrototypes = LoadFile(resourcePath, overwrite);
changedPrototypes.AddRange(filePrototypes);
LoadFile(resourcePath, overwrite, changed);
}
return changedPrototypes;
}
public Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path)
@@ -545,17 +535,15 @@ namespace Robust.Shared.Prototypes
}
}
public HashSet<IPrototype> LoadFile(ResourcePath file, bool overwrite = false)
public void LoadFile(ResourcePath file, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null)
{
var changedPrototypes = new HashSet<IPrototype>();
try
{
using var reader = ReadFile(file, !overwrite);
if (reader == null)
{
return changedPrototypes;
return;
}
var yamlStream = new YamlStream();
@@ -567,8 +555,7 @@ namespace Robust.Shared.Prototypes
{
try
{
var documentPrototypes = LoadFromDocument(yamlStream.Documents[i], overwrite);
changedPrototypes.UnionWith(documentPrototypes);
LoadFromDocument(yamlStream.Documents[i], overwrite, changed);
}
catch (Exception e)
{
@@ -581,13 +568,10 @@ namespace Robust.Shared.Prototypes
var sawmill = Logger.GetSawmill("eng");
sawmill.Error("YamlException whilst loading prototypes from {0}: {1}", file, e.Message);
}
return changedPrototypes;
}
public List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false)
public void LoadFromStream(TextReader stream, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null)
{
var changedPrototypes = new List<IPrototype>();
_hasEverBeenReloaded = true;
var yaml = new YamlStream();
yaml.Load(stream);
@@ -596,8 +580,7 @@ namespace Robust.Shared.Prototypes
{
try
{
var documentPrototypes = LoadFromDocument(yaml.Documents[i], overwrite);
changedPrototypes.AddRange(documentPrototypes);
LoadFromDocument(yaml.Documents[i], overwrite, changed);
}
catch (Exception e)
{
@@ -606,13 +589,11 @@ namespace Robust.Shared.Prototypes
}
LoadedData?.Invoke(yaml, "anonymous prototypes YAML stream");
return changedPrototypes;
}
public List<IPrototype> LoadString(string str, bool overwrite = false)
public void LoadString(string str, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null)
{
return LoadFromStream(new StringReader(str), overwrite);
LoadFromStream(new StringReader(str), overwrite, changed);
}
public void RemoveString(string prototypes)
@@ -653,14 +634,14 @@ namespace Robust.Shared.Prototypes
}
}
private HashSet<IPrototype> LoadFromDocument(YamlDocument document, bool overwrite = false)
private void LoadFromDocument(YamlDocument document, bool overwrite = false, Dictionary<Type, HashSet<string>>? changed = null)
{
var changedPrototypes = new HashSet<IPrototype>();
var rootNode = (YamlSequenceNode) document.RootNode;
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
foreach (var node in rootNode.Cast<YamlMappingNode>())
{
var type = node.GetNode("type").AsString();
var datanode = node.ToDataNodeCast<MappingDataNode>();
var type = datanode.Get<ValueDataNode>("type").Value;
if (!_prototypeTypes.ContainsKey(type))
{
if (_ignoredPrototypeTypes.Contains(type))
@@ -672,30 +653,28 @@ namespace Robust.Shared.Prototypes
}
var prototypeType = _prototypeTypes[type];
var res = _serializationManager.Read(prototypeType, node.ToDataNode(), skipHook: true);
var prototype = (IPrototype) res.RawValue!;
if (!overwrite && _prototypes[prototypeType].ContainsKey(prototype.ID))
if (!datanode.TryGet<ValueDataNode>(IdDataFieldAttribute.Name, out var idNode))
throw new PrototypeLoadException($"Prototype type {type} is missing an 'id' datafield.");
if (!overwrite && _prototypes[prototypeType].ContainsKey(idNode.Value))
{
throw new PrototypeLoadException($"Duplicate ID: '{prototype.ID}'");
throw new PrototypeLoadException($"Duplicate ID: '{idNode.Value}'");
}
_prototypeResults[prototypeType][prototype.ID] = res;
if (prototype is IInheritingPrototype inheritingPrototype)
_prototypeResults[prototypeType][idNode.Value] = datanode;
if (prototypeType.IsAssignableTo(typeof(IInheritingPrototype)))
{
_inheritanceTrees[prototypeType].AddId(prototype.ID, inheritingPrototype.Parent, true);
}
else
{
//we call it here since it wont get called when pushing inheritance
res.CallAfterDeserializationHook();
datanode.TryGet<ValueDataNode>(ParentDataFieldAttribute.Name, out var parentNode);
_inheritanceTrees[prototypeType].AddId(idNode.Value, parentNode?.Value, true);
}
_prototypes[prototypeType][prototype.ID] = prototype;
changedPrototypes.Add(prototype);
if (changed == null) continue;
if (!changed.TryGetValue(prototypeType, out var set))
changed[prototypeType] = set = new HashSet<string>();
set.Add(idNode.Value);
}
return changedPrototypes;
}
public bool HasIndex<T>(string id) where T : class, IPrototype
@@ -725,6 +704,23 @@ namespace Robust.Shared.Prototypes
return index.TryGetValue(id, out prototype);
}
public bool HasMapping<T>(string id)
{
if (!_prototypeResults.TryGetValue(typeof(T), out var index))
{
throw new UnknownPrototypeException(id);
}
return index.ContainsKey(id);
}
public bool TryGetMapping(Type type, string id, [NotNullWhen(true)] out MappingDataNode? mappings)
{
var ret = _prototypeResults[type].TryGetValue(id, out var originalMappings);
mappings = originalMappings?.Copy();
return ret;
}
/// <inheritdoc />
public bool HasVariant(string variant)
{
@@ -805,13 +801,66 @@ namespace Robust.Shared.Prototypes
$"Duplicate prototype type ID: {attribute.Type}. Current: {_prototypeTypes[attribute.Type]}");
}
var foundIdAttribute = false;
var foundParentAttribute = false;
var foundAbstractAttribute = false;
foreach (var info in type.GetAllPropertiesAndFields())
{
var hasId = info.HasAttribute<IdDataFieldAttribute>();
var hasParent = info.HasAttribute<ParentDataFieldAttribute>();
if (hasId)
{
if (foundIdAttribute)
throw new InvalidImplementationException(type,
typeof(IPrototype),
$"Found two {nameof(IdDataFieldAttribute)}");
foundIdAttribute = true;
}
if (hasParent)
{
if (foundParentAttribute)
throw new InvalidImplementationException(type,
typeof(IInheritingPrototype),
$"Found two {nameof(ParentDataFieldAttribute)}");
foundParentAttribute = true;
}
if (hasId && hasParent)
throw new InvalidImplementationException(type,
typeof(IPrototype),
$"Prototype {type} has the Id- & ParentDatafield on single member {info.Name}");
if (info.HasAttribute<AbstractDataFieldAttribute>())
{
if (foundAbstractAttribute)
throw new InvalidImplementationException(type,
typeof(IInheritingPrototype),
$"Found two {nameof(AbstractDataFieldAttribute)}");
foundAbstractAttribute = true;
}
}
if (!foundIdAttribute)
throw new InvalidImplementationException(type,
typeof(IPrototype),
$"Did not find any member annotated with the {nameof(IdDataFieldAttribute)}");
if (type.IsAssignableTo(typeof(IInheritingPrototype)) && (!foundParentAttribute || !foundAbstractAttribute))
throw new InvalidImplementationException(type,
typeof(IInheritingPrototype),
$"Did not find any member annotated with the {nameof(ParentDataFieldAttribute)} and/or {nameof(AbstractDataFieldAttribute)}");
_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>();
_prototypeResults[type] = new Dictionary<string, MappingDataNode>();
if (typeof(IInheritingPrototype).IsAssignableFrom(type))
_inheritanceTrees[type] = new PrototypeInheritanceTree();
}

View File

@@ -12,7 +12,6 @@
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.2" />
<PackageReference Include="Microsoft.ILVerification" Version="6.0.0" />
<PackageReference Include="Nett" Version="0.15.0" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="Pidgin" Version="2.5.0" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="Robust.Shared.AuthLib" Version="0.1.2" />

View File

@@ -6,7 +6,8 @@ namespace Robust.Shared.Serialization.Manager.Attributes
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[MeansImplicitAssignment]
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]
public sealed class DataFieldAttribute : Attribute
[Virtual]
public class DataFieldAttribute : Attribute
{
public readonly string Tag;
public readonly int Priority;

View File

@@ -1,19 +1,15 @@
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Mapping;
namespace Robust.Shared.Serialization.Manager.Definition
{
public partial class DataDefinition
{
private delegate DeserializedFieldEntry[] DeserializeDelegate(
private delegate object PopulateDelegateSignature(
object target,
MappingDataNode mappingDataNode,
ISerializationManager serializationManager,
ISerializationContext? context,
bool skipHook);
private delegate DeserializationResult PopulateDelegateSignature(
object target,
DeserializedFieldEntry[] deserializationResults,
bool skipHook,
object?[] defaultValues);
private delegate MappingDataNode SerializeDelegateSignature(
@@ -29,10 +25,6 @@ namespace Robust.Shared.Serialization.Manager.Definition
ISerializationManager serializationManager,
ISerializationContext? context);
private delegate DeserializationResult CreateDefinitionDelegate(
object value,
DeserializedFieldEntry[] mappings);
private delegate TValue AccessField<TTarget, TValue>(ref TTarget target);
private delegate void AssignField<TTarget, TValue>(ref TTarget target, TValue? value);

View File

@@ -4,7 +4,6 @@ using System.Reflection;
using System.Reflection.Emit;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
@@ -14,20 +13,22 @@ namespace Robust.Shared.Serialization.Manager.Definition
{
public partial class DataDefinition
{
private DeserializeDelegate EmitDeserializationDelegate()
private PopulateDelegateSignature EmitPopulateDelegate()
{
DeserializedFieldEntry[] DeserializationDelegate(MappingDataNode mappingDataNode,
ISerializationManager serializationManager, ISerializationContext? serializationContext, bool skipHook)
object PopulateDelegate(
object target,
MappingDataNode mappingDataNode,
ISerializationManager serializationManager,
ISerializationContext? serializationContext,
bool skipHook,
object?[] defaultValues)
{
var mappedInfo = new DeserializedFieldEntry[BaseFieldDefinitions.Length];
for (var i = 0; i < BaseFieldDefinitions.Length; i++)
{
var fieldDefinition = BaseFieldDefinitions[i];
if (fieldDefinition.Attribute.ServerOnly && !IoCManager.Resolve<INetManager>().IsServer)
{
mappedInfo[i] = new DeserializedFieldEntry(false, fieldDefinition.InheritanceBehavior);
continue;
}
@@ -35,13 +36,14 @@ namespace Robust.Shared.Serialization.Manager.Definition
if (!mapped)
{
mappedInfo[i] = new DeserializedFieldEntry(mapped, fieldDefinition.InheritanceBehavior);
if (fieldDefinition.Attribute.Required)
throw new InvalidOperationException($"Required field {fieldDefinition.Attribute.Tag} of type {target.GetType()} wasn't mapped.");
continue;
}
var type = fieldDefinition.FieldType;
var node = mappingDataNode.Get(fieldDefinition.Attribute.Tag);
DeserializationResult result;
object? result;
if (fieldDefinition.Attribute.CustomTypeSerializer != null)
{
var foundInterface = false;
@@ -68,52 +70,17 @@ namespace Robust.Shared.Serialization.Manager.Definition
result = serializationManager.Read(type, node, serializationContext, skipHook);
}
var entry = new DeserializedFieldEntry(mapped, fieldDefinition.InheritanceBehavior, result);
mappedInfo[i] = entry;
}
return mappedInfo;
}
return DeserializationDelegate;
}
private PopulateDelegateSignature EmitPopulateDelegate()
{
// TODO Serialization: validate mappings array count
var constructor =
typeof(DeserializedDefinition<>).MakeGenericType(Type).GetConstructor(new[] {Type, typeof(DeserializedFieldEntry[])}) ??
throw new NullReferenceException();
var valueParam = Expression.Parameter(typeof(object), "value");
var valueParamCast = Expression.Convert(valueParam, Type);
var mappingParam = Expression.Parameter(typeof(DeserializedFieldEntry[]), "mapping");
var newExp = Expression.New(constructor, valueParamCast, mappingParam);
var createDefinitionDelegate = Expression.Lambda<CreateDefinitionDelegate>(newExp, valueParam, mappingParam).Compile();
DeserializationResult PopulateDelegate(
object target,
DeserializedFieldEntry[] deserializedFields,
object?[] defaultValues)
{
for (var i = 0; i < BaseFieldDefinitions.Length; i++)
{
var res = deserializedFields[i];
if (!res.Mapped) continue;
var defValue = defaultValues[i];
if (Equals(res.Result?.RawValue, defValue))
if (Equals(result, defValue))
{
continue;
}
FieldAssigners[i](ref target, res.Result?.RawValue);
FieldAssigners[i](ref target, result);
}
return createDefinitionDelegate(target, deserializedFields);
return target;
}
return PopulateDelegate;

View File

@@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Log;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
@@ -16,7 +15,6 @@ namespace Robust.Shared.Serialization.Manager.Definition
{
public sealed partial class DataDefinition
{
private readonly DeserializeDelegate _deserialize;
private readonly PopulateDelegateSignature _populate;
private readonly SerializeDelegateSignature _serialize;
private readonly CopyDelegateSignature _copy;
@@ -41,7 +39,6 @@ namespace Robust.Shared.Serialization.Manager.Definition
BaseFieldDefinitions = fields.ToImmutableArray();
DefaultValues = fieldDefs.Select(f => f.DefaultValue).ToArray();
_deserialize = EmitDeserializationDelegate();
_populate = EmitPopulateDelegate();
_serialize = EmitSerializeDelegate();
_copy = EmitCopyDelegate();
@@ -71,20 +68,14 @@ namespace Robust.Shared.Serialization.Manager.Definition
internal ImmutableArray<FieldDefinition> BaseFieldDefinitions { get; }
public DeserializationResult Populate(object target, DeserializedFieldEntry[] fields)
{
return _populate(target, fields, DefaultValues);
}
public DeserializationResult Populate(
public object Populate(
object target,
MappingDataNode mapping,
ISerializationManager serialization,
ISerializationContext? context,
bool skipHook)
{
var fields = _deserialize(mapping, serialization, context, skipHook);
return _populate(target, fields, DefaultValues);
return _populate(target, mapping, serialization, context, skipHook, DefaultValues);
}
public MappingDataNode Serialize(

View File

@@ -1,6 +1,6 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Manager.Definition;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
@@ -25,7 +25,7 @@ namespace Robust.Shared.Serialization.Manager
/// <param name="type">The type to check for.</param>
/// <returns>True if it does, false otherwise.</returns>
bool HasDataDefinition(Type type);
#region Validation
/// <summary>
@@ -59,52 +59,7 @@ namespace Robust.Shared.Serialization.Manager
#endregion
/// <summary>
/// Creates a deserialization result from a generic type and its fields,
/// populating the object.
/// </summary>
/// <param name="fields">The fields to use for deserialization.</param>
/// <param name="skipHook">Whether or not to skip running <see cref="ISerializationHooks"/></param>
/// <typeparam name="T">The type to populate.</typeparam>
/// <returns>A result with the populated type.</returns>
DeserializationResult CreateDataDefinition<T>(DeserializedFieldEntry[] fields, bool skipHook = false) where T : notnull, new();
#region Populate
/// <summary>
/// Creates a deserialization result from a generic type and its definition,
/// populating the object.
/// </summary>
/// <param name="obj">The object to populate.</param>
/// <param name="definition">The data to use for deserialization.</param>
/// <param name="skipHook">Whether or not to skip running <see cref="ISerializationHooks"/></param>
/// <typeparam name="T">The type of <see cref="obj"/> to populate.</typeparam>
/// <returns>A result with the populated object.</returns>
DeserializationResult PopulateDataDefinition<T>(T obj, DeserializedDefinition<T> definition, bool skipHook = false) where T : notnull, new();
/// <summary>
/// Creates a deserialization result from an object and its definition,
/// populating the object.
/// </summary>
/// <param name="obj">The object to populate.</param>
/// <param name="definition">The data to use for deserialization.</param>
/// <param name="skipHook">Whether or not to skip running <see cref="ISerializationHooks"/></param>
/// <returns>A result with the populated object.</returns>
DeserializationResult PopulateDataDefinition(object obj, IDeserializedDefinition definition, bool skipHook = false);
#endregion
#region Read
/// <summary>
/// Deserializes a node into an object, populating it.
/// </summary>
/// <param name="type">The type of object to populate.</param>
/// <param name="node">The node to deserialize.</param>
/// <param name="context">The context to use, if any.</param>
/// <param name="skipHook">Whether or not to skip running <see cref="ISerializationHooks"/></param>
/// <returns>A result with the deserialized object.</returns>
DeserializationResult Read(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false);
/// <summary>
/// Deserializes a node into an object, populating it.
/// </summary>
@@ -112,20 +67,10 @@ namespace Robust.Shared.Serialization.Manager
/// <param name="node">The node to deserialize.</param>
/// <param name="context">The context to use, if any.</param>
/// <param name="skipHook">Whether or not to skip running <see cref="ISerializationHooks"/></param>
/// <param name="value">The value to read into. If none is supplied, a new object will be created.</param>
/// <returns>The deserialized object or null.</returns>
public object? ReadValue(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false);
/// <summary>
/// Deserializes a node into an object of the given <see cref="type"/>,
/// directly casting it to the given generic type <see cref="T"/>.
/// </summary>
/// <param name="type">The type of object to deserialize into.</param>
/// <param name="node">The node to deserialize.</param>
/// <param name="context">The context to use, if any.</param>
/// <param name="skipHook">Whether or not to skip running <see cref="ISerializationHooks"/></param>
/// <typeparam name="T">The generic type to cast the resulting object to.</typeparam>
/// <returns>The deserialized casted object, or null.</returns>
T? ReadValueCast<T>(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false);
public object? Read(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false,
object? value = null);
/// <summary>
/// Deserializes a node into a populated object of the given generic type <see cref="T"/>
@@ -133,12 +78,13 @@ namespace Robust.Shared.Serialization.Manager
/// <param name="node">The node to deserialize.</param>
/// <param name="context">The context to use, if any.</param>
/// <param name="skipHook">Whether or not to skip running <see cref="ISerializationHooks"/></param>
/// <param name="value">The value to read into. If none is supplied, a new object will be created.</param>
/// <typeparam name="T">The type of object to create and populate.</typeparam>
/// <returns>The deserialized object, or null.</returns>
T? ReadValue<T>(DataNode node, ISerializationContext? context = null, bool skipHook = false);
T Read<T>(DataNode node, ISerializationContext? context = null, bool skipHook = false, T? value = default);
DeserializationResult ReadWithTypeSerializer(Type value, Type serializer, DataNode node,
ISerializationContext? context = null, bool skipHook = false);
object? ReadWithTypeSerializer(Type type, Type serializer, DataNode node,
ISerializationContext? context = null, bool skipHook = false, object? value = null);
#endregion
@@ -253,5 +199,23 @@ namespace Robust.Shared.Serialization.Manager
Type GetConstantTypeFromTag(Type tagType);
#endregion
#region Composition
DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null);
public TNode PushComposition<TType, TNode>(TNode[] parents, TNode child, ISerializationContext? context = null) where TNode : DataNode
{
// ReSharper disable once CoVariantArrayConversion
return (TNode)PushComposition(typeof(TType), parents, child, context);
}
public TNode PushCompositionWithGenericNode<TNode>(Type type, TNode[] parents, TNode child, ISerializationContext? context = null) where TNode : DataNode
{
// ReSharper disable once CoVariantArrayConversion
return (TNode) PushComposition(type, parents, child, context);
}
#endregion
}
}

View File

@@ -1,24 +0,0 @@
namespace Robust.Shared.Serialization.Manager.Result
{
public abstract class DeserializationResult
{
public abstract object? RawValue { get; }
public abstract DeserializationResult PushInheritanceFrom(DeserializationResult source);
public abstract DeserializationResult Copy();
public abstract void CallAfterDeserializationHook();
public T Cast<T>() where T : DeserializationResult
{
if (this is T value) return value;
throw new InvalidDeserializedResultTypeException<T>(GetType());
}
}
public abstract class DeserializationResult<T> : DeserializationResult
{
public abstract T? Value { get; }
}
}

View File

@@ -1,71 +0,0 @@
using System;
namespace Robust.Shared.Serialization.Manager.Result
{
public sealed class DeserializedArray : DeserializationResult
{
public DeserializedArray(Array array, DeserializationResult[] mappings)
{
Value = array;
Mappings = mappings;
}
public Array Value { get; }
public DeserializationResult[] Mappings { get; }
public override object RawValue => Value;
public override DeserializationResult PushInheritanceFrom(DeserializationResult source)
{
var sourceCollection = source.Cast<DeserializedArray>();
var values = (Array) Activator.CreateInstance(Value.GetType(), Value.Length)!;
var results = new DeserializationResult[sourceCollection.Mappings.Length];
for (var i = 0; i < sourceCollection.Mappings.Length; i++)
{
var oldRes = sourceCollection.Mappings[i];
var newRes = oldRes.Copy();
values.SetValue(newRes.RawValue, i);
results[i] = newRes;
}
for (var i = 0; i < Mappings.Length; i++)
{
var oldRes = Mappings[i];
var newRes = oldRes.Copy();
values.SetValue(newRes.RawValue, i);
results[i] = newRes;
}
return new DeserializedArray(values, results);
}
public override DeserializationResult Copy()
{
var values = (Array) Activator.CreateInstance(Value.GetType(), Value.Length)!;
var results = new DeserializationResult[Mappings.Length];
for (var i = 0; i < Mappings.Length; i++)
{
var oldRes = Mappings[i];
var newRes = oldRes.Copy();
values.SetValue(newRes.RawValue, i);
results[i] = newRes;
}
return new DeserializedArray(values, results);
}
public override void CallAfterDeserializationHook()
{
foreach (var elem in Mappings)
{
elem.CallAfterDeserializationHook();
}
}
}
}

View File

@@ -1,73 +0,0 @@
using System.Collections.Generic;
namespace Robust.Shared.Serialization.Manager.Result
{
public sealed class DeserializedCollection<TCollection, TElement> : DeserializationResult<TCollection> where TCollection : IReadOnlyCollection<TElement>
{
public delegate TCollection Create(List<TElement> elements);
public DeserializedCollection(
TCollection value,
IEnumerable<DeserializationResult> mappings,
Create createDelegate)
{
Value = value;
Mappings = mappings;
CreateDelegate = createDelegate;
}
public override TCollection Value { get; }
public IEnumerable<DeserializationResult> Mappings { get; }
public override object RawValue => Value;
private Create CreateDelegate { get; }
public override DeserializationResult PushInheritanceFrom(DeserializationResult source)
{
var sourceCollection = source.Cast<DeserializedCollection<TCollection, TElement>>();
var valueList = new List<TElement>();
var resList = new List<DeserializationResult>();
foreach (var oldRes in sourceCollection.Mappings)
{
var newRes = oldRes.Copy().Cast<DeserializationResult<TElement>>();
valueList.Add(newRes.Value!);
resList.Add(newRes);
}
foreach (var oldRes in Mappings)
{
var newRes = oldRes.Copy().Cast<DeserializationResult<TElement>>();
valueList.Add(newRes.Value!);
resList.Add(newRes);
}
return new DeserializedCollection<TCollection, TElement>(CreateDelegate(valueList), resList, CreateDelegate);
}
public override DeserializationResult Copy()
{
var valueList = new List<TElement>();
var resList = new List<DeserializationResult>();
foreach (var oldRes in Mappings)
{
var newRes = oldRes.Copy();
valueList.Add((TElement) newRes.RawValue!);
resList.Add(newRes);
}
return new DeserializedCollection<TCollection, TElement>(CreateDelegate(valueList), resList, CreateDelegate);
}
public override void CallAfterDeserializationHook()
{
foreach (var val in Mappings)
{
val.CallAfterDeserializationHook();
}
}
}
}

View File

@@ -1,111 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
using static Robust.Shared.Prototypes.EntityPrototype;
namespace Robust.Shared.Serialization.Manager.Result
{
public sealed class DeserializedComponentRegistry : DeserializationResult<ComponentRegistry>
{
public DeserializedComponentRegistry(
ComponentRegistry value,
IReadOnlyDictionary<DeserializationResult, DeserializationResult> mappings)
{
Value = value;
Mappings = mappings;
}
public override ComponentRegistry Value { get; }
public IReadOnlyDictionary<DeserializationResult, DeserializationResult> Mappings { get; }
public override object RawValue => Value;
public override DeserializationResult PushInheritanceFrom(DeserializationResult source)
{
var componentFactory = IoCManager.Resolve<IComponentFactory>();
var sourceRes = source.Cast<DeserializedComponentRegistry>();
var mappingDict = Mappings.ToDictionary(p => p.Key.Copy(), p => p.Value.Copy());
foreach (var (keyRes, valRes) in sourceRes.Mappings)
{
var newKeyRes = keyRes.Copy();
var newValueRes = valRes.Copy();
if (mappingDict.Any(p =>
{
var k1 = (string) newKeyRes.RawValue!;
var k2 = (string) p.Key.RawValue!;
if (k1 == k2)
{
return false;
}
var registration1 = componentFactory.GetRegistration(k1!);
var registration2 = componentFactory.GetRegistration(k2!);
foreach (var reference in registration1.References)
{
if (registration2.References.Contains(reference))
{
return true;
}
}
return false;
}))
{
continue;
}
var res = newKeyRes;
var oldEntry = mappingDict.FirstOrNull(p => Equals(p.Key.RawValue, res.RawValue));
if (oldEntry.HasValue)
{
newKeyRes = oldEntry.Value.Key.PushInheritanceFrom(newKeyRes);
newValueRes = oldEntry.Value.Value.PushInheritanceFrom(newValueRes);
mappingDict.Remove(oldEntry.Value.Key);
}
mappingDict.Add(newKeyRes, newValueRes);
}
var valueDict = new ComponentRegistry();
foreach (var (key, val) in mappingDict)
{
valueDict.Add((string) key.RawValue!, (IComponent) val.RawValue!);
}
return new DeserializedComponentRegistry(valueDict, mappingDict);
}
public override DeserializationResult Copy()
{
var registry = new ComponentRegistry();
var mappingDict = new Dictionary<DeserializationResult, DeserializationResult>();
foreach (var (keyRes, valRes) in Mappings)
{
var newKeyRes = keyRes.Copy();
var newValueRes = valRes.Copy();
registry.Add((string) newKeyRes.RawValue!, (IComponent) newValueRes.RawValue!);
mappingDict.Add(newKeyRes, newValueRes);
}
return new DeserializedComponentRegistry(registry, mappingDict);
}
public override void CallAfterDeserializationHook()
{
foreach (var (_, comp) in Mappings)
{
comp.CallAfterDeserializationHook();
}
}
}
}

View File

@@ -1,59 +0,0 @@
using System;
using Robust.Shared.IoC;
namespace Robust.Shared.Serialization.Manager.Result
{
public sealed class DeserializedDefinition<T> : DeserializationResult<T>, IDeserializedDefinition where T : notnull, new()
{
public DeserializedDefinition(T value, DeserializedFieldEntry[] mapping)
{
Value = value;
Mapping = mapping;
}
public override T Value { get; }
public DeserializedFieldEntry[] Mapping { get; }
public override object RawValue => Value;
public override DeserializationResult PushInheritanceFrom(DeserializationResult source)
{
var dataDef = source.Cast<DeserializedDefinition<T>>();
if (dataDef.Mapping.Length != Mapping.Length)
throw new ArgumentException($"Mapping length mismatch in PushInheritanceFrom ({typeof(T)})");
var newMapping = new DeserializedFieldEntry[Mapping.Length];
for (var i = 0; i < dataDef.Mapping.Length; i++)
{
newMapping[i] = Mapping[i].PushInheritanceFrom(dataDef.Mapping[i]);
}
return IoCManager.Resolve<ISerializationManager>().CreateDataDefinition<T>(newMapping, true);
}
public override DeserializationResult Copy()
{
var newMapping = new DeserializedFieldEntry[Mapping.Length];
for (var i = 0; i < Mapping.Length; i++)
{
newMapping[i] = Mapping[i].Copy();
}
return IoCManager.Resolve<ISerializationManager>().CreateDataDefinition<T>(newMapping, true);
}
public override void CallAfterDeserializationHook()
{
foreach (var fieldEntry in Mapping)
{
fieldEntry.Result?.CallAfterDeserializationHook();
}
if (Value is ISerializationHooks hooks)
hooks.AfterDeserialization();
}
}
}

View File

@@ -1,83 +0,0 @@
using System.Collections.Generic;
namespace Robust.Shared.Serialization.Manager.Result
{
public sealed class DeserializedDictionary<TDict, TKey, TValue> :
DeserializationResult<TDict>
where TKey : notnull
where TDict : IReadOnlyDictionary<TKey, TValue>
{
public delegate TDict Create(Dictionary<TKey, TValue> elements);
public DeserializedDictionary(
TDict value,
IReadOnlyDictionary<DeserializationResult, DeserializationResult> mappings,
Create createDelegate)
{
Value = value;
Mappings = mappings;
CreateDelegate = createDelegate;
}
public override TDict Value { get; }
public IReadOnlyDictionary<DeserializationResult, DeserializationResult> Mappings { get; }
public Create CreateDelegate { get; }
public override object RawValue => Value;
public override DeserializationResult PushInheritanceFrom(DeserializationResult source)
{
var sourceRes = source.Cast<DeserializedDictionary<TDict, TKey, TValue>>();
var valueDict = new Dictionary<TKey, TValue>();
var mappingDict = new Dictionary<DeserializationResult, DeserializationResult>();
foreach (var (keyRes, valRes) in sourceRes.Mappings)
{
var newKeyRes = keyRes.Copy();
var newValueRes = valRes.Copy();
valueDict.Add((TKey)newKeyRes.RawValue!, (TValue)newValueRes.RawValue!);
mappingDict.Add(newKeyRes, newValueRes);
}
foreach (var (keyRes, valRes) in Mappings)
{
var newKeyRes = keyRes.Copy();
var newValueRes = valRes.Copy();
valueDict.Add((TKey) newKeyRes.RawValue!, (TValue)newValueRes.RawValue!);
mappingDict.Add(newKeyRes, newValueRes);
}
return new DeserializedDictionary<TDict, TKey, TValue>(CreateDelegate(valueDict), mappingDict, CreateDelegate);
}
public override DeserializationResult Copy()
{
var valueDict = new Dictionary<TKey, TValue>();
var mappingDict = new Dictionary<DeserializationResult, DeserializationResult>();
foreach (var (keyRes, valRes) in Mappings)
{
var newKeyRes = keyRes.Copy();
var newValueRes = valRes.Copy();
valueDict.Add((TKey)newKeyRes.RawValue!, (TValue)newValueRes.RawValue!);
mappingDict.Add(newKeyRes, newValueRes);
}
return new DeserializedDictionary<TDict, TKey, TValue>(CreateDelegate(valueDict), mappingDict, CreateDelegate);
}
public override void CallAfterDeserializationHook()
{
foreach (var (key, val) in Mappings)
{
key.CallAfterDeserializationHook();
val.CallAfterDeserializationHook();
}
}
}
}

View File

@@ -1,49 +0,0 @@
using Robust.Shared.Serialization.Manager.Definition;
namespace Robust.Shared.Serialization.Manager.Result
{
public sealed class DeserializedFieldEntry
{
public DeserializedFieldEntry(bool mapped, InheritanceBehavior inheritanceBehavior, DeserializationResult? result = null)
{
Mapped = mapped;
Result = result;
InheritanceBehavior = inheritanceBehavior;
}
public bool Mapped { get; }
public InheritanceBehavior InheritanceBehavior { get; }
public DeserializationResult? Result { get; }
public DeserializedFieldEntry PushInheritanceFrom(DeserializedFieldEntry fieldEntry)
{
if (Mapped)
{
if (InheritanceBehavior == InheritanceBehavior.Always)
{
if (Result != null)
{
return fieldEntry.Result != null
? new DeserializedFieldEntry(Mapped, InheritanceBehavior, Result.PushInheritanceFrom(fieldEntry.Result))
: Copy();
}
else
{
return fieldEntry.Copy();
}
}
return Copy();
}
return InheritanceBehavior == InheritanceBehavior.Never ? Copy() : fieldEntry.Copy();
}
public DeserializedFieldEntry Copy()
{
return new(Mapped, InheritanceBehavior, Result?.Copy());
}
}
}

View File

@@ -1,58 +0,0 @@
namespace Robust.Shared.Serialization.Manager.Result
{
public sealed class DeserializedValue : DeserializationResult
{
public DeserializedValue(object? value)
{
RawValue = value;
}
public override object? RawValue { get; }
public override DeserializationResult PushInheritanceFrom(DeserializationResult source)
{
return source.Copy().Cast<DeserializedValue>();
}
public override DeserializationResult Copy()
{
return new DeserializedValue(RawValue);
}
public override void CallAfterDeserializationHook()
{
if (RawValue is ISerializationHooks hooks)
hooks.AfterDeserialization();
}
}
public sealed class DeserializedValue<T> : DeserializationResult<T>
{
public DeserializedValue(T value)
{
Value = value;
}
public override T Value { get; }
public override object? RawValue => Value;
public override DeserializationResult PushInheritanceFrom(DeserializationResult source)
{
return source.Copy().Cast<DeserializedValue<T>>();
}
public override DeserializationResult Copy()
{
return new DeserializedValue<T>(Value);
}
public override void CallAfterDeserializationHook()
{
if (Value is ISerializationHooks hooks)
{
hooks.AfterDeserialization();
}
}
}
}

View File

@@ -1,7 +0,0 @@
namespace Robust.Shared.Serialization.Manager.Result
{
public interface IDeserializedDefinition
{
DeserializedFieldEntry[] Mapping { get; }
}
}

View File

@@ -1,34 +0,0 @@
using System;
using System.Runtime.Serialization;
using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Result
{
[Virtual]
public class InvalidDeserializedResultTypeException<TExpected> : Exception
{
public readonly Type ReceivedType;
public override string Message => $"Invalid Type {ReceivedType} received. Expected {typeof(TExpected)}";
public InvalidDeserializedResultTypeException(Type receivedType)
{
ReceivedType = receivedType;
}
protected InvalidDeserializedResultTypeException([NotNull] SerializationInfo info, StreamingContext context, Type receivedType) : base(info, context)
{
ReceivedType = receivedType;
}
public InvalidDeserializedResultTypeException([CanBeNull] string? message, Type receivedType) : base(message)
{
ReceivedType = receivedType;
}
public InvalidDeserializedResultTypeException([CanBeNull] string? message, [CanBeNull] Exception? innerException, Type receivedType) : base(message, innerException)
{
ReceivedType = receivedType;
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Linq.Expressions;
using Robust.Shared.Serialization.Manager.Definition;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Utility;
namespace Robust.Shared.Serialization.Manager;
public partial class SerializationManager
{
private delegate DataNode PushCompositionDelegate(
Type type,
DataNode parent,
DataNode child,
ISerializationContext? context = null);
private readonly ConcurrentDictionary<(Type value, Type node), PushCompositionDelegate> _compositionPushers = new();
public DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null)
{
DebugTools.Assert(parents.All(x => x.GetType() == child.GetType()));
var pusher = GetOrCreatePushCompositionDelegate(type, child);
var node = child;
for (int i = 0; i < parents.Length; i++)
{
node = pusher(type, parents[i], node, context);
}
return node;
}
private PushCompositionDelegate GetOrCreatePushCompositionDelegate(Type type, DataNode node)
{
return _compositionPushers.GetOrAdd((type, node.GetType()), static (tuple, vfArgument) =>
{
var (value, nodeType) = tuple;
var (node, instance) = vfArgument;
var instanceConst = Expression.Constant(instance);
var dependencyCollectionConst = Expression.Constant(instance.DependencyCollection);
var typeParam = Expression.Parameter(typeof(Type), "type");
var parentParam = Expression.Parameter(typeof(DataNode), "parent");
var childParam = Expression.Parameter(typeof(DataNode), "child");
//todo paul serializers in the context should also override default serializers for array etc
var contextParam = Expression.Parameter(typeof(ISerializationContext), "context");
Expression expression;
if (instance.TryGetTypeInheritanceHandler(value, nodeType, out var handler))
{
var readerType = typeof(ITypeInheritanceHandler<,>).MakeGenericType(value, nodeType);
var readerConst = Expression.Constant(handler, readerType);
expression = Expression.Call(
readerConst,
"PushInheritance",
Type.EmptyTypes,
instanceConst,
Expression.Convert(childParam, nodeType),
Expression.Convert(parentParam, nodeType),
dependencyCollectionConst,
contextParam);
}
else if (nodeType == typeof(MappingDataNode) && instance.TryGetDefinition(value, out var dataDefinition))
{
var definitionConst = Expression.Constant(dataDefinition, typeof(DataDefinition));
expression = Expression.Call(
instanceConst,
nameof(PushInheritanceDefinition),
Type.EmptyTypes,
Expression.Convert(childParam, nodeType),
Expression.Convert(parentParam, nodeType),
definitionConst,
contextParam);
}
else
{
expression = node switch
{
SequenceDataNode => Expression.Call(
instanceConst,
nameof(PushInheritanceSequence),
Type.EmptyTypes,
Expression.Convert(childParam, nodeType),
Expression.Convert(parentParam, nodeType)),
MappingDataNode => Expression.Call(
instanceConst,
nameof(PushInheritanceMapping),
Type.EmptyTypes,
Expression.Convert(childParam, nodeType),
Expression.Convert(parentParam, nodeType)),
_ => childParam
};
}
return Expression.Lambda<PushCompositionDelegate>(
expression,
typeParam,
parentParam,
childParam,
contextParam).Compile();
}, (node, this));
}
private SequenceDataNode PushInheritanceSequence(SequenceDataNode child, SequenceDataNode _)
{
return child; //todo implement different inheritancebehaviours for yamlfield
}
private MappingDataNode PushInheritanceMapping(MappingDataNode child, MappingDataNode _)
{
return child; //todo implement different inheritancebehaviours for yamlfield
}
private MappingDataNode PushInheritanceDefinition(MappingDataNode child, MappingDataNode parent,
DataDefinition definition, ISerializationContext? context = null)
{
var newMapping = child.Copy();
foreach (var field in definition.BaseFieldDefinitions)
{
if (field.InheritanceBehavior == InheritanceBehavior.Never) continue;
var key = new ValueDataNode(field.Attribute.Tag);
if (parent.TryGetValue(key, out var parentValue))
{
if (newMapping.TryGetValue(key, out var childValue))
{
if (field.InheritanceBehavior == InheritanceBehavior.Always)
{
newMapping[key] = PushComposition(field.FieldType, new[] { parentValue }, childValue, context);
}
}
else
{
newMapping.Add(key, parentValue);
}
}
}
return newMapping;
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
@@ -12,10 +11,11 @@ namespace Robust.Shared.Serialization.Manager
{
public partial class SerializationManager
{
private delegate DeserializationResult ReadSerializerDelegate(
private delegate object? ReadSerializerDelegate(
DataNode node,
ISerializationContext? context = null,
bool skipHook = false);
bool skipHook = false,
object? value = null);
private delegate DataNode WriteSerializerDelegate(
object value,
@@ -47,6 +47,7 @@ namespace Robust.Shared.Serialization.Manager
var nodeParam = Expression.Parameter(typeof(DataNode), "node");
var contextParam = Expression.Parameter(typeof(ISerializationContext), "context");
var skipHookParam = Expression.Parameter(typeof(bool), "skipHook");
var valueParam = Expression.Parameter(typeof(object), "value");
var call = Expression.Call(
instanceParam,
@@ -54,13 +55,15 @@ namespace Robust.Shared.Serialization.Manager
new[] {tuple.value, tuple.node, tuple.serializer},
Expression.Convert(nodeParam, tuple.node),
contextParam,
skipHookParam);
skipHookParam,
valueParam);
return Expression.Lambda<ReadSerializerDelegate>(
call,
Expression.Convert(call, typeof(object)),
nodeParam,
contextParam,
skipHookParam).Compile();
skipHookParam,
valueParam).Compile();
}, this);
}
@@ -126,25 +129,27 @@ namespace Robust.Shared.Serialization.Manager
}, (common, source, target, serializer));
}
private DeserializationResult ReadWithSerializerRaw(
Type value,
private object? ReadWithSerializerRaw(
Type type,
DataNode node,
Type serializer,
ISerializationContext? context = null,
bool skipHook = false)
bool skipHook = false,
object? value = null)
{
return GetOrCreateReadSerializerDelegate(value, node.GetType(), serializer)(node, context, skipHook);
return GetOrCreateReadSerializerDelegate(type, node.GetType(), serializer)(node, context, skipHook, value);
}
private DeserializationResult ReadWithSerializer<T, TNode, TSerializer>(
private T ReadWithSerializer<T, TNode, TSerializer>(
TNode node,
ISerializationContext? context = null,
bool skipHook = false)
bool skipHook = false,
object? value = default)
where TSerializer : ITypeReader<T, TNode>
where TNode : DataNode
{
var serializer = (ITypeReader<T, TNode>) GetTypeSerializer(typeof(TSerializer));
return serializer.Read(this, node, DependencyCollection, skipHook, context);
return serializer.Read(this, node, DependencyCollection, skipHook, context, value == null ? default : (T)value);
}
private DataNode WriteWithSerializerRaw(

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Robust.Shared.Serialization.Manager.Definition;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
@@ -14,14 +14,37 @@ namespace Robust.Shared.Serialization.Manager
{
public partial class SerializationManager
{
private delegate DeserializationResult ReadDelegate(
private delegate object? ReadDelegate(
Type type,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false);
bool skipHook = false,
object? value = null);
private readonly ConcurrentDictionary<(Type value, Type node), ReadDelegate> _readers = new();
public T Read<T>(DataNode node, ISerializationContext? context = null, bool skipHook = false, T? value = default) //todo paul this default should be null
{
return (T)Read(typeof(T), node, context, skipHook, value)!;
}
public object? Read(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false, object? value = null)
{
var val = GetOrCreateReader(type, node)(type, node, context, skipHook, value);
ReadNullCheck(type, val);
return val;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadNullCheck(Type type, object? val)
{
if (!type.IsNullable() && val == null)
{
throw new InvalidOperationException(
$"{nameof(Read)}-Call returned a null value for non-nullable type {type}");
}
}
private ReadDelegate GetOrCreateReader(Type value, DataNode node)
{
if (node.Tag?.StartsWith("!type:") ?? false)
@@ -42,8 +65,10 @@ namespace Robust.Shared.Serialization.Manager
var typeParam = Expression.Parameter(typeof(Type), "type");
var nodeParam = Expression.Parameter(typeof(DataNode), "node");
//todo paul serializers in the context should also override default serializers for array etc
var contextParam = Expression.Parameter(typeof(ISerializationContext), "context");
var skipHookParam = Expression.Parameter(typeof(bool), "skipHook");
var valueParam = Expression.Parameter(typeof(object), "value");
MethodCallExpression call;
@@ -53,12 +78,14 @@ namespace Robust.Shared.Serialization.Manager
switch (node)
{
case ValueDataNode when nullable:
case ValueDataNode:
call = Expression.Call(
instanceConst,
nameof(ReadArrayValue),
new[] { elementType },
Expression.Convert(nodeParam, typeof(ValueDataNode)));
Expression.Convert(nodeParam, typeof(ValueDataNode)),
contextParam,
skipHookParam);
break;
case SequenceDataNode seqNode:
var isSealed = elementType.IsPrimitive || elementType.IsEnum ||
@@ -140,7 +167,7 @@ namespace Robust.Shared.Serialization.Manager
nameof(ReadSelfSerialize),
new[] { value },
Expression.Convert(nodeParam, typeof(ValueDataNode)),
instantiatorConst)
instantiatorConst, valueParam)
};
}
else if (instance.TryGetTypeReader(value, nodeType, out var reader))
@@ -160,12 +187,13 @@ namespace Robust.Shared.Serialization.Manager
skipHookParam),
ValueDataNode when nullable && !value.IsValueType => Expression.Call(
instanceConst,
nameof(ReadWithTypeReaderNullable),
nameof(ReadWithTypeReaderNullableClass),
new[] { value },
Expression.Convert(nodeParam, typeof(ValueDataNode)),
readerConst,
contextParam,
skipHookParam),
skipHookParam,
valueParam),
_ => Expression.Call(
instanceConst,
nameof(ReadWithTypeReader),
@@ -173,7 +201,8 @@ namespace Robust.Shared.Serialization.Manager
Expression.Convert(nodeParam, nodeType),
readerConst,
contextParam,
skipHookParam)
skipHookParam,
valueParam)
};
}
else if (value.IsInterface || value.IsAbstract)
@@ -203,7 +232,8 @@ namespace Robust.Shared.Serialization.Manager
populateConst,
hooksConst,
contextParam,
skipHookParam),
skipHookParam,
valueParam),
ValueDataNode => Expression.Call(
instanceConst,
nameof(ReadGenericValue),
@@ -214,7 +244,8 @@ namespace Robust.Shared.Serialization.Manager
populateConst,
hooksConst,
contextParam,
skipHookParam),
skipHookParam,
valueParam),
MappingDataNode => Expression.Call(
instanceConst,
nameof(ReadGenericMapping),
@@ -225,53 +256,54 @@ namespace Robust.Shared.Serialization.Manager
populateConst,
hooksConst,
contextParam,
skipHookParam),
skipHookParam,
valueParam),
SequenceDataNode => throw new ArgumentException($"No mapping node provided for type {value} at line: {node.Start.Line}"),
_ => throw new ArgumentException($"Unknown node type {nodeType} provided. Expected mapping node at line: {node.Start.Line}")
};
}
return Expression.Lambda<ReadDelegate>(
call,
Expression.Convert(call, typeof(object)),
typeParam,
nodeParam,
contextParam,
skipHookParam).Compile();
skipHookParam,
valueParam).Compile();
}, (node, this));
}
private DeserializationResult ReadArrayValue<T>(ValueDataNode value)
private T[]? ReadArrayValue<T>(
ValueDataNode value,
ISerializationContext? context = null,
bool skipHook = false)
{
if (value.Value == "null")
{
return new DeserializedValue<T[]?>(null);
return null;
}
throw new InvalidNodeTypeException("Cannot read an array from a value data node that is not null.");
var array = new T[1];
array[0] = Read<T>(value, context, skipHook);
return array;
}
private DeserializationResult ReadArraySequence<T>(
private T[] ReadArraySequence<T>(
SequenceDataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
var type = typeof(T);
var array = new T[node.Sequence.Count];
var results = new DeserializationResult[node.Sequence.Count];
for (var i = 0; i < node.Sequence.Count; i++)
{
var subNode = node.Sequence[i];
var result = Read(type, subNode, context, skipHook);
results[i] = result;
array[i] = (T) result.RawValue!;
array[i] = Read<T>(node.Sequence[i], context, skipHook);
}
return new DeserializedArray(array, results);
return array;
}
private DeserializationResult ReadArraySequenceSealed<T>(
private T[] ReadArraySequenceSealed<T>(
SequenceDataNode node,
ReadDelegate elementReader,
ISerializationContext? context = null,
@@ -279,90 +311,87 @@ namespace Robust.Shared.Serialization.Manager
{
var type = typeof(T);
var array = new T[node.Sequence.Count];
var results = new DeserializationResult[node.Sequence.Count];
for (var i = 0; i < node.Sequence.Count; i++)
{
var subNode = node.Sequence[i];
var result = elementReader(type, subNode, context, skipHook);
results[i] = result;
array[i] = (T) result.RawValue!;
ReadNullCheck(type, result);
array[i] = (T) result!;
}
return new DeserializedArray(array, results);
return array;
}
private DeserializationResult ReadEnumNullable<TEnum>(ValueDataNode node) where TEnum : struct
private TEnum? ReadEnumNullable<TEnum>(ValueDataNode node) where TEnum : struct
{
if (node.Value == "null")
{
return new DeserializedValue<TEnum?>(null);
return null;
}
var value = Enum.Parse<TEnum>(node.Value, true);
return new DeserializedValue<TEnum?>(value);
return ReadEnumValue<TEnum>(node);
}
private DeserializationResult ReadEnumValue<TEnum>(ValueDataNode node) where TEnum : struct
private TEnum ReadEnumValue<TEnum>(ValueDataNode node) where TEnum : struct
{
var value = Enum.Parse<TEnum>(node.Value, true);
return new DeserializedValue<TEnum>(value);
return Enum.Parse<TEnum>(node.Value, true);
}
private DeserializationResult ReadEnumSequence<TEnum>(SequenceDataNode node) where TEnum : struct
private TEnum ReadEnumSequence<TEnum>(SequenceDataNode node) where TEnum : struct
{
var value = Enum.Parse<TEnum>(string.Join(", ", node.Sequence), true);
return new DeserializedValue<TEnum>(value);
return Enum.Parse<TEnum>(string.Join(", ", node.Sequence), true);
}
private DeserializationResult ReadSelfSerialize<TValue>(
private TValue? ReadSelfSerialize<TValue>(
ValueDataNode node,
InstantiationDelegate<object> instantiator)
InstantiationDelegate<object> instantiator,
object? rawValue = null)
where TValue : ISelfSerialize
{
if (node.Value == "null")
{
return new DeserializedValue<TValue?>(default);
return default; //todo paul this default should be null
}
var value = (TValue) instantiator();
var value = (TValue) (rawValue ?? instantiator());
value.Deserialize(node.Value);
return new DeserializedValue<TValue?>(value);
return value;
}
private DeserializationResult ReadSelfSerializeNullableStruct<TValue>(
private TValue? ReadSelfSerializeNullableStruct<TValue>(
ValueDataNode node,
InstantiationDelegate<object> instantiator)
where TValue : struct, ISelfSerialize
{
if (node.Value == "null")
{
return new DeserializedValue<TValue?>(null);
return null;
}
var value = (TValue) instantiator();
value.Deserialize(node.Value);
return new DeserializedValue<TValue?>(value);
return value;
}
private DeserializationResult ReadWithTypeReaderNullable<TValue>(
private TValue? ReadWithTypeReaderNullableClass<TValue>(
ValueDataNode node,
ITypeReader<TValue, ValueDataNode> reader,
ISerializationContext? context = null,
bool skipHook = false)
bool skipHook = false,
object? value = null)
where TValue : class
{
if (node.Value == "null")
{
return new DeserializedValue<TValue?>(default);
return null;
}
return ReadWithTypeReader(node, reader, context, skipHook);
return ReadWithTypeReader(node, reader, context, skipHook, value);
}
private DeserializationResult ReadWithTypeReaderNullableStruct<TValue>(
private TValue? ReadWithTypeReaderNullableStruct<TValue>(
ValueDataNode node,
ITypeReader<TValue, ValueDataNode> reader,
ISerializationContext? context = null,
@@ -371,17 +400,18 @@ namespace Robust.Shared.Serialization.Manager
{
if (node.Value == "null")
{
return new DeserializedValue<TValue?>(null);
return null;
}
return ReadWithTypeReader(node, reader, context, skipHook);
}
private DeserializationResult ReadWithTypeReader<TValue, TNode>(
private TValue ReadWithTypeReader<TValue, TNode>(
TNode node,
ITypeReader<TValue, TNode> reader,
ISerializationContext? context = null,
bool skipHook = false)
bool skipHook = false,
object? value = null)
where TNode : DataNode
{
if (context != null &&
@@ -390,34 +420,36 @@ namespace Robust.Shared.Serialization.Manager
reader = (ITypeReader<TValue, TNode>) readerUnCast;
}
return reader.Read(this, node, DependencyCollection, skipHook, context);
return reader.Read(this, node, DependencyCollection, skipHook, context, value == null ? default : (TValue) value);
}
private DeserializationResult ReadGenericNullable<TValue>(
private TValue? ReadGenericNullable<TValue>(
ValueDataNode node,
InstantiationDelegate<object> instantiator,
DataDefinition? definition,
bool populate,
bool hooks,
ISerializationContext? context = null,
bool skipHook = false)
bool skipHook = false,
object? value = null)
{
if (node.Value == "null")
{
return new DeserializedValue<TValue?>(default);
return default; //todo paul this default should be null
}
return ReadGenericValue<TValue?>(node, instantiator, definition, populate, hooks, context, skipHook);
return ReadGenericValue<TValue>(node, instantiator, definition, populate, hooks, context, skipHook, value);
}
private DeserializationResult ReadGenericValue<TValue>(
private TValue ReadGenericValue<TValue>(
ValueDataNode node,
InstantiationDelegate<object> instantiator,
DataDefinition? definition,
bool populate,
bool hooks,
ISerializationContext? context = null,
bool skipHook = false)
bool skipHook = false,
object? instance = null)
{
var type = typeof(TValue);
@@ -425,7 +457,7 @@ namespace Robust.Shared.Serialization.Manager
context.TypeReaders.TryGetValue((typeof(TValue), typeof(ValueDataNode)), out var readerUnCast))
{
var reader = (ITypeReader<TValue, ValueDataNode>) readerUnCast;
return reader.Read(this, node, DependencyCollection, skipHook, context);
return reader.Read(this, node, DependencyCollection, skipHook, context, instance == null ? default : (TValue)instance);
}
if (definition == null)
@@ -433,7 +465,7 @@ namespace Robust.Shared.Serialization.Manager
throw new ArgumentException($"No data definition found for type {type} with node type {node.GetType()} when reading");
}
var instance = instantiator();
instance ??= instantiator();
if (populate)
{
@@ -445,36 +477,32 @@ namespace Robust.Shared.Serialization.Manager
throw new ArgumentException($"No mapping node provided for type {type} at line: {node.Start.Line}");
}
// If we get an empty ValueDataNode we just use an empty mapping
var mapping = new MappingDataNode();
var result = definition.Populate(instance, mapping, this, context, skipHook);
if (!skipHook && hooks)
{
((ISerializationHooks) result.RawValue!).AfterDeserialization();
((ISerializationHooks) instance).AfterDeserialization();
}
return result;
return (TValue) instance;
}
private DeserializationResult ReadGenericMapping<TValue>(
private TValue ReadGenericMapping<TValue>(
MappingDataNode node,
InstantiationDelegate<object> instantiator,
DataDefinition? definition,
bool populate,
bool hooks,
ISerializationContext? context = null,
bool skipHook = false)
bool skipHook = false,
object? instance = null)
{
var type = typeof(TValue);
var instance = instantiator();
instance ??= instantiator();
if (context != null &&
context.TypeReaders.TryGetValue((type, typeof(MappingDataNode)), out var readerUnCast))
{
var reader = (ITypeReader<TValue, MappingDataNode>) readerUnCast;
return reader.Read(this, node, DependencyCollection, skipHook, context);
return reader.Read(this, node, DependencyCollection, skipHook, context, (TValue?) instance);
}
if (definition == null)
@@ -487,47 +515,20 @@ namespace Robust.Shared.Serialization.Manager
((IPopulateDefaultValues) instance).PopulateDefaultValues();
}
var result = definition.Populate(instance, node, this, context, skipHook);
var result = (TValue)definition.Populate(instance, node, this, context, skipHook)!;
if (!skipHook && hooks)
{
((ISerializationHooks) result.RawValue!).AfterDeserialization();
((ISerializationHooks) result).AfterDeserialization();
}
return result;
}
public DeserializationResult Read(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false)
public object? ReadWithTypeSerializer(Type type, Type serializer, DataNode node, ISerializationContext? context = null,
bool skipHook = false, object? value = null)
{
return GetOrCreateReader(type, node)(type, node, context, skipHook);
}
public object? ReadValue(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false)
{
return Read(type, node, context, skipHook).RawValue;
}
public T? ReadValueCast<T>(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false)
{
var value = Read(type, node, context, skipHook);
if (value.RawValue == null)
{
return default;
}
return (T?) value.RawValue;
}
public T? ReadValue<T>(DataNode node, ISerializationContext? context = null, bool skipHook = false)
{
return ReadValueCast<T>(typeof(T), node, context, skipHook);
}
public DeserializationResult ReadWithTypeSerializer(Type value, Type serializer, DataNode node, ISerializationContext? context = null,
bool skipHook = false)
{
return ReadWithSerializerRaw(value, node, serializer, context, skipHook);
return ReadWithSerializerRaw(type, node, serializer, context, skipHook, value);
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Robust.Shared.Serialization.Manager;
public partial class SerializationManager
{
private readonly Dictionary<(Type Type, Type DataNodeType), object> _typeInheritanceHandlers = new();
private object? GetTypeInheritanceHandler(Type value, Type node)
{
if (_typeInheritanceHandlers.TryGetValue((value, node), out var handler))
{
return handler;
}
if (TryGetGenericInheritanceHandler(value, node, out handler))
{
return handler;
}
return null;
}
private bool TryGetTypeInheritanceHandler(Type value, Type node, [NotNullWhen(true)] out object? handler)
{
return (handler = GetTypeInheritanceHandler(value, node)) != null;
}
private bool TryGetGenericInheritanceHandler(Type type, Type node, [NotNullWhen(true)] out object? handler)
{
if (type.IsGenericType)
{
var typeDef = type.GetGenericTypeDefinition();
Type? serializerTypeDef = null;
foreach (var (key, val) in _genericInheritanceHandlerTypes)
{
if (typeDef.HasSameMetadataDefinitionAs(key.Type) && key.DataNodeType.IsAssignableFrom(node))
{
serializerTypeDef = val;
break;
}
}
if (serializerTypeDef == null)
{
handler = null;
return false;
}
var serializerType = serializerTypeDef.MakeGenericType(type.GetGenericArguments());
handler = RegisterSerializer(serializerType) ?? throw new NullReferenceException();
return true;
}
handler = null;
return false;
}
}

View File

@@ -10,6 +10,7 @@ namespace Robust.Shared.Serialization.Manager
public partial class SerializationManager
{
private readonly Dictionary<(Type Type, Type DataNodeType), Type> _genericReaderTypes = new();
private readonly Dictionary<(Type Type, Type DataNodeType), Type> _genericInheritanceHandlerTypes = new();
private readonly Dictionary<Type, Type> _genericWriterTypes = new();
private readonly Dictionary<Type, Type> _genericCopierTypes = new();
private readonly Dictionary<(Type Type, Type DataNodeType), Type> _genericValidatorTypes = new();
@@ -44,11 +45,14 @@ namespace Robust.Shared.Serialization.Manager
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypeCopier<>)).ToArray();
var validatorInterfaces = type.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypeValidator<,>)).ToArray();
var inheritanceHandlerInterfaces = type.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypeInheritanceHandler<,>)).ToArray();
if (readerInterfaces.Length == 0 &&
writerInterfaces.Length == 0 &&
copierInterfaces.Length == 0 &&
validatorInterfaces.Length == 0)
validatorInterfaces.Length == 0 &&
inheritanceHandlerInterfaces.Length == 0)
{
throw new InvalidOperationException(
"Tried to register TypeReader/Writer/Copier that had none of the interfaces inherited.");
@@ -84,6 +88,12 @@ namespace Robust.Shared.Serialization.Manager
Logger.ErrorS(LogCategory, $"Tried registering generic reader for type {validatorInterface.GetGenericArguments()[0]} and node {validatorInterface.GetGenericArguments()[1]} twice");
}
foreach (var inheritanceHandlerInterface in inheritanceHandlerInterfaces)
{
if (!_genericInheritanceHandlerTypes.TryAdd((inheritanceHandlerInterface.GetGenericArguments()[0], inheritanceHandlerInterface.GetGenericArguments()[1]), type))
Logger.ErrorS(LogCategory, $"Tried registering generic reader for type {inheritanceHandlerInterface.GetGenericArguments()[0]} and node {inheritanceHandlerInterface.GetGenericArguments()[1]} twice");
}
return null;
}
else
@@ -114,6 +124,12 @@ namespace Robust.Shared.Serialization.Manager
Logger.ErrorS(LogCategory, $"Tried registering reader for type {validatorInterface.GetGenericArguments()[0]} and node {validatorInterface.GetGenericArguments()[1]} twice");
}
foreach (var inheritanceHandlerInterface in inheritanceHandlerInterfaces)
{
if (!_typeInheritanceHandlers.TryAdd((inheritanceHandlerInterface.GetGenericArguments()[0], inheritanceHandlerInterface.GetGenericArguments()[1]), serializer))
Logger.ErrorS(LogCategory, $"Tried registering reader for type {inheritanceHandlerInterface.GetGenericArguments()[0]} and node {inheritanceHandlerInterface.GetGenericArguments()[1]} twice");
}
return serializer;
}
}

View File

@@ -14,7 +14,6 @@ using Robust.Shared.Log;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Definition;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
@@ -304,39 +303,6 @@ namespace Robust.Shared.Serialization.Manager
return ValidateNodeWith(typeof(TType), typeof(TSerializer), node, context);
}
public DeserializationResult CreateDataDefinition<T>(DeserializedFieldEntry[] fields, bool skipHook = false)
where T : notnull, new()
{
var obj = new T();
return PopulateDataDefinition(obj, new DeserializedDefinition<T>(obj, fields), skipHook);
}
public DeserializationResult PopulateDataDefinition<T>(T obj, DeserializedDefinition<T> definition, bool skipHook = false)
where T : notnull, new()
{
return PopulateDataDefinition(obj, (IDeserializedDefinition) definition, skipHook);
}
public DeserializationResult PopulateDataDefinition(object obj, IDeserializedDefinition definition, bool skipHook = false)
{
if (!TryGetDefinition(obj.GetType(), out var dataDefinition))
throw new ArgumentException($"Provided Type is not a data definition ({obj.GetType()})");
if (obj is IPopulateDefaultValues populateDefaultValues)
{
populateDefaultValues.PopulateDefaultValues();
}
var res = dataDefinition.Populate(obj, definition.Mapping);
if (!skipHook && res.RawValue is ISerializationHooks serializationHooksAfter)
{
serializationHooksAfter.AfterDeserialization();
}
return res;
}
internal DataDefinition? GetDefinition(Type type)
{
return DataDefinitions.TryGetValue(type, out var dataDefinition)
@@ -344,7 +310,7 @@ namespace Robust.Shared.Serialization.Manager
: null;
}
private bool TryGetDefinition(Type type, [NotNullWhen(true)] out DataDefinition? dataDefinition)
internal bool TryGetDefinition(Type type, [NotNullWhen(true)] out DataDefinition? dataDefinition)
{
dataDefinition = GetDefinition(type);
return dataDefinition != null;

View File

@@ -1,116 +1 @@
#nullable enable
using System;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
namespace Robust.Shared.Serialization.Manager
{
public static class SerializationManagerReadExtensions
{
public static T ReadValueOrThrow<T>(
this ISerializationManager manager,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
return manager.ReadValue<T>(node, context, skipHook) ?? throw new NullReferenceException();
}
public static T ReadValueOrThrow<T>(
this ISerializationManager manager,
Type type,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
return manager.ReadValueCast<T>(type, node, context, skipHook) ?? throw new NullReferenceException();
}
public static object ReadValueOrThrow(
this ISerializationManager manager,
Type type,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
return manager.ReadValue(type, node, context, skipHook) ?? throw new NullReferenceException();
}
public static (DeserializationResult result, object? value) ReadWithValue(
this ISerializationManager manager,
Type type, DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
var result = manager.Read(type, node, context, skipHook);
return (result, result.RawValue);
}
public static (DeserializationResult result, T? value) ReadWithValue<T>(
this ISerializationManager manager,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
var result = manager.Read(typeof(T), node, context, skipHook);
if (result.RawValue == null)
{
return (result, default);
}
return (result, (T) result.RawValue);
}
public static (DeserializationResult result, T? value) ReadWithValueCast<T>(
this ISerializationManager manager,
Type type,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
var result = manager.Read(type, node, context, skipHook);
if (result.RawValue == null)
{
return (result, default);
}
return (result, (T) result.RawValue);
}
public static (T value, DeserializationResult result) ReadWithValueOrThrow<T>(
this ISerializationManager manager,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
var result = manager.Read(typeof(T), node, context, skipHook);
if (result.RawValue == null)
{
throw new NullReferenceException();
}
return ((T) result.RawValue, result);
}
public static (T value, DeserializationResult result) ReadWithValueOrThrow<T>(
this ISerializationManager manager,
Type type,
DataNode node,
ISerializationContext? context = null,
bool skipHook = false)
{
var result = manager.Read(type, node, context, skipHook);
if (result.RawValue == null)
{
throw new NullReferenceException();
}
return ((T) result.RawValue, result);
}
}
}

View File

@@ -15,10 +15,14 @@ namespace Robust.Shared.Serialization.Markdown
End = end;
}
public abstract bool IsEmpty { get; }
public abstract DataNode Copy();
public abstract DataNode? Except(DataNode node);
public abstract DataNode PushInheritance(DataNode parent);
public override bool Equals(object? obj)
{
if (obj is not DataNode other)
@@ -45,9 +49,16 @@ namespace Robust.Shared.Serialization.Markdown
public abstract T? Except(T node);
public abstract T PushInheritance(T node);
public override DataNode? Except(DataNode node)
{
return node is not T tNode ? throw new InvalidNodeTypeException() : Except(tNode);
}
public override DataNode PushInheritance(DataNode parent)
{
return parent is not T tNode ? throw new InvalidNodeTypeException() : PushInheritance(tNode);
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -9,7 +10,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Serialization.Markdown.Mapping
{
public sealed class MappingDataNode : DataNode<MappingDataNode>
public sealed class MappingDataNode : DataNode<MappingDataNode>, IDictionary<DataNode, DataNode>
{
// To fetch nodes by key name with YAML, we NEED a YamlScalarNode.
// We use a thread local one to avoid allocating one every fetch, since we just replace the inner value.
@@ -51,6 +52,7 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
set => Add(new ValueDataNode(index), value);
}
//todo paul i dont think this is thread-safe
private static ValueDataNode GetFetchNode(string key)
{
var node = FetchNode.Value!;
@@ -64,16 +66,41 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return this;
}
public bool ContainsKey(DataNode key) => _children.ContainsKey(key);
bool IDictionary<DataNode, DataNode>.Remove(DataNode key) => _children.Remove(key);
public bool TryGetValue(DataNode key, [NotNullWhen(true)] out DataNode? value) => TryGet(key, out value);
public DataNode this[DataNode key]
{
get => _children[key];
set => _children[key] = value;
}
public ICollection<DataNode> Keys => _children.Keys;
public ICollection<DataNode> Values => _children.Values;
public DataNode Get(DataNode key)
{
return _children[key];
}
public T Get<T>(DataNode key) where T : DataNode
{
return (T) Get(key);
}
public DataNode Get(string key)
{
return Get(GetFetchNode(key));
}
public T Get<T>(string key) where T : DataNode
{
return Get<T>(GetFetchNode(key));
}
public bool TryGet(DataNode key, [NotNullWhen(true)] out DataNode? node)
{
if (_children.TryGetValue(key, out node))
@@ -85,11 +112,24 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return false;
}
public bool TryGet<T>(DataNode key, [NotNullWhen(true)] out T? node) where T : DataNode
{
node = null;
if (!TryGet(key, out var rawNode)) return false;
node = (T) rawNode;
return true;
}
public bool TryGet(string key, [NotNullWhen(true)] out DataNode? node)
{
return TryGet(GetFetchNode(key), out node);
}
public bool TryGet<T>(string key, [NotNullWhen(true)] out T? node) where T : DataNode
{
return TryGet(GetFetchNode(key), out node);
}
public bool Has(DataNode key)
{
return _children.ContainsKey(key);
@@ -100,6 +140,8 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return Has(GetFetchNode(key));
}
void IDictionary<DataNode, DataNode>.Add(DataNode key, DataNode value) => _children.Add(key, value);
public MappingDataNode Remove(DataNode key)
{
_children.Remove(key);
@@ -146,6 +188,8 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return newMapping;
}
public override bool IsEmpty => _children.Count == 0;
public override MappingDataNode Copy()
{
var newMapping = new MappingDataNode()
@@ -192,6 +236,21 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return mappingNode;
}
public override MappingDataNode PushInheritance(MappingDataNode node)
{
var newNode = Copy();
foreach (var (key, val) in node)
{
if(newNode.Has(key)) continue;
newNode[key.Copy()] = val.Copy();
}
return newNode;
}
public IEnumerator<KeyValuePair<DataNode, DataNode>> GetEnumerator() => _children.GetEnumerator();
public override int GetHashCode()
{
var code = new HashCode();
@@ -203,5 +262,26 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return code.ToHashCode();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(KeyValuePair<DataNode, DataNode> item) => _children.Add(item.Key, item.Value);
public void Clear() => _children.Clear();
public bool Contains(KeyValuePair<DataNode, DataNode> item) =>
((IDictionary<DataNode, DataNode>) _children).Contains(item);
public void CopyTo(KeyValuePair<DataNode, DataNode>[] array, int arrayIndex) =>
((IDictionary<DataNode, DataNode>) _children).CopyTo(array, arrayIndex);
public bool Remove(KeyValuePair<DataNode, DataNode> item) =>
((IDictionary<DataNode, DataNode>) _children).Remove(item);
public int Count => _children.Count;
public bool IsReadOnly => false;
}
}

View File

@@ -1,11 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Robust.Shared.Serialization.Markdown.Value;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Serialization.Markdown.Sequence
{
public sealed class SequenceDataNode : DataNode<SequenceDataNode>
public sealed class SequenceDataNode : DataNode<SequenceDataNode>, IList<DataNode>
{
private readonly List<DataNode> _nodes = new();
@@ -65,23 +66,44 @@ namespace Robust.Shared.Serialization.Markdown.Sequence
public IReadOnlyList<DataNode> Sequence => _nodes;
public DataNode this[int index] => _nodes[index];
public int IndexOf(DataNode item) => _nodes.IndexOf(item);
public void Insert(int index, DataNode item) => _nodes.Insert(index, item);
public void RemoveAt(int index) => _nodes.RemoveAt(index);
public DataNode this[int index]
{
get => _nodes[index];
set => _nodes[index] = value;
}
public void Add(DataNode node)
{
_nodes.Add(node);
}
public void Remove(DataNode node)
public void Clear() => _nodes.Clear();
public bool Contains(DataNode item) => _nodes.Contains(item);
public void CopyTo(DataNode[] array, int arrayIndex) => _nodes.CopyTo(array, arrayIndex);
public bool Remove(DataNode node)
{
_nodes.Remove(node);
return _nodes.Remove(node);
}
public int Count => _nodes.Count;
public bool IsReadOnly => false;
public T Cast<T>(int index) where T : DataNode
{
return (T) this[index];
}
public override bool IsEmpty => _nodes.Count == 0;
public override SequenceDataNode Copy()
{
var newSequence = new SequenceDataNode()
@@ -99,6 +121,8 @@ namespace Robust.Shared.Serialization.Markdown.Sequence
return newSequence;
}
public IEnumerator<DataNode> GetEnumerator() => _nodes.GetEnumerator();
public override int GetHashCode()
{
var code = new HashCode();
@@ -110,6 +134,11 @@ namespace Robust.Shared.Serialization.Markdown.Sequence
return code.ToHashCode();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override SequenceDataNode? Except(SequenceDataNode node)
{
var newList = new List<DataNode>();
@@ -130,5 +159,16 @@ namespace Robust.Shared.Serialization.Markdown.Sequence
return null;
}
public override SequenceDataNode PushInheritance(SequenceDataNode node)
{
var newNode = Copy();
foreach (var val in node)
{
newNode.Add(val.Copy());
}
return newNode;
}
}
}

View File

@@ -1,9 +1,13 @@
using System.Globalization;
using JetBrains.Annotations;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Serialization.Markdown.Value
{
public sealed class ValueDataNode : DataNode<ValueDataNode>
{
public ValueDataNode() : this(string.Empty) {}
public ValueDataNode(string value) : base(NodeMark.Invalid, NodeMark.Invalid)
{
Value = value;
@@ -17,6 +21,8 @@ namespace Robust.Shared.Serialization.Markdown.Value
public string Value { get; set; }
public override bool IsEmpty => Value == string.Empty;
public override ValueDataNode Copy()
{
return new(Value)
@@ -32,6 +38,11 @@ namespace Robust.Shared.Serialization.Markdown.Value
return node.Value == Value ? null : Copy();
}
public override ValueDataNode PushInheritance(ValueDataNode node)
{
return Copy();
}
public override bool Equals(object? obj)
{
return obj is ValueDataNode node && node.Value == Value;
@@ -46,5 +57,23 @@ namespace Robust.Shared.Serialization.Markdown.Value
{
return Value;
}
[Pure]
public int AsInt()
{
return int.Parse(Value, CultureInfo.InvariantCulture);
}
[Pure]
public float AsFloat()
{
return float.Parse(Value, CultureInfo.InvariantCulture);
}
[Pure]
public bool AsBool()
{
return bool.Parse(Value);
}
}
}

View File

@@ -4,7 +4,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
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.Markdown.Value;
@@ -15,10 +14,10 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
[TypeSerializer]
public sealed class AngleSerializer : ITypeSerializer<Angle, ValueDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
public Angle Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
ISerializationContext? context = null, Angle value = default)
{
var nodeContents = node.Value;
@@ -27,7 +26,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
CultureInfo.InvariantCulture))
: Angle.FromDegrees(double.Parse(nodeContents, CultureInfo.InvariantCulture));
return new DeserializedValue<Angle>(angle);
return angle;
}
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,

View File

@@ -4,7 +4,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
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.Markdown.Value;
@@ -15,10 +14,10 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
[TypeSerializer]
public sealed class Box2Serializer : ITypeSerializer<Box2, ValueDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
public Box2 Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
ISerializationContext? context = null, Box2 value = default)
{
var args = node.Value.Split(',');
@@ -32,7 +31,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
var r = float.Parse(args[2], CultureInfo.InvariantCulture);
var t = float.Parse(args[3], CultureInfo.InvariantCulture);
return new DeserializedValue<Box2>(new Box2(l, b, r, t));
return new Box2(l, b, r, t);
}
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,

View File

@@ -3,7 +3,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
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.Markdown.Value;
@@ -14,16 +13,16 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
[TypeSerializer]
public sealed class ColorSerializer : ITypeSerializer<Color, ValueDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
public Color Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
ISerializationContext? context = null, Color value = default)
{
var deserializedColor = Color.TryFromName(node.Value, out var color)
? color :
Color.FromHex(node.Value);
return new DeserializedValue<Color>(deserializedColor);
return deserializedColor;
}
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,

View File

@@ -7,29 +7,28 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
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.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Utility;
using static Robust.Shared.Prototypes.EntityPrototype;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations
{
[TypeSerializer]
public sealed class ComponentRegistrySerializer : ITypeSerializer<ComponentRegistry, SequenceDataNode>
public sealed class ComponentRegistrySerializer : ITypeSerializer<ComponentRegistry, SequenceDataNode>, ITypeInheritanceHandler<ComponentRegistry, SequenceDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager,
public ComponentRegistry Read(ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
ISerializationContext? context = null, ComponentRegistry? components = null)
{
var factory = dependencies.Resolve<IComponentFactory>();
var components = new ComponentRegistry();
var mappings = new Dictionary<DeserializationResult, DeserializationResult>();
components ??= new ComponentRegistry();
foreach (var componentMapping in node.Sequence.Cast<MappingDataNode>())
{
@@ -59,10 +58,9 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
copy.Remove("type");
var type = factory.GetRegistration(compType).Type;
var read = serializationManager.ReadWithValueOrThrow<IComponent>(type, copy, skipHook: skipHook);
var read = (IComponent)serializationManager.Read(type, copy, skipHook: skipHook)!;
components[compType] = read.value;
mappings.Add(new DeserializedValue<string>(compType), read.result);
components[compType] = read;
}
var referenceTypes = new List<Type>();
@@ -82,7 +80,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
}
}
return new DeserializedComponentRegistry(components, mappings);
return components;
}
public ValidationNode Validate(ISerializationManager serializationManager,
@@ -179,5 +177,46 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
return target;
}
public SequenceDataNode PushInheritance(ISerializationManager serializationManager, SequenceDataNode child,
SequenceDataNode parent,
IDependencyCollection dependencies, ISerializationContext context)
{
var componentFactory = dependencies.Resolve<IComponentFactory>();
var newCompReg = child.Copy();
var newCompRegDict = ToTypeIndexedDictionary(newCompReg, componentFactory);
var parentDict = ToTypeIndexedDictionary(parent, componentFactory);
foreach (var (reg, mapping) in parentDict)
{
if (newCompRegDict.TryFirstOrNull(childReg => reg.References.Any(x => childReg.Key.References.Contains(x)), out var entry))
{
newCompReg[entry.Value.Value] = serializationManager.PushCompositionWithGenericNode(reg.Type,
new[] { parent[mapping] }, newCompReg[entry.Value.Value], context);
}
else
{
newCompReg.Add(parent[mapping]);
newCompRegDict[reg] = newCompReg.Count-1;
}
}
return newCompReg;
}
private Dictionary<IComponentRegistration, int> ToTypeIndexedDictionary(SequenceDataNode node, IComponentFactory componentFactory)
{
var dict = new Dictionary<IComponentRegistration, int>();
for (var i = 0; i < node.Count; i++)
{
var mapping = (MappingDataNode)node[i];
var type = mapping.Get<ValueDataNode>("type").Value;
var availability = componentFactory.GetComponentAvailability(type);
if(availability == ComponentAvailability.Ignore) continue;
dict.Add(componentFactory.GetRegistration(type), i);
}
return dict;
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using Robust.Shared.IoC;
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.Markdown.Value;
@@ -18,11 +17,11 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom
return Enum.TryParse(constType, node.Value, out _) ? new ValidatedValueNode(node) : new ErrorNode(node, "Failed parsing constant.", false);
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public int Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, int value = default)
{
var constType = serializationManager.GetConstantTypeFromTag(typeof(TTag));
return new DeserializedValue((int) Enum.Parse(constType, node.Value));
return (int) Enum.Parse(constType, node.Value);
}
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,

View File

@@ -1,7 +1,6 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -19,11 +18,11 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom
return Enum.TryParse(flagType, node.Value, out _) ? new ValidatedValueNode(node) : new ErrorNode(node, "Failed parsing flag.", false);
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public int Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, int value = default)
{
var flagType = serializationManager.GetFlagTypeFromTag(typeof(TTag));
return new DeserializedValue((int)Enum.Parse(flagType, node.Value));
return (int)Enum.Parse(flagType, node.Value);
}
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,
@@ -80,8 +79,8 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom
return new ValidatedValueNode(node);
}
public DeserializationResult Read(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public int Read(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, int value = default)
{
var flagType = serializationManager.GetFlagTypeFromTag(typeof(TTag));
var flags = 0;
@@ -92,7 +91,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom
flags |= (int) Enum.Parse(flagType, valueDataNode.Value);
}
return new DeserializedValue(flags);
return flags;
}
}
}

View File

@@ -3,7 +3,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
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.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -62,25 +61,28 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return Validate(serializationManager, node, dependencies, context);
}
DeserializationResult ITypeReader<Dictionary<string, TValue>, MappingDataNode>.Read(
Dictionary<string, TValue> ITypeReader<Dictionary<string, TValue>, MappingDataNode>.Read(
ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context)
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context,
Dictionary<string, TValue>? value)
{
return _dictionarySerializer.Read(serializationManager, node, dependencies, skipHook, context);
return _dictionarySerializer.Read(serializationManager, node, dependencies, skipHook, context, value);
}
DeserializationResult ITypeReader<SortedDictionary<string, TValue>, MappingDataNode>.Read(
SortedDictionary<string, TValue> ITypeReader<SortedDictionary<string, TValue>, MappingDataNode>.Read(
ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context)
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context,
SortedDictionary<string, TValue>? value)
{
return _dictionarySerializer.Read(serializationManager, node, dependencies, skipHook, context);
return ((ITypeReader<SortedDictionary<string, TValue>, MappingDataNode>)_dictionarySerializer).Read(serializationManager, node, dependencies, skipHook, context, value);
}
DeserializationResult ITypeReader<IReadOnlyDictionary<string, TValue>, MappingDataNode>.Read(
IReadOnlyDictionary<string, TValue> ITypeReader<IReadOnlyDictionary<string, TValue>, MappingDataNode>.Read(
ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context)
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context,
IReadOnlyDictionary<string, TValue>? value)
{
return _dictionarySerializer.Read(serializationManager, node, dependencies, skipHook, context);
return ((ITypeReader<IReadOnlyDictionary<string, TValue>, MappingDataNode>)_dictionarySerializer).Read(serializationManager, node, dependencies, skipHook, context, value);
}
public DataNode Write(ISerializationManager serializationManager, Dictionary<string, TValue> value,

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -22,27 +22,26 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return ValidateInternal(serializationManager, node, dependencies, context);
}
public DeserializationResult Read(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public ImmutableList<string> Read(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null,
ImmutableList<string>? rawValue = null)
{
if(rawValue != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(ImmutableList<string>)}. Ignoring...");
var builder = ImmutableList.CreateBuilder<string>();
var mappings = new List<DeserializationResult>();
foreach (var dataNode in node.Sequence)
{
var result = _prototypeSerializer.Read(
builder.Add(_prototypeSerializer.Read(
serializationManager,
(ValueDataNode) dataNode,
dependencies,
skipHook,
context);
builder.Add((string) result.RawValue!);
mappings.Add(result);
context));
}
return new DeserializedCollection<ImmutableList<string>, string>(builder.ToImmutable(), mappings,
ImmutableList.CreateRange);
return builder.ToImmutable();
}
public DataNode Write(ISerializationManager serializationManager, ImmutableList<string> value, bool alwaysWrite = false,

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -24,31 +24,29 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return ValidateInternal(serializationManager, node, dependencies, context);
}
DeserializationResult ITypeReader<IReadOnlyCollection<string>, SequenceDataNode>.Read(
IReadOnlyCollection<string> ITypeReader<IReadOnlyCollection<string>, SequenceDataNode>.Read(
ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
ISerializationContext? context, IReadOnlyCollection<string>? rawValue)
{
if(rawValue != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(IReadOnlyCollection<string>)}. Ignoring...");
var list = new List<string>();
var mappings = new List<DeserializationResult>();
foreach (var dataNode in node.Sequence)
{
var result = _prototypeSerializer.Read(
list.Add(_prototypeSerializer.Read(
serializationManager,
(ValueDataNode) dataNode,
dependencies,
skipHook,
context);
list.Add((string) result.RawValue!);
mappings.Add(result);
context));
}
return new DeserializedCollection<List<string>, string>(list, mappings,
elements => new List<string>(elements));
return list;
}
DataNode ITypeWriter<IReadOnlyCollection<string>>.Write(

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -35,31 +35,29 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return new List<string>(source);
}
DeserializationResult ITypeReader<IReadOnlyList<string>, SequenceDataNode>.Read(
IReadOnlyList<string> ITypeReader<IReadOnlyList<string>, SequenceDataNode>.Read(
ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
ISerializationContext? context, IReadOnlyList<string>? rawValue)
{
if(rawValue != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(IReadOnlyList<string>)}. Ignoring...");
var list = new List<string>();
var mappings = new List<DeserializationResult>();
foreach (var dataNode in node.Sequence)
{
var result = _prototypeSerializer.Read(
list.Add(_prototypeSerializer.Read(
serializationManager,
(ValueDataNode) dataNode,
dependencies,
skipHook,
context);
list.Add((string) result.RawValue!);
mappings.Add(result);
context));
}
return new DeserializedCollection<IReadOnlyList<string>, string>(list, mappings,
elements => new List<string>(elements));
return list;
}
ValidationNode ITypeValidator<IReadOnlyList<string>, SequenceDataNode>.Validate(

View File

@@ -2,7 +2,6 @@
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -62,31 +61,25 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return ValidateInternal(serializationManager, node, dependencies, context);
}
DeserializationResult ITypeReader<List<string>, SequenceDataNode>.Read(
ISerializationManager serializationManager,
List<string> ITypeReader<List<string>, SequenceDataNode>.Read(ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
ISerializationContext? context, List<string>? list)
{
var list = new List<string>();
var mappings = new List<DeserializationResult>();
list ??= new List<string>();
foreach (var dataNode in node.Sequence)
{
var result = _prototypeSerializer.Read(
list.Add(_prototypeSerializer.Read(
serializationManager,
(ValueDataNode) dataNode,
dependencies,
skipHook,
context);
list.Add((string) result.RawValue!);
mappings.Add(result);
context));
}
return new DeserializedCollection<List<string>, string>(list, mappings,
elements => new List<string>(elements));
return list;
}
DataNode ITypeWriter<List<string>>.Write(

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
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.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -38,9 +38,13 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return new ValidatedSequenceNode(list);
}
public DeserializationResult Read(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public PrototypeFlags<T> Read(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null,
PrototypeFlags<T>? rawValue = null)
{
if(rawValue != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
var flags = new List<string>(node.Sequence.Count);
foreach (var dataNode in node.Sequence)
@@ -51,7 +55,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
flags.Add(value.Value);
}
return new DeserializedValue<PrototypeFlags<T>>(new PrototypeFlags<T>(flags));
return new PrototypeFlags<T>(flags);
}
public DataNode Write(ISerializationManager serializationManager, PrototypeFlags<T> value, bool alwaysWrite = false,
@@ -72,10 +76,14 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return serializationManager.ValidateNodeWith<string, PrototypeIdSerializer<T>, ValueDataNode>(node, context);
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public PrototypeFlags<T> Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null,
PrototypeFlags<T>? rawValue = null)
{
return new DeserializedValue<PrototypeFlags<T>>(new PrototypeFlags<T>(node.Value));
if(rawValue != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
return new PrototypeFlags<T>(node.Value);
}
}
}

View File

@@ -1,7 +1,6 @@
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
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.Markdown.Value;
@@ -14,15 +13,15 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
return dependencies.Resolve<IPrototypeManager>().HasIndex<TPrototype>(node.Value)
return dependencies.Resolve<IPrototypeManager>().HasMapping<TPrototype>(node.Value)
? new ValidatedValueNode(node)
: new ErrorNode(node, $"PrototypeID {node.Value} for type {typeof(TPrototype)} not found");
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public string Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, string? value = default)
{
return new DeserializedValue<string>(node.Value);
return node.Value;
}
public DataNode Write(ISerializationManager serializationManager, string value, bool alwaysWrite = false,

View File

@@ -2,7 +2,6 @@ using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -33,26 +32,23 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
return new ValidatedSequenceNode(list);
}
public DeserializationResult Read(ISerializationManager serializationManager, SequenceDataNode node, IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public HashSet<string> Read(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null,
HashSet<string>? set = null)
{
var set = new HashSet<string>();
var mappings = new List<DeserializationResult>();
set ??= new HashSet<string>();
foreach (var dataNode in node.Sequence)
{
var result = _prototypeSerializer.Read(
set.Add(_prototypeSerializer.Read(
serializationManager,
(ValueDataNode) dataNode,
dependencies,
skipHook,
context);
set.Add((string) result.RawValue!);
mappings.Add(result);
context));
}
return new DeserializedCollection<HashSet<string>, string>(set, mappings,
elements => new HashSet<string>(elements));
return set;
}
public DataNode Write(ISerializationManager serializationManager, HashSet<string> value, bool alwaysWrite = false, ISerializationContext? context = null)

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