mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
30 Commits
reactjs-su
...
v0.12.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1eb7393a60 | ||
|
|
4cf88507c2 | ||
|
|
3565d8b321 | ||
|
|
7094c29b2e | ||
|
|
63004b270f | ||
|
|
6714a99b38 | ||
|
|
4c3b8df1e7 | ||
|
|
4bb695121f | ||
|
|
09fd47c421 | ||
|
|
fd1e25c584 | ||
|
|
6bb66ae70e | ||
|
|
cc82d6b1d9 | ||
|
|
956be749b6 | ||
|
|
6585a00608 | ||
|
|
c0525f710f | ||
|
|
d3672807d2 | ||
|
|
60f18d5f36 | ||
|
|
e72d3de256 | ||
|
|
ba9846b9c4 | ||
|
|
09586284dc | ||
|
|
a1ee4374b2 | ||
|
|
4de6f25f11 | ||
|
|
582d8a5587 | ||
|
|
ec53b04f99 | ||
|
|
950fc94408 | ||
|
|
58d12e6e09 | ||
|
|
94323005c4 | ||
|
|
4989842057 | ||
|
|
80172636a8 | ||
|
|
8491f7be24 |
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
56
Robust.Client/Audio/Midi/IMidiManager.cs
Normal file
56
Robust.Client/Audio/Midi/IMidiManager.cs
Normal 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();
|
||||
}
|
||||
174
Robust.Client/Audio/Midi/IMidiRenderer.cs
Normal file
174
Robust.Client/Audio/Midi/IMidiRenderer.cs
Normal 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();
|
||||
}
|
||||
155
Robust.Client/Audio/Midi/MidiManager.Conversion.cs
Normal file
155
Robust.Client/Audio/Midi/MidiManager.Conversion.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
218
Robust.Server/Maps/GridSerializer.cs
Normal file
218
Robust.Server/Maps/GridSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
64
Robust.Shared/Audio/Midi/RobustMidiCommand.cs
Normal file
64
Robust.Shared/Audio/Midi/RobustMidiCommand.cs
Normal 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,
|
||||
}
|
||||
94
Robust.Shared/Audio/Midi/RobustMidiEvent.cs
Normal file
94
Robust.Shared/Audio/Midi/RobustMidiEvent.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 { }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -410,7 +410,7 @@ public partial class EntitySystem
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
15
Robust.Shared/GameObjects/Systems/DebugExceptionSystem.cs
Normal file
15
Robust.Shared/GameObjects/Systems/DebugExceptionSystem.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Robust.Shared.Serialization.Manager.Result
|
||||
{
|
||||
public interface IDeserializedDefinition
|
||||
{
|
||||
DeserializedFieldEntry[] Mapping { get; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user