Compare commits

...

35 Commits

Author SHA1 Message Date
Pieter-Jan Briers
8cc2a17444 Version: 141.2.0 2023-07-28 22:16:59 +02:00
Pieter-Jan Briers
9c64fbfce2 Fix protocol abuse exception. 2023-07-27 19:30:39 +02:00
metalgearsloth
0218c4b969 Version: 141.1.0 2023-07-27 18:22:48 +10:00
Vordenburg
cd72523701 Add CollisionLayerChangeEvent (#4147) 2023-07-27 18:20:29 +10:00
metalgearsloth
76bb9b4b19 Run MapInit on pmanager entities (#4196) 2023-07-27 06:49:09 +12:00
metalgearsloth
afdfbba312 Version: 141.0.0 2023-07-26 22:41:12 +10:00
metalgearsloth
8b4925863e Cull Component.Initialize (#4191) 2023-07-26 22:37:45 +10:00
Pieter-Jan Briers
e1597da4c7 Enable EXCEPTION_TOLERANCE on Tools.
This wasn't the case yet??? Whoops.
2023-07-24 20:52:39 +02:00
Pieter-Jan Briers
8375a4038b Throw error if creation of buffered audio source fails.
This can happen if we're out of audio streams. Before, we just kinda pretended like everything was OK, which easily caused crash bugs in e.g. MIDI.

Ideally the audio engine would be less terrible and this could be handled better than "throw new Exception()", but I'm fixing a stack overflow here alright?
2023-07-24 20:52:11 +02:00
Pieter-Jan Briers
8270442d66 Hard exit server on double ^C. 2023-07-23 17:44:20 +02:00
Pieter-Jan Briers
85e1920b95 Version: 140.0.0 2023-07-23 15:41:44 +02:00
Pieter-Jan Briers
5347eb3350 Replay recording API improvements. (#4193) 2023-07-23 15:36:35 +02:00
metalgearsloth
e4a14d1ec8 Start MapGrid ECS (#4185) 2023-07-23 20:50:23 +10:00
metalgearsloth
c52db4d3f2 Version: 139.0.0 2023-07-23 16:13:06 +10:00
metalgearsloth
89f78d76ab Cull Component.Startup (#4190) 2023-07-23 16:04:37 +10:00
metalgearsloth
bbc4668f9c Version: 138.1.0 2023-07-18 21:41:40 +10:00
metalgearsloth
ce4016965e Add NoLerp methods for rotation (#4186) 2023-07-18 21:40:09 +10:00
Pieter-Jan Briers
21e74c9881 Fix ordering of control AnimationCompleted event.
This was changed recently, and it caused exceptions in SS14 (wire hacking animations).
2023-07-18 01:04:49 +02:00
Vera Aguilera Puerto
aa52e8c2ef Version: 138.0.0 2023-07-16 21:09:48 +02:00
Vera Aguilera Puerto
e106d3f72b MidiRenderer master/puppet and various improvements/cleanup (#4184) 2023-07-16 21:08:57 +02:00
metalgearsloth
8eae802fb6 Version: 137.1.0 2023-07-17 00:57:35 +10:00
Pieter-Jan Briers
681feaf0c7 Use relative time in PrecisionSleepWindowsHighResolution
Duh.
2023-07-15 23:38:12 +02:00
Pieter-Jan Briers
0a5a214a06 Precision sleep implementation for Linux (nanosleep). 2023-07-15 23:28:55 +02:00
Pieter-Jan Briers
d80be16f6c Fix sys.precise_sleep being interpreted the wrong way around. 2023-07-15 23:28:26 +02:00
Pieter-Jan Briers
d967bc9fdc Speed up DirLoader.DirLoader on Windows.
New FileHelper.TryOpenFileRead that doesn't throw if the file doesn't exist.

Also used it in a couple other spots.
2023-07-15 19:08:37 +02:00
Pieter-Jan Briers
177ca6b627 Sandboxing performance improvements.
Don't leave file handles dangling.
Prefetch verifying assembly images to speed stuff up.
2023-07-15 18:09:57 +02:00
Pieter-Jan Briers
3c262afaa4 Why would you put an attribute inside an ifdef like that, god. 2023-07-15 16:14:03 +02:00
Pieter-Jan Briers
bce2901b0f Remove manual Windows P/Invokes in favor of TerraFX. 2023-07-15 15:41:34 +02:00
Pieter-Jan Briers
c392d4f996 Precise, time period-independent timing for Windows game loop. 2023-07-15 15:22:52 +02:00
Pieter-Jan Briers
d7ee2bccd7 Use TerraFX.Interop.Windows in Shared. 2023-07-15 15:20:32 +02:00
Pieter-Jan Briers
4e816fa5e7 Add ModUpdateLevel.InputPostEngine 2023-07-15 15:09:17 +02:00
Pieter-Jan Briers
545e55055e BQL paused handling changes (#4099) 2023-07-15 19:21:36 +10:00
metalgearsloth
6b059ed356 Version: 137.0.0 2023-07-13 20:22:47 +10:00
metalgearsloth
b69b4fd8fe Kill comp getstate / handlestate (#4183) 2023-07-13 20:19:58 +10:00
metalgearsloth
cb63499ec9 Add EntityQuery<MetaDataComponent> to EntityManager (#4166) 2023-07-13 20:14:47 +10:00
105 changed files with 2836 additions and 1683 deletions

View File

@@ -23,7 +23,7 @@
<PropertyGroup Condition="'$(FullRelease)' != 'True'">
<DefineConstants>$(DefineConstants);DEVELOPMENT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<PropertyGroup Condition="'$(Configuration)' == 'Release' Or '$(Configuration)' == 'Tools'">
<DefineConstants>$(DefineConstants);EXCEPTION_TOLERANCE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(EnableClientScripting)' == 'True'">

View File

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

View File

@@ -54,6 +54,114 @@ END TEMPLATE-->
*None yet*
## 141.2.0
### Other
* Fix bug in `NetManager` that allowed exception spam through protocol abuse.
## 141.1.0
### New features
* MapInitEvent is run clientside for placementmanager entities to predict entity appearances.
* Add CollisionLayerChangeEvent for physics fixtures.
## 141.0.0
### Breaking changes
* Component.Initialize has been fully replaced with the Eventbus.
### Bugfixes
* Fixed potential crashes if buffered audio sources (e.g. MIDI) fail to create due to running out of audio streams.
### Other
* Pressing `^C` twice on the server will now cause it to hard-exit immediately.
* `Tools` now has `EXCEPTION_TOLERANCE` enabled.
## 140.0.0
### Breaking changes
* `IReplayRecordingManager.RecordingFinished` now takes a `ReplayRecordingFinished` object as argument.
* `IReplayRecordingManager.GetReplayStats` now returns a `ReplayRecordingStats` struct instead of a tuple. The units have also been normalized
### New features
* `IReplayRecordingManager` can now track a "state" object for an active recording.
* If the path given to `IReplayRecordingManager.TryStartRecording` is rooted, the base replay directory is ignored.
### Other
* `IReplayRecordingManager` no longer considers itself recording inside `RecordingFinished`.
* `IReplayRecordingManager.Initialize()` was moved to an engine-internal interface.
## 139.0.0
### Breaking changes
* Remove Component.Startup(), fully replacing it with the Eventbus.
## 138.1.0
### New features
* Add rotation methods to TransformSystem for no lerp.
### Bugfixes
* Fix AnimationCompleted ordering.
## 138.0.0
### Breaking changes
* Obsoleted unused `IMidiRenderer.VolumeBoost` property. Use `IMidiRenderer.VelocityOverride` instead.
* `IMidiRenderer.TrackedCoordinates` is now a `MapCoordinates`.
### New features
* Added `Master` property to `IMidiRenderer`, which allows it to copy all MIDI events from another renderer.
* Added `FilteredChannels` property to `IMidiRenderer`, which allows it to filter out notes from certain channels.
* Added `SystemReset` helper property to `IMidiRenderer`, which allows you to easily send it a SystemReset MIDI message.
### Bugfixes
* Fixed some cases were `MidiRenderer` would not respect the `MidiBank` and `MidiProgram.
* Fixed user soundfonts not loading.
* Fixed `ItemList` item selection unselecting everything when in `Multiple` mode.
## 137.1.0
### New features
* Added BQL `paused` selector.
* `ModUpdateLevel.PostInput` allows running content code after network and async task processing.
### Other
* BQL `with` now includes paused entities.
* The game loop now times more accurately and avoids sleeping more than necessary.
* Sandboxing (and thus, client startup) should be much faster when ran from the launcher.
## 137.0.0
### Breaking changes
* Component network state handler methods have been fully deprecated and replaced with the eventbus event equivalents (ComponentGetState and ComponentHandleState).
## 136.0.1
### Bugfixes

View File

@@ -22,7 +22,7 @@ cmd-replay-skip-hint = Ticks or timespan (HH:MM:SS).
cmd-replay-set-time-desc = Jump forwards or backwards to some specific time.
cmd-replay-set-time-help = replay_set <tick or time>
cmd-replay-set-time-hint = Tick or timespan (HH:MM:SS), starting from
cmd-replay-set-time-hint = Tick or timespan (HH:MM:SS), starting from
cmd-replay-error-time = "{$time}" is not an integer or timespan.
cmd-replay-error-args = Wrong number of arguments.
@@ -33,7 +33,7 @@ cmd-replay-error-run-level = You cannot load a replay while connected to a serve
# Recording commands
cmd-replay-recording-start-desc = Starts a replay recording, optionally with some time limit.
cmd-replay-recording-start-help = Usage: replay_recording_start [name] [overwrite] [time limit]
cmd-replay-recording-start-help = Usage: replay_recording_start [name] [overwrite] [time limit]
cmd-replay-recording-start-success = Started recording a replay.
cmd-replay-recording-start-already-recording = Already recording a replay.
cmd-replay-recording-start-error = An error occurred while trying to start the recording.
@@ -48,7 +48,7 @@ cmd-replay-recording-stop-not-recording = Not currently recording a replay.
cmd-replay-recording-stats-desc = Displays information about the current replay recording.
cmd-replay-recording-stats-help = Usage: replay_recording_stats
cmd-replay-recording-stats-result = Duration: {$time} min, Ticks: {$ticks}, Size: {$size} mb, rate: {$rate} mb/min.
cmd-replay-recording-stats-result = Duration: {$time} min, Ticks: {$ticks}, Size: {$size} MB, rate: {$rate} MB/min.
# Time Control UI
@@ -56,4 +56,4 @@ replay-time-box-scrubbing-label = Dynamic Scrubbing
replay-time-box-replay-time-label = Recording Time: {$current} / {$end} ({$percentage}%)
replay-time-box-server-time-label = Server Time: {$current} / {$end}
replay-time-box-index-label = Index: {$current} / {$total}
replay-time-box-tick-label = Tick: {$current} / {$total}
replay-time-box-tick-label = Tick: {$current} / {$total}

View File

@@ -0,0 +1,75 @@
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
public class ComponentIteratorBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N;
public A[] Comps = default!;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.RegisterComponents(f => f.RegisterClass<A>())
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
Comps = new A[N+2];
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{
var uid = _entityManager.SpawnEntity(null, coords);
_entityManager.AddComponent<A>(uid);
}
}
[Benchmark]
public A[] ComponentStructEnumerator()
{
var query = _entityManager.EntityQueryEnumerator<A>();
var i = 0;
while (query.MoveNext(out var comp))
{
Comps[i] = comp;
i++;
}
return Comps;
}
[Benchmark]
public A[] ComponentIEnumerable()
{
var i = 0;
foreach (var comp in _entityManager.EntityQuery<A>())
{
Comps[i] = comp;
i++;
}
return Comps;
}
[ComponentProtoName("A")]
public sealed class A : Component
{
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Audio.Midi;
using Robust.Shared.GameObjects;
@@ -15,7 +17,6 @@ public enum MidiRendererStatus : byte
public interface IMidiRenderer : IDisposable
{
/// <summary>
/// The buffered audio source of this renderer.
/// </summary>
@@ -34,6 +35,7 @@ public interface IMidiRenderer : IDisposable
/// <summary>
/// This increases all note on velocities to 127.
/// </summary>
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
bool VolumeBoost { get; set; }
/// <summary>
@@ -94,6 +96,27 @@ public interface IMidiRenderer : IDisposable
/// </summary>
double SequencerTimeScale { get; }
/// <summary>
/// Whether this renderer will subscribe to another and copy its events.
/// See <see cref="FilteredChannels"/> to filter specific channels.
/// </summary>
IMidiRenderer? Master { get; set; }
// NOTE: Why is the properties below BitArray, you ask?
// Well see, MIDI 2.0 supports up to 256(!) channels as opposed to MIDI 1.0's meekly 16 channels...
// I'd like us to support MIDI 2.0 one day so I'm just future-proofing here. Also BitArray is cool!
/// <summary>
/// Allows you to filter out note events from certain channels.
/// Only NoteOn will be filtered.
/// </summary>
BitArray FilteredChannels { get; }
/// <summary>
/// Allows you to override all NoteOn velocities. Set to null to disable.
/// </summary>
byte? VelocityOverride { get; set; }
/// <summary>
/// Start listening for midi input.
/// </summary>
@@ -120,6 +143,11 @@ public interface IMidiRenderer : IDisposable
/// </summary>
void StopAllNotes();
/// <summary>
/// Reset renderer back to a clean state.
/// </summary>
void SystemReset();
/// <summary>
/// Clears all scheduled events.
/// </summary>
@@ -156,7 +184,7 @@ public interface IMidiRenderer : IDisposable
/// This is only used if <see cref="Mono"/> is set to True
/// and <see cref="TrackingEntity"/> is null.
/// </summary>
EntityCoordinates? TrackingCoordinates { get; set; }
MapCoordinates? TrackingCoordinates { get; set; }
MidiRendererState RendererState { get; }
@@ -164,7 +192,8 @@ public interface IMidiRenderer : IDisposable
/// Send a midi event for the renderer to play.
/// </summary>
/// <param name="midiEvent">The midi event to be played</param>
void SendMidiEvent(RobustMidiEvent midiEvent);
/// <param name="raiseEvent">Whether to raise an event for this event.</param>
void SendMidiEvent(RobustMidiEvent midiEvent, bool raiseEvent = true);
/// <summary>
/// Schedule a MIDI event to be played at a later time.
@@ -177,7 +206,7 @@ public interface IMidiRenderer : IDisposable
/// <summary>
/// Apply a certain state to the renderer.
/// </summary>
void ApplyState(MidiRendererState state);
void ApplyState(MidiRendererState state, bool filterChannels = false);
/// <summary>
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.

View File

@@ -3,20 +3,27 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using NFluidsynth;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Audio.Midi;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
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.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -26,6 +33,13 @@ internal sealed partial class MidiManager : IMidiManager
{
public const string SoundfontEnvironmentVariable = "ROBUST_SOUNDFONT_OVERRIDE";
private int _minRendererParallel;
private float _occlusionUpdateDelay;
private float _positionUpdateDelay;
[ViewVariables] private TimeSpan _nextOcclusionUpdate = TimeSpan.Zero;
[ViewVariables] private TimeSpan _nextPositionUpdate = TimeSpan.Zero;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceCacheInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -33,6 +47,9 @@ internal sealed partial class MidiManager : IMidiManager
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ILogManager _logger = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly IRuntimeLog _runtime = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
@@ -59,11 +76,10 @@ internal sealed partial class MidiManager : IMidiManager
}
}
[ViewVariables]
private readonly List<IMidiRenderer> _renderers = new();
[ViewVariables] private readonly List<IMidiRenderer> _renderers = new();
private bool _alive = true;
private Settings? _settings;
[ViewVariables] private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
@@ -115,7 +131,7 @@ internal sealed partial class MidiManager : IMidiManager
private bool _failedInitialize;
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _sawmill = default!;
private ISawmill _fluidsynthSawmill = default!;
private float _maxCastLength;
[ViewVariables(VVAccess.ReadWrite)]
@@ -130,20 +146,28 @@ internal sealed partial class MidiManager : IMidiManager
{
if (FluidsynthInitialized || _failedInitialize) return;
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
_volumeDirty = true;
}, true);
_cfgMan.OnValueChanged(CVars.MidiMinRendererParallel,
value => _minRendererParallel = value, true);
_cfgMan.OnValueChanged(CVars.MidiOcclusionUpdateDelay,
value => _occlusionUpdateDelay = value, true);
_cfgMan.OnValueChanged(CVars.MidiPositionUpdateDelay,
value => _positionUpdateDelay = value, true);
_midiSawmill = _logger.GetSawmill("midi");
#if DEBUG
_midiSawmill.Level = LogLevel.Debug;
#else
_midiSawmill.Level = LogLevel.Error;
#endif
_sawmill = _logger.GetSawmill("midi.fluidsynth");
_fluidsynthSawmill = _logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
if (!_resourceManager.UserData.Exists(CustomSoundfontDirectory))
@@ -167,8 +191,6 @@ internal sealed partial class MidiManager : IMidiManager
_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";
@@ -176,8 +198,11 @@ internal sealed partial class MidiManager : IMidiManager
_settings["audio.period-size"].IntValue = 4096;
_settings["midi.autoconnect"].IntValue = 1;
_settings["player.reset-synth"].IntValue = 0;
_settings["synth.midi-channels"].IntValue = Math.Clamp(RobustMidiEvent.MaxChannels, 16, 256);
_settings["synth.midi-bank-select"].StringValue = "gm";
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
_parallel.AddAndInvokeParallelCountChanged(UpdateParallelCount);
}
catch (Exception e)
{
@@ -195,6 +220,18 @@ internal sealed partial class MidiManager : IMidiManager
FluidsynthInitialized = true;
}
private void UpdateParallelCount()
{
if (_settings == null)
return;
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(_parallel.ParallelProcessCount) * 2048), 1, 65535);
_settings["synth.cpu-cores"].IntValue = Math.Clamp(_parallel.ParallelProcessCount, 1, 256);
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
}
private void OnRaycastLengthChanged(float value)
{
_maxCastLength = value;
@@ -211,7 +248,7 @@ internal sealed partial class MidiManager : IMidiManager
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
_ => LogLevel.Debug
};
_sawmill.Log(rLevel, message);
_fluidsynthSawmill.Log(rLevel, message);
}
public IMidiRenderer? GetNewRenderer(bool mono = true)
@@ -238,7 +275,7 @@ internal sealed partial class MidiManager : IMidiManager
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill);
_midiSawmill.Debug($"Loading soundfont {FallbackSoundfont}");
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
@@ -252,8 +289,8 @@ internal sealed partial class MidiManager : IMidiManager
try
{
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
renderer.LoadSoundfont(filepath);
_midiSawmill.Debug($"Loaded Linux soundfont {filepath}");
}
catch (Exception)
{
@@ -267,7 +304,7 @@ internal sealed partial class MidiManager : IMidiManager
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
{
_midiSawmill.Debug($"Loading soundfont {OsxSoundfont}");
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
renderer.LoadSoundfont(OsxSoundfont);
}
}
@@ -275,7 +312,7 @@ internal sealed partial class MidiManager : IMidiManager
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
{
_midiSawmill.Debug($"Loading soundfont {WindowsSoundfont}");
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
renderer.LoadSoundfont(WindowsSoundfont);
}
}
@@ -286,27 +323,31 @@ internal sealed partial class MidiManager : IMidiManager
{
if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
{
_midiSawmill.Debug($"Loading soundfont {soundfontOverride} from environment variable.");
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
renderer.LoadSoundfont(soundfontOverride);
}
}
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
_midiSawmill.Debug($"Loading soundfonts from {ContentCustomSoundfontDirectory}");
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading soundfont {file}");
_midiSawmill.Debug($"Loading content soundfont {file}");
renderer.LoadSoundfont(file.ToString());
}
var userDataPath = _resourceManager.UserData.RootDir == null
? CustomSoundfontDirectory
: new ResPath(_resourceManager.UserData.RootDir) / CustomSoundfontDirectory.ToRelativePath();
// Load every soundfont from the user data directory last, since those may override any other soundfont.
_midiSawmill.Debug($"Loading soundfonts from {{USERDATA}} {CustomSoundfontDirectory}");
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}/*").Item1;
_midiSawmill.Debug($"Loading soundfonts from user data directory {userDataPath}");
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
foreach (var file in enumerator)
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading soundfont {{USERDATA}} {file}");
_midiSawmill.Debug($"Loading user soundfont {file}");
renderer.LoadSoundfont(file.ToString());
}
@@ -336,72 +377,108 @@ internal sealed partial class MidiManager : IMidiManager
lock (_renderers)
{
foreach (var renderer in _renderers)
if (_renderers.Count == 0)
return;
var transQuery = _entityManager.GetEntityQuery<TransformComponent>();
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parallel.ParallelProcessCount };
if (_renderers.Count > _minRendererParallel)
{
if (renderer.Disposed)
continue;
if(_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
Parallel.ForEach(_renderers, opts, renderer => UpdateRenderer(renderer, transQuery, physicsQuery));
}
else
{
foreach (var renderer in _renderers)
{
renderer.Source.SetGlobal();
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 = pos.Position - _eyeManager.CurrentEye.Position.Position;
var occlusion = 0f;
if (sourceRelative.Length() > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
_eyeManager.CurrentEye.Position.Position,
sourceRelative.Normalized(),
OcclusionCollisionMask),
MathF.Min(sourceRelative.Length(), _maxCastLength),
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
if (!renderer.Source.SetPosition(pos.Position))
{
return;
}
if (trackingEntity)
{
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value);
renderer.Source.SetVelocity(vel);
}
}
else
{
renderer.Source.SetOcclusion(float.MaxValue);
UpdateRenderer(renderer, transQuery, physicsQuery);
}
}
}
if (_nextOcclusionUpdate < _timing.RealTime)
_nextOcclusionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_occlusionUpdateDelay));
if (_nextPositionUpdate < _timing.RealTime)
_nextPositionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_positionUpdateDelay));
_volumeDirty = false;
}
private void UpdateRenderer(IMidiRenderer renderer, EntityQuery<TransformComponent> transQuery,
EntityQuery<PhysicsComponent> physicsQuery)
{
try
{
if (renderer.Disposed)
return;
if (_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
{
renderer.Source.SetGlobal();
return;
}
if (_nextPositionUpdate < _timing.RealTime)
{
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
{
renderer.TrackingCoordinates = transQuery.GetComponent(renderer.TrackingEntity!.Value).MapPosition;
}
else if (renderer.TrackingCoordinates == null)
{
return;
}
if (!renderer.Source.SetPosition(renderer.TrackingCoordinates.Value.Position))
{
return;
}
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value,
xformQuery: transQuery, physicsQuery: physicsQuery);
renderer.Source.SetVelocity(vel);
}
if (renderer.TrackingCoordinates != null && renderer.TrackingCoordinates.Value.MapId == _eyeManager.CurrentMap)
{
if (_nextOcclusionUpdate >= _timing.RealTime)
return;
var pos = renderer.TrackingCoordinates.Value;
var sourceRelative = pos.Position - _eyeManager.CurrentEye.Position.Position;
var occlusion = 0f;
if (sourceRelative.Length() > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
_eyeManager.CurrentEye.Position.Position,
sourceRelative.Normalized(),
OcclusionCollisionMask),
MathF.Min(sourceRelative.Length(), _maxCastLength),
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
}
else
{
renderer.Source.SetOcclusion(float.MaxValue);
}
}
catch (Exception ex)
{
_runtime.LogException(ex, _midiSawmill.Name);
}
}
/// <summary>
/// Main method for the thread rendering the midi audio.
@@ -416,7 +493,12 @@ internal sealed partial class MidiManager : IMidiManager
{
var renderer = _renderers[i];
if (!renderer.Disposed)
{
if (renderer.Master is { Disposed: true })
renderer.Master = null;
renderer.Render();
}
else
{
renderer.InternalDispose();

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections;
using JetBrains.Annotations;
using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Shared.Asynchronous;
@@ -9,7 +11,6 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Logger = Robust.Shared.Log.Logger;
namespace Robust.Client.Audio.Midi;
@@ -21,14 +22,13 @@ internal sealed class MidiRenderer : IMidiRenderer
// TODO: Make this a replicated CVar in MidiManager
private const int MidiSizeLimit = 2000000;
private const double BytesToMegabytes = 0.000001d;
private const int ChannelCount = 16;
private const int ChannelCount = RobustMidiEvent.MaxChannels;
private readonly ISawmill _midiSawmill;
private readonly Settings _settings;
[ViewVariables(VVAccess.ReadWrite)]
private bool _debugEvents = false;
[ViewVariables(VVAccess.ReadWrite)] private bool _debugEvents = false;
// Kept around to avoid the loader callbacks getting GC'd
// ReSharper disable once NotAccessedField.Local
@@ -48,8 +48,9 @@ internal sealed class MidiRenderer : IMidiRenderer
private readonly SequencerClientId _robustRegister;
private readonly SequencerClientId _debugRegister;
[ViewVariables]
private MidiRendererState _rendererState = new();
[ViewVariables] private MidiRendererState _rendererState = new();
private IMidiRenderer? _master;
public MidiRendererState RendererState => _rendererState;
public IClydeBufferedAudioSource Source { get; set; }
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
@@ -70,8 +71,8 @@ internal sealed class MidiRenderer : IMidiRenderer
{
for (byte i = 0; i < ChannelCount; i++)
{
// Channel 9 is the percussion channel. Let's not change its instrument...
if (i == 9)
// Don't change percussion channel instrument.
if (i == RobustMidiEvent.PercussionChannel)
continue;
SendMidiEvent(RobustMidiEvent.ProgramChange(i, value, SequencerTick));
@@ -96,11 +97,14 @@ internal sealed class MidiRenderer : IMidiRenderer
{
for (byte i = 0; i < ChannelCount; i++)
{
// Channel 9 is the percussion channel. Let's not change its bank...
if (i == 9)
// Don't change percussion channel bank.
if (i == RobustMidiEvent.PercussionChannel)
continue;
SendMidiEvent(RobustMidiEvent.BankSelect(i, value, SequencerTick));
// Re-select program.
SendMidiEvent(RobustMidiEvent.ProgramChange(i, _midiProgram, SequencerTick));
}
}
@@ -128,7 +132,11 @@ internal sealed class MidiRenderer : IMidiRenderer
}
[ViewVariables(VVAccess.ReadWrite)]
public bool DisablePercussionChannel { get; set; } = true;
public bool DisablePercussionChannel
{
get => FilteredChannels[RobustMidiEvent.PercussionChannel];
set => FilteredChannels[RobustMidiEvent.PercussionChannel] = value;
}
[ViewVariables(VVAccess.ReadWrite)]
public bool DisableProgramChangeEvent { get; set; } = true;
@@ -181,13 +189,62 @@ internal sealed class MidiRenderer : IMidiRenderer
}
[ViewVariables(VVAccess.ReadWrite)]
public bool VolumeBoost { get; set; }
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
public bool VolumeBoost
{
get => VelocityOverride == 127;
set => VelocityOverride = value ? 127 : null;
}
[ViewVariables(VVAccess.ReadWrite)]
public EntityUid? TrackingEntity { get; set; } = null;
[ViewVariables(VVAccess.ReadWrite)]
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
public MapCoordinates? TrackingCoordinates { get; set; } = null;
[ViewVariables]
public BitArray FilteredChannels { get; } = new(RobustMidiEvent.MaxChannels);
[ViewVariables(VVAccess.ReadWrite)]
public byte? VelocityOverride { get; set; } = null;
[ViewVariables(VVAccess.ReadWrite)]
public IMidiRenderer? Master
{
get => _master;
set
{
if (value == _master)
return;
if (_master is { Disposed: false })
{
try
{
_master.OnMidiEvent -= SendMidiEvent;
}
catch
{
// ignored
}
}
_master = value;
if (_master == null)
return;
_master.OnMidiEvent += SendMidiEvent;
ApplyState(_master.RendererState, true);
MidiBank = _midiBank;
}
}
[ViewVariables, UsedImplicitly]
private double CpuLoad => !_synth.Disposed ? _synth.CpuLoad : 0;
public event Action<RobustMidiEvent>? OnMidiEvent;
public event Action? OnMidiPlayerFinished;
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono,
IMidiManager midiManager, IClydeAudio clydeAudio, ITaskManager taskManager, ISawmill midiSawmill)
@@ -354,6 +411,11 @@ internal sealed class MidiRenderer : IMidiRenderer
}
}
public void SystemReset()
{
SendMidiEvent(RobustMidiEvent.SystemReset(SequencerTick));
}
public void ClearAllEvents()
{
_sequencer.RemoveEvents(SequencerClientId.Wildcard, SequencerClientId.Wildcard, -1);
@@ -368,9 +430,6 @@ internal sealed class MidiRenderer : IMidiRenderer
}
}
public event Action<RobustMidiEvent>? OnMidiEvent;
public event Action? OnMidiPlayerFinished;
void IMidiRenderer.Render()
{
Render();
@@ -432,7 +491,7 @@ internal sealed class MidiRenderer : IMidiRenderer
if (!Source.IsPlaying) Source.StartPlaying();
}
public void ApplyState(MidiRendererState state)
public void ApplyState(MidiRendererState state, bool filterChannels = false)
{
lock (_playerStateLock)
{
@@ -440,6 +499,9 @@ internal sealed class MidiRenderer : IMidiRenderer
for (var channel = 0; channel < ChannelCount; channel++)
{
if (filterChannels && !FilteredChannels[channel])
continue;
_synth.AllNotesOff(channel);
_synth.PitchBend(channel, state.PitchBend.AsSpan[channel]);
@@ -462,7 +524,8 @@ internal sealed class MidiRenderer : IMidiRenderer
}
}
_synth.ProgramChange(channel, state.Program.AsSpan[channel]);
var program = DisableProgramChangeEvent ? MidiProgram : state.Program.AsSpan[channel];
_synth.ProgramChange(channel, program);
for (var key = 0; key < state.NoteVelocities.AsSpan[channel].AsSpan.Length; key++)
{
@@ -487,7 +550,12 @@ internal sealed class MidiRenderer : IMidiRenderer
}
}
public void SendMidiEvent(RobustMidiEvent midiEvent)
private void SendMidiEvent(RobustMidiEvent midiEvent)
{
SendMidiEvent(midiEvent, true);
}
public void SendMidiEvent(RobustMidiEvent midiEvent, bool raiseEvent)
{
if (Disposed)
return;
@@ -505,11 +573,10 @@ internal sealed class MidiRenderer : IMidiRenderer
break;
case RobustMidiCommand.NoteOn:
// Channel 9 is the percussion channel. We only block NoteOn events to it.
if (DisablePercussionChannel && midiEvent.Channel == 9)
return;
if (FilteredChannels[midiEvent.Channel])
break;
var velocity = (byte)(VolumeBoost ? 127 : midiEvent.Velocity);
var velocity = VelocityOverride ?? midiEvent.Velocity;
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
@@ -523,7 +590,7 @@ internal sealed class MidiRenderer : IMidiRenderer
case RobustMidiCommand.ControlChange:
// CC0 is bank selection
if (midiEvent.Control == 0x0 && DisableProgramChangeEvent)
return;
break;
_rendererState.Controllers.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Control] = midiEvent.Value;
if(midiEvent.Control != 0x0)
@@ -534,7 +601,7 @@ internal sealed class MidiRenderer : IMidiRenderer
case RobustMidiCommand.ProgramChange:
if (DisableProgramChangeEvent)
return;
break;
_rendererState.Program.AsSpan[midiEvent.Channel] = midiEvent.Program;
_synth.ProgramChange(midiEvent.Channel, midiEvent.Program);
@@ -561,14 +628,14 @@ internal sealed class MidiRenderer : IMidiRenderer
switch (midiEvent.Control)
{
case 0x0 when midiEvent.Status == 0xFF:
_rendererState = new ();
_rendererState = new MidiRendererState();
_synth.SystemReset();
// Reset the instrument to the one we were using.
if (DisableProgramChangeEvent)
{
MidiProgram = _midiProgram;
MidiBank = _midiBank;
MidiProgram = _midiProgram;
}
break;
@@ -597,7 +664,10 @@ internal sealed class MidiRenderer : IMidiRenderer
//_midiSawmill.Error("Exception while sending midi event of type {0}: {1}", midiEvent.Type, e, midiEvent);
}
_taskManager.RunOnMainThread(() => OnMidiEvent?.Invoke(midiEvent));
if (raiseEvent)
{
_taskManager.RunOnMainThread(() => OnMidiEvent?.Invoke(midiEvent));
}
}
public void ScheduleMidiEvent(RobustMidiEvent midiEvent, uint time, bool absolute = false)
@@ -633,6 +703,9 @@ internal sealed class MidiRenderer : IMidiRenderer
/// <inheritdoc />
void IMidiRenderer.InternalDispose()
{
OnMidiEvent = null;
OnMidiPlayerFinished = null;
Source?.Dispose();
_driver?.Dispose();

View File

@@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Audio.Midi;
@@ -12,7 +13,7 @@ public struct MidiRendererState
internal FixedArray16<byte> ChannelPressure;
internal FixedArray16<ushort> PitchBend;
internal Span<byte> AsSpan => MemoryMarshal.CreateSpan(ref NoteVelocities._00._00, 4160);
[ViewVariables] internal Span<byte> AsSpan => MemoryMarshal.CreateSpan(ref NoteVelocities._00._00, 4160);
static unsafe MidiRendererState()
{

View File

@@ -84,6 +84,7 @@ namespace Robust.Client
deps.Register<IReplayLoadManager, ReplayLoadManager>();
deps.Register<IReplayPlaybackManager, ReplayPlaybackManager>();
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
deps.Register<IClientGameStateManager, ClientGameStateManager>();
deps.Register<IBaseClient, BaseClient>();
deps.Register<IPlayerManager, PlayerManager>();

View File

@@ -621,6 +621,7 @@ namespace Robust.Client.Console.Commands
internal sealed class ChunkInfoCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IInputManager _input = default!;
@@ -631,16 +632,17 @@ namespace Robust.Client.Console.Commands
{
var mousePos = _eye.ScreenToMap(_input.MouseScreenPosition);
if (!_map.TryFindGridAt(mousePos, out _, out var grid))
if (!_map.TryFindGridAt(mousePos, out var gridUid, out var grid))
{
shell.WriteLine("No grid under your mouse cursor.");
return;
}
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = grid.GetOrAddChunk(chunkIndex);
var mapSystem = _entManager.System<SharedMapSystem>();
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, grid.MapToGrid(mousePos));
var chunk = mapSystem.GetOrAddChunk(gridUid, grid, chunkIndex);
shell.WriteLine($"worldBounds: {grid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
shell.WriteLine($"worldBounds: {mapSystem.CalcWorldAABB(gridUid, grid, chunk)} localBounds: {chunk.CachedBounds}");
}
}

View File

@@ -84,7 +84,7 @@ namespace Robust.Client
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
[Dependency] private readonly IReplayLoadManager _replayLoader = default!;
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
private IWebViewManagerHook? _webViewHook;
@@ -200,7 +200,12 @@ namespace Robust.Client
// Setup main loop
if (_mainLoop == null)
{
_mainLoop = new GameLoop(_gameTiming, _runtimeLog, _prof, _logManager.GetSawmill("eng"))
_mainLoop = new GameLoop(
_gameTiming,
_runtimeLog,
_prof,
_logManager.GetSawmill("eng"),
GameLoopOptions.FromCVars(_configurationManager))
{
SleepMode = displayMode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
};
@@ -560,6 +565,11 @@ namespace Robust.Client
{
_taskManager.ProcessPendingTasks(); // tasks like connect
}
using (_prof.Group("Content post engine"))
{
_modLoader.BroadcastUpdate(ModUpdateLevel.InputPostEngine, frameEventArgs);
}
}
private void Tick(FrameEventArgs frameEventArgs)
@@ -756,6 +766,8 @@ namespace Robust.Client
internal void CleanupGameThread()
{
_replayRecording.Shutdown();
_modLoader.Shutdown();
// CEF specifically makes a massive silent stink of it if we don't shut it down from the correct thread.

View File

@@ -15,15 +15,12 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables]
private Eye? _eye = default!;
[ViewVariables] internal Eye? _eye = default!;
// Horrible hack to get around ordering issues.
private bool _setCurrentOnInitialize;
[DataField("drawFov")]
private bool _setDrawFovOnInitialize = true;
[DataField("zoom")]
private Vector2 _setZoomOnInitialize = Vector2.One;
internal bool _setCurrentOnInitialize;
[DataField("drawFov")] internal bool _setDrawFovOnInitialize = true;
[DataField("zoom")] internal Vector2 _setZoomOnInitialize = Vector2.One;
/// <summary>
/// If not null, this entity is used to update the eye's position instead of just using the component's owner.
@@ -119,54 +116,6 @@ namespace Robust.Client.GameObjects
[ViewVariables]
public MapCoordinates? Position => _eye?.Position;
/// <inheritdoc />
protected override void Initialize()
{
base.Initialize();
_eye = new Eye
{
Position = _entityManager.GetComponent<TransformComponent>(Owner).MapPosition,
Zoom = _setZoomOnInitialize,
DrawFov = _setDrawFovOnInitialize
};
if ((_eyeManager.CurrentEye == _eye) != _setCurrentOnInitialize)
{
if (_setCurrentOnInitialize)
{
_eyeManager.ClearCurrentEye();
}
else
{
_eyeManager.CurrentEye = _eye;
}
}
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (curState is not EyeComponentState state)
{
return;
}
DrawFov = state.DrawFov;
// TODO: Should be a way for content to override lerping and lerp the zoom
Zoom = state.Zoom;
Offset = state.Offset;
VisibilityMask = state.VisibilityMask;
}
protected override void OnRemove()
{
base.OnRemove();
Current = false;
}
/// <summary>
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
/// keep the view following the entity.

View File

@@ -40,7 +40,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
return;
comp.Enabled = enabled;
Dirty(comp);
Dirty(uid, comp);
var xform = Transform(uid);
QueueTreeUpdate(uid, comp, xform);

View File

@@ -38,7 +38,8 @@ public sealed class DebugEntityLookupSystem : EntitySystem
IoCManager.Resolve<IOverlayManager>().AddOverlay(
new EntityLookupOverlay(
EntityManager,
Get<EntityLookupSystem>()));
EntityManager.System<EntityLookupSystem>(),
EntityManager.System<SharedTransformSystem>()));
}
else
{
@@ -52,31 +53,35 @@ public sealed class DebugEntityLookupSystem : EntitySystem
public sealed class EntityLookupOverlay : Overlay
{
private IEntityManager _entityManager = default!;
private EntityLookupSystem _lookup = default!;
private readonly IEntityManager _entityManager;
private readonly EntityLookupSystem _lookup;
private readonly SharedTransformSystem _transform;
private EntityQuery<TransformComponent> _xformQuery;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup)
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup, SharedTransformSystem transform)
{
_entityManager = entManager;
_lookup = lookup;
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
_transform = transform;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
var worldBounds = args.WorldBounds;
foreach (var lookup in _lookup.FindLookupsIntersecting(args.MapId, args.WorldBounds))
// TODO: Static version
_lookup.FindLookupsIntersecting(args.MapId, worldBounds, (uid, lookup) =>
{
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var (_, rotation, matrix, invMatrix) = lookupXform.GetWorldPositionRotationMatrixWithInv();
var (_, rotation, matrix, invMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(uid);
worldHandle.SetTransform(matrix);
var lookupAABB = invMatrix.TransformBox(args.WorldBounds);
var lookupAABB = invMatrix.TransformBox(worldBounds);
var ents = new List<EntityUid>();
lookup.DynamicTree.QueryAabb(ref ents, static (ref List<EntityUid> state, in FixtureProxy value) =>
@@ -105,20 +110,22 @@ public sealed class EntityLookupOverlay : Overlay
foreach (var ent in ents)
{
if (_entityManager.Deleted(ent)) continue;
var xform = xformQuery.GetComponent(ent);
if (_entityManager.Deleted(ent))
continue;
var xform = _xformQuery.GetComponent(ent);
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
var (entPos, entRot) = xform.GetWorldPositionRotation();
var (entPos, entRot) = _transform.GetWorldPositionRotation(ent);
var lookupPos = invMatrix.Transform(entPos);
var lookupRot = entRot - rotation;
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, xformQuery);
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, _xformQuery);
worldHandle.DrawRect(aabb, Color.Blue.WithAlpha(0.2f));
}
}
});
worldHandle.SetTransform(Matrix3.Identity);
}

View File

@@ -0,0 +1,60 @@
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
namespace Robust.Client.GameObjects;
public sealed class EyeSystem : SharedEyeSystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EyeComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<EyeComponent, ComponentHandleState>(OnHandleState);
}
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
{
component._eye = new Eye
{
Position = Transform(uid).MapPosition,
Zoom = component._setZoomOnInitialize,
DrawFov = component._setDrawFovOnInitialize
};
if ((_eyeManager.CurrentEye == component._eye) != component._setCurrentOnInitialize)
{
if (component._setCurrentOnInitialize)
{
_eyeManager.ClearCurrentEye();
}
else
{
_eyeManager.CurrentEye = component._eye;
}
}
}
private void OnRemove(EntityUid uid, EyeComponent component, ComponentRemove args)
{
component.Current = false;
}
private void OnHandleState(EntityUid uid, EyeComponent component, ref ComponentHandleState args)
{
if (args.Current is not EyeComponentState state)
{
return;
}
component.DrawFov = state.DrawFov;
// TODO: Should be a way for content to override lerping and lerp the zoom
component.Zoom = state.Zoom;
component.Offset = state.Offset;
component.VisibilityMask = state.VisibilityMask;
}
}

View File

@@ -37,7 +37,7 @@ namespace Robust.Client.GameObjects
return;
comp.ContainerOccluded = occluded;
Dirty(comp);
Dirty(uid, comp);
if (comp.Enabled)
_lightTree.QueueTreeUpdate(uid, comp);
@@ -50,7 +50,7 @@ namespace Robust.Client.GameObjects
comp._enabled = enabled;
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
Dirty(comp);
Dirty(uid, comp);
var cast = (PointLightComponent)comp;
if (!cast.ContainerOccluded)
@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
return;
comp._radius = radius;
Dirty(comp);
Dirty(uid, comp);
var cast = (PointLightComponent)comp;
if (cast.TreeUid != null)

View File

@@ -22,6 +22,13 @@ public sealed partial class TransformSystem
base.SetLocalPositionNoLerp(xform, value);
}
public override void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
{
xform.NextRotation = null;
xform.LerpParent = EntityUid.Invalid;
base.SetLocalRotationNoLerp(xform, angle);
}
public override void SetLocalRotation(TransformComponent xform, Angle angle)
{
xform.PrevRotation = xform._localRotation;

View File

@@ -532,7 +532,6 @@ namespace Robust.Client.GameStates
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
comp.HandleComponentState(compState, null);
comp.LastModifiedTick = _timing.LastRealTick;
}
@@ -561,7 +560,6 @@ namespace Robust.Client.GameStates
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
comp.HandleComponentState(state, null);
comp.ClearCreationTick(); // don't undo the re-adding.
comp.LastModifiedTick = _timing.LastRealTick;
}
@@ -1172,7 +1170,6 @@ namespace Robust.Client.GameStates
{
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
comp.HandleComponentState(cur, next);
}
catch (Exception e)
{
@@ -1223,7 +1220,7 @@ namespace Robust.Client.GameStates
return;
using var _ = _timing.StartStateApplicationArea();
ResetEnt(meta, false);
ResetEnt(uid, meta, false);
}
/// <summary>
@@ -1301,24 +1298,24 @@ namespace Robust.Client.GameStates
{
using var _ = _timing.StartStateApplicationArea();
foreach (var meta in _entities.EntityQuery<MetaDataComponent>(true))
var query = _entityManager.AllEntityQueryEnumerator<MetaDataComponent>();
while (query.MoveNext(out var uid, out var meta))
{
ResetEnt(meta);
ResetEnt(uid, meta);
}
}
/// <summary>
/// Reset a given entity to the most recent server state.
/// </summary>
private void ResetEnt(MetaDataComponent meta, bool skipDetached = true)
private void ResetEnt(EntityUid uid, MetaDataComponent meta, bool skipDetached = true)
{
if (skipDetached && (meta.Flags & MetaDataFlags.Detached) != 0)
return;
meta.Flags &= ~MetaDataFlags.Detached;
var uid = meta.Owner;
if (!_processor.TryGetLastServerStates(uid, out var lastState))
return;
@@ -1334,7 +1331,6 @@ namespace Robust.Client.GameStates
var handleState = new ComponentHandleState(state, null);
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
comp.HandleComponentState(state, null);
}
// ensure we don't have any extra components

View File

@@ -261,6 +261,9 @@ namespace Robust.Client.Graphics.Audio
{
var source = AL.GenSource();
if (!AL.IsSource(source))
throw new Exception("Failed to generate source. Too many simultaneous audio streams?");
// ReSharper disable once PossibleInvalidOperationException
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);

View File

@@ -23,7 +23,7 @@ namespace Robust.Client.Physics
{
if (_enableDebug == value) return;
Sawmill.Info($"Set grid fixture debug to {value}");
Log.Info($"Set grid fixture debug to {value}");
_enableDebug = value;
if (_enableDebug)
@@ -59,7 +59,7 @@ namespace Robust.Client.Physics
private void OnDebugMessage(ChunkSplitDebugMessage ev)
{
Sawmill.Info($"Received grid fixture debug data");
Log.Info($"Received grid fixture debug data");
if (!_enableDebug) return;
_nodes[ev.Grid] = ev.Nodes;

View File

@@ -694,6 +694,9 @@ namespace Robust.Client.Placement
IsActive = true;
CurrentPlacementOverlayEntity = EntityManager.SpawnEntity(templateName, MapCoordinates.Nullspace);
EntityManager.RunMapInit(
CurrentPlacementOverlayEntity.Value,
EntityManager.GetComponent<MetaDataComponent>(CurrentPlacementOverlayEntity.Value));
}
public void PreparePlacementSprite(SpriteComponent sprite)

View File

@@ -78,15 +78,16 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
IWritableDirProvider directory,
string? name = null,
bool overwrite = false,
TimeSpan? duration = null)
TimeSpan? duration = null,
object? state = null)
{
if (!base.TryStartRecording(directory, name, overwrite, duration))
if (!base.TryStartRecording(directory, name, overwrite, duration, state))
return false;
var (state, detachMsg) = CreateFullState();
var (gameState, detachMsg) = CreateFullState();
if (detachMsg != null)
RecordReplayMessage(detachMsg);
Update(state);
Update(gameState);
return true;
}

View File

@@ -50,6 +50,7 @@
<!-- ILLink configuration -->
<ItemGroup>
<RobustLinkRoots Include="Robust.Client" />
<RobustLinkRoots Include="Robust.Shared" />
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
<RobustLinkAssemblies Include="OpenToolkit.Graphics" />
</ItemGroup>

View File

@@ -55,12 +55,12 @@ namespace Robust.Client.UserInterface
continue;
toRemove.Add(key);
AnimationCompleted?.Invoke(key);
}
foreach (var key in toRemove)
{
_playingAnimations.Remove(key);
AnimationCompleted?.Invoke(key);
}
}
}

View File

@@ -88,9 +88,9 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar.MoveToEnd();
}
public Item AddItem(string text, Texture? icon = null, bool selectable = true)
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null)
{
var item = new Item(this) {Text = text, Icon = icon, Selectable = selectable};
var item = new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata};
Add(item);
return item;
}
@@ -448,7 +448,8 @@ namespace Robust.Client.UserInterface.Controls
{
if (item.Selected && SelectMode != ItemListSelectMode.Button)
{
ClearSelected();
if(SelectMode != ItemListSelectMode.Multiple)
ClearSelected();
item.Selected = false;
return;
}

View File

@@ -62,18 +62,19 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
var screenSize = _displayManager.ScreenSize;
var screenScale = _displayManager.MainWindow.ContentScale;
MapCoordinates mouseWorldMap;
EntityCoordinates mouseGridPos;
TileRef tile;
mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
var mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
if (mouseWorldMap == MapCoordinates.Nullspace)
return;
if (_mapManager.TryFindGridAt(mouseWorldMap, out _, out var mouseGrid))
var mapSystem = _entityManager.System<SharedMapSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
{
mouseGridPos = mouseGrid.MapToGrid(mouseWorldMap);
tile = mouseGrid.GetTileRef(mouseGridPos);
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
}
else
{

View File

@@ -98,7 +98,7 @@ namespace Robust.Server
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly IStatusHost _statusHost = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IReplayRecordingManager _replay = default!;
[Dependency] private readonly IReplayRecordingManagerInternal _replay = default!;
[Dependency] private readonly IGamePrototypeLoadManager _protoLoadMan = default!;
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
@@ -510,7 +510,12 @@ namespace Robust.Server
{
if (_mainLoop == null)
{
_mainLoop = new GameLoop(_time, _runtimeLog, _prof, _log.GetSawmill("eng"))
_mainLoop = new GameLoop(
_time,
_runtimeLog,
_prof,
_log.GetSawmill("eng"),
GameLoopOptions.FromCVars(_config))
{
SleepMode = SleepMode.Delay,
DetectSoftLock = true,
@@ -628,6 +633,8 @@ namespace Robust.Server
// called right before main loop returns, do all saving/cleanup in here
public void Cleanup()
{
_replay.Shutdown();
_modLoader.Shutdown();
_playerManager.Shutdown();
@@ -670,6 +677,8 @@ namespace Robust.Server
_network.ProcessPackets();
_taskManager.ProcessPendingTasks();
_modLoader.BroadcastUpdate(ModUpdateLevel.InputPostEngine, args);
}
private void Update(FrameEventArgs frameEventArgs)

View File

@@ -29,7 +29,7 @@ namespace Robust.Server.Bql
return base.DoInitialSelection(arguments, isInverted, entityManager);
}
return entityManager.GetAllComponents((Type) arguments[0])
return entityManager.GetAllComponents((Type) arguments[0], includePaused: true)
.Select(x => x.Owner);
}
}
@@ -352,4 +352,17 @@ namespace Robust.Server.Bql
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.Anchored) ^ isInverted);
}
}
[RegisterBqlQuerySelector]
public sealed class PausedQuerySelector : BqlQuerySelector
{
public override string Token => "paused";
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
return input.Where(e => entityManager.GetComponent<MetaDataComponent>(e).EntityPaused ^ isInverted);
}
}
}

View File

@@ -47,6 +47,7 @@ namespace Robust.Server.Console
private long _lastReceivedBytes;
private long _lastSentBytes;
private bool _hasCancelled;
public void Dispose()
{
@@ -319,8 +320,15 @@ namespace Robust.Server.Console
private void CancelKeyHandler(object? sender, ConsoleCancelEventArgs args)
{
if (_hasCancelled)
{
Con.WriteLine("Double CancelKey, terminating process.");
return;
}
// Handle process exiting ourselves.
args.Cancel = true;
_hasCancelled = true;
_taskManager.RunOnMainThread(() => { _baseServer.Shutdown("CancelKey"); });
}

View File

@@ -87,10 +87,5 @@ namespace Robust.Server.GameObjects
Dirty();
}
}
public override ComponentState GetComponentState()
{
return new EyeComponentState(DrawFov, Zoom, Offset, VisibilityMask);
}
}
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
namespace Robust.Server.GameObjects;
public sealed class EyeSystem : SharedEyeSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EyeComponent, ComponentGetState>(OnHandleState);
}
private void OnHandleState(EntityUid uid, EyeComponent component, ref ComponentGetState args)
{
args.State = new EyeComponentState(component.DrawFov, component.Zoom, component.Offset, component.VisibilityMask);
}
}

View File

@@ -44,6 +44,7 @@ public sealed class MapLoaderSystem : EntitySystem
private IServerEntityManagerInternal _serverEntityManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _logLoader = default!;
@@ -764,11 +765,12 @@ public sealed class MapLoaderSystem : EntitySystem
SequenceDataNode yamlGridChunks = (SequenceDataNode)yamlGrid["chunks"];
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
var gridUid = grid.Owner;
foreach (var chunkNode in yamlGridChunks.Cast<MappingDataNode>())
{
var (chunkOffsetX, chunkOffsetY) = _serManager.Read<Vector2i>(chunkNode["ind"]);
_serManager.Read(chunkNode, _context, instanceProvider: () => grid.GetOrAddChunk(chunkOffsetX, chunkOffsetY), notNullableOverride: true);
_serManager.Read(chunkNode, _context, instanceProvider: () => _mapSystem.GetOrAddChunk(gridUid, grid, chunkOffsetX, chunkOffsetY), notNullableOverride: true);
}
}
}

View File

@@ -70,7 +70,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
foreach (var (_, comp) in EntityManager.GetNetComponents(uid))
{
if (comp.SessionSpecific || comp.SendOnlyToOwner)
Dirty(comp);
Dirty(uid, comp);
}
}

View File

@@ -107,15 +107,12 @@ internal sealed partial class PvsSystem : EntitySystem
private EntityQuery<EyeComponent> _eyeQuery;
private EntityQuery<TransformComponent> _xformQuery;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_eyeQuery = GetEntityQuery<EyeComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_sawmill = Logger.GetSawmill("PVS");
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
@@ -182,7 +179,7 @@ internal sealed partial class PvsSystem : EntitySystem
sb.Append($" Entity last sent: {lastSeenTick.Value}");
}
_sawmill.Warning(sb.ToString());
Log.Warning(sb.ToString());
sessionData.LastSeenAt.Clear();
@@ -231,7 +228,7 @@ internal sealed partial class PvsSystem : EntitySystem
private PVSCollection<TIndex> RegisterPVSCollection<TIndex>() where TIndex : IComparable<TIndex>, IEquatable<TIndex>
{
var collection = new PVSCollection<TIndex>(_sawmill, EntityManager, _transform);
var collection = new PVSCollection<TIndex>(Log, EntityManager, _transform);
_pvsCollections.Add(collection);
return collection;
}
@@ -334,12 +331,12 @@ internal sealed partial class PvsSystem : EntitySystem
if (e.NewStatus == SessionStatus.InGame)
{
if (!PlayerData.TryAdd(e.Session, new()))
_sawmill.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
Log.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
foreach (var pvsCollection in _pvsCollections)
{
if (!pvsCollection.AddPlayer(e.Session))
_sawmill.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
Log.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
}
return;
}
@@ -353,7 +350,7 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var pvsCollection in _pvsCollections)
{
if (!pvsCollection.RemovePlayer(e.Session))
_sawmill.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
Log.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
}
if (data.Overflow != null)
@@ -822,7 +819,7 @@ internal sealed partial class PvsSystem : EntitySystem
#if DEBUG
// This happens relatively frequently for the current TickBuffer value, and doesn't really provide any
// useful info when not debugging/testing locally. Hence only enable on DEBUG.
_sawmill.Debug($"Client {session} exceeded tick buffer.");
Log.Debug($"Client {session} exceeded tick buffer.");
#endif
}
else if (oldEntry.Value.Value != lastAcked)
@@ -980,7 +977,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating)
{
var rep = new EntityStringRepresentation(uid, metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
_sawmill.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
return;
}
@@ -1110,7 +1107,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (component.Deleted || !component.Initialized)
{
_sawmill.Error("Entity manager returned deleted or uninitialized components while sending entity data");
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
continue;
}

View File

@@ -77,7 +77,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
chunk.TrySetTile(x, y, tile, out _, out _);
}
}
@@ -143,7 +143,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
chunk.SetTile(x, y, source.GetTile(x, y));
chunk.TrySetTile(x, y, source.GetTile(x, y), out _, out _);
}
}

View File

@@ -9,6 +9,7 @@
<ServerGarbageCollection>true</ServerGarbageCollection>
<!-- Try to fix sporadic errors against Robust.Packaging, apparently?? -->
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<RobustILLink>true</RobustILLink>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
@@ -40,5 +41,14 @@
</EmbeddedResource>
</ItemGroup>
<!-- ILLink configuration -->
<ItemGroup>
<RobustLinkRoots Include="Robust.Server" />
<RobustLinkRoots Include="Robust.Shared" />
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Properties.targets" />
<Import Project="..\MSBuild\Robust.Trimming.targets" />
</Project>

View File

@@ -70,6 +70,7 @@ namespace Robust.Server
deps.Register<IServerEntityManagerInternal, ServerEntityManager>();
deps.Register<IServerGameStateManager, ServerGameStateManager>();
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
deps.Register<IServerReplayRecordingManager, ReplayRecordingManager>();
deps.Register<IServerNetManager, NetManager>();
deps.Register<IStatusHost, StatusHost>();

View File

@@ -105,11 +105,11 @@ internal sealed partial class StatusHost
private Task<bool> SourceAczDictionaryViaFile(AssetPass pass, IPackageLogger logger)
{
var path = PathHelpers.ExecutableRelativeFile("Content.Client.zip");
if (!File.Exists(path))
if (!FileHelper.TryOpenFileRead(path, out var fileStream))
return Task.FromResult(false);
_aczSawmill.Info($"StatusHost found client zip: {path}");
using var zip = new ZipArchive(File.OpenRead(path), ZipArchiveMode.Read, leaveOpen: false);
using var zip = new ZipArchive(fileStream, ZipArchiveMode.Read, leaveOpen: false);
SourceAczDictionaryViaZipStream(zip, pass, logger);
return Task.FromResult(true);
}

View File

@@ -1,21 +1,17 @@
using System;
using System.Runtime.InteropServices;
using TerraFX.Interop.Windows;
namespace Robust.Server.Utility
{
internal static class WindowsTickPeriod
{
private const uint TIMERR_NOERROR = 0;
// This is an actual error code my god.
private const uint TIMERR_NOCANDO = 97;
public static void TimeBeginPeriod(uint period)
{
if (!OperatingSystem.IsWindows())
throw new InvalidOperationException();
var ret = timeBeginPeriod(period);
if (ret != TIMERR_NOERROR)
var ret = Windows.timeBeginPeriod(period);
if (ret != Windows.TIMERR_NOERROR)
throw new InvalidOperationException($"timeBeginPeriod returned error: {ret}");
}
@@ -24,15 +20,9 @@ namespace Robust.Server.Utility
if (!OperatingSystem.IsWindows())
throw new InvalidOperationException();
var ret = timeEndPeriod(period);
if (ret != TIMERR_NOERROR)
var ret = Windows.timeBeginPeriod(period);
if (ret != Windows.TIMERR_NOERROR)
throw new InvalidOperationException($"timeEndPeriod returned error: {ret}");
}
[DllImport("Winmm.dll")]
private static extern uint timeBeginPeriod(uint uPeriod);
[DllImport("Winmm.dll")]
private static extern uint timeEndPeriod(uint uPeriod);
}
}

View File

@@ -162,10 +162,10 @@ namespace Robust.Shared.Scripting
public abstract void show(object obj);
#region EntityManager proxy methods
public T Comp<T>(EntityUid uid)
public T Comp<T>(EntityUid uid) where T : Component
=> ent.GetComponent<T>(uid);
public bool TryComp<T>(EntityUid uid, out T? comp)
public bool TryComp<T>(EntityUid uid, out T? comp) where T : Component
=> ent.TryGetComponent(uid, out comp);
public bool HasComp<T>(EntityUid uid)

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Serialization;
namespace Robust.Shared.Audio.Midi
@@ -9,6 +10,9 @@ namespace Robust.Shared.Audio.Midi
[Serializable, NetSerializable]
public readonly struct RobustMidiEvent
{
public const int MaxChannels = 16;
public const int PercussionChannel = 9;
#region Data
/// <summary>
@@ -49,6 +53,17 @@ namespace Robust.Shared.Audio.Midi
Tick = tick;
}
/// <summary>
/// Clones another event but with a different tick value.
/// </summary>
public RobustMidiEvent(RobustMidiEvent ev, uint tick)
{
Status = ev.Status;
Data1 = ev.Data1;
Data2 = ev.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} <<";

View File

@@ -319,6 +319,12 @@ namespace Robust.Shared
public static readonly CVarDef<bool> SysGCCollectStart =
CVarDef.Create("sys.gc_collect_start", true);
/// <summary>
/// Use precise sleeping methods in the game loop.
/// </summary>
public static readonly CVarDef<bool> SysPreciseSleep =
CVarDef.Create("sys.precise_sleep", true);
/*
* METRICS
*/
@@ -1258,6 +1264,15 @@ namespace Robust.Shared
public static readonly CVarDef<float> MidiVolume =
CVarDef.Create("midi.volume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<int> MidiMinRendererParallel =
CVarDef.Create("midi.min_renderers_parallel_update", 3, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<float> MidiPositionUpdateDelay =
CVarDef.Create("midi.position_update_delay", 0.125f, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<float> MidiOcclusionUpdateDelay =
CVarDef.Create("midi.occlusion_update_delay", 0.25f, CVar.CLIENTONLY | CVar.ARCHIVE);
/*
* HUB
* CVars related to public master server hub

View File

@@ -15,8 +15,16 @@ internal sealed class RecursiveMoveSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
private EntityQuery<TransformComponent> _xformQuery;
bool Subscribed = false;
public override void Initialize()
{
base.Initialize();
_xformQuery = GetEntityQuery<TransformComponent>();
}
internal void AddSubscription()
{
if (Subscribed)
@@ -34,14 +42,12 @@ internal sealed class RecursiveMoveSystem : EntitySystem
DebugTools.Assert(!_mapManager.IsMap(args.Sender));
DebugTools.Assert(!_mapManager.IsGrid(args.Sender));
var xformQuery = GetEntityQuery<TransformComponent>();
AnythingMovedSubHandler(args.Sender, args.Component, xformQuery);
AnythingMovedSubHandler(args.Sender, args.Component);
}
private void AnythingMovedSubHandler(
EntityUid uid,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery)
TransformComponent xform)
{
// TODO maybe use a c# event? This event gets raised a lot.
// Would probably help with server performance and is also the main bottleneck for replay scrubbing.
@@ -55,8 +61,8 @@ internal sealed class RecursiveMoveSystem : EntitySystem
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
if (xformQuery.TryGetComponent(child.Value, out var childXform))
AnythingMovedSubHandler(child.Value, childXform, xformQuery);
if (_xformQuery.TryGetComponent(child.Value, out var childXform))
AnythingMovedSubHandler(child.Value, childXform);
}
}
}

View File

@@ -54,7 +54,7 @@ internal sealed class TeleportCommand : LocalizedCommands
if (_map.TryFindGridAt(mapId, position, out var gridUid, out var grid))
{
var gridPos = grid.WorldToLocal(position);
var gridPos = xformSystem.GetInvWorldMatrix(gridUid).Transform(position);
xformSystem.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
}

View File

@@ -54,28 +54,6 @@ namespace Robust.Shared.Containers
Containers.Clear();
}
/// <inheritdoc />
public override ComponentState GetComponentState()
{
// naive implementation that just sends the full state of the component
Dictionary<string, ContainerManagerComponentState.ContainerData> containerSet = new(Containers.Count);
foreach (var container in Containers.Values)
{
var uidArr = new EntityUid[container.ContainedEntities.Count];
for (var index = 0; index < container.ContainedEntities.Count; index++)
{
uidArr[index] = container.ContainedEntities[index];
}
var sContainer = new ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr);
containerSet.Add(container.ID, sContainer);
}
return new ContainerManagerComponentState(containerSet);
}
/// <inheritdoc />
public T MakeContainer<T>(string id)
where T : IContainer

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -23,6 +24,28 @@ namespace Robust.Shared.Containers
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChanged);
SubscribeLocalEvent<ContainerManagerComponent, ComponentStartup>(OnStartupValidation);
SubscribeLocalEvent<ContainerManagerComponent, ComponentGetState>(OnContainerGetState);
}
private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args)
{
// naive implementation that just sends the full state of the component
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet = new(component.Containers.Count);
foreach (var container in component.Containers.Values)
{
var uidArr = new EntityUid[container.ContainedEntities.Count];
for (var index = 0; index < container.ContainedEntities.Count; index++)
{
uidArr[index] = container.ContainedEntities[index];
}
var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr);
containerSet.Add(container.ID, sContainer);
}
args.State = new ContainerManagerComponent.ContainerManagerComponentState(containerSet);
}
// TODO: Make ContainerManagerComponent ECS and make these proxy methods the real deal.

View File

@@ -59,7 +59,7 @@ namespace Robust.Shared.ContentPack
_config = Task.Run(() => LoadConfig(sawmill));
}
private Resolver CreateResolver()
internal Resolver CreateResolver()
{
var dotnetDir = Path.GetDirectoryName(typeof(int).Assembly.Location)!;
var ourPath = typeof(AssemblyTypeChecker).Assembly.Location;
@@ -100,6 +100,19 @@ namespace Robust.Shared.ContentPack
/// <param name="assembly">Assembly to load.</param>
/// <returns></returns>
public bool CheckAssembly(Stream assembly)
{
using var resolver = CreateResolver();
return CheckAssembly(assembly, resolver);
}
/// <summary>
/// Check the assembly for any illegal types. Any types not on the white list
/// will cause the assembly to be rejected.
/// </summary>
/// <param name="assembly">Assembly to load.</param>
/// <returns></returns>
public bool CheckAssembly(Stream assembly, Resolver resolver)
{
if (WouldNoOp)
{
@@ -110,8 +123,7 @@ namespace Robust.Shared.ContentPack
_sawmill.Debug("Checking assembly...");
var fullStopwatch = Stopwatch.StartNew();
var resolver = CreateResolver();
using var peReader = ModLoader.MakePEReader(assembly, leaveOpen: true);
using var peReader = ModLoader.MakePEReader(assembly, leaveOpen: true, PEStreamOptions.PrefetchEntireImage);
var reader = peReader.GetMetadataReader();
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
@@ -856,7 +868,7 @@ namespace Robust.Shared.ContentPack
return handle.IsNil ? null : reader.GetString(handle);
}
private sealed class Resolver : IResolver
internal sealed class Resolver : IResolver, IDisposable
{
private readonly ConcurrentDictionary<string, PEReader?> _dictionary = new();
private readonly AssemblyTypeChecker _parent;
@@ -883,12 +895,10 @@ namespace Robust.Shared.ContentPack
{
var path = Path.Combine(diskLoadPath, dllName);
if (!File.Exists(path))
{
if (!FileHelper.TryOpenFileRead(path, out var fileStream))
continue;
}
return ModLoader.MakePEReader(File.OpenRead(path));
return ModLoader.MakePEReader(fileStream);
}
foreach (var resLoadPath in _resLoadPaths)
@@ -910,6 +920,14 @@ namespace Robust.Shared.ContentPack
{
return _dictionary.GetOrAdd(simpleName, ResolveCore);
}
public void Dispose()
{
foreach (var reader in _dictionary.Values)
{
reader?.Dispose();
}
}
}
internal sealed class TypeProvider : ISignatureTypeProvider<MType, int>

View File

@@ -46,16 +46,11 @@ namespace Robust.Shared.ContentPack
public bool TryGetFile(ResPath relPath, [NotNullWhen(true)] out Stream? stream)
{
var path = GetPath(relPath);
if (!File.Exists(path))
{
stream = null;
return false;
}
CheckPathCasing(relPath);
stream = File.OpenRead(path);
return true;
var ret = FileHelper.TryOpenFileRead(path, out var fStream);
stream = fStream;
return ret;
}
public bool FileExists(ResPath relPath)

View File

@@ -118,13 +118,14 @@ namespace Robust.Shared.ContentPack
var checkerSw = Stopwatch.StartNew();
var typeChecker = MakeTypeChecker();
var resolver = typeChecker.CreateResolver();
Parallel.ForEach(files, pair =>
{
var (name, (path, _)) = pair;
using var stream = _res.ContentFileRead(path);
if (!typeChecker.CheckAssembly(stream))
if (!typeChecker.CheckAssembly(stream, resolver))
{
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
}
@@ -426,12 +427,15 @@ namespace Robust.Shared.ContentPack
};
}
internal static PEReader MakePEReader(Stream stream, bool leaveOpen=false)
internal static PEReader MakePEReader(Stream stream, bool leaveOpen=false, PEStreamOptions options=PEStreamOptions.Default)
{
if (!stream.CanSeek)
stream = leaveOpen ? stream.CopyToMemoryStream() : stream.ConsumeToMemoryStream();
return new PEReader(stream, leaveOpen ? PEStreamOptions.LeaveOpen : default);
if (leaveOpen)
options |= PEStreamOptions.LeaveOpen;
return new PEReader(stream, options);
}
}
}

View File

@@ -24,5 +24,10 @@
/// This update is called after the main state manager on render frames, thus only applies to the client.
/// </summary>
FramePostEngine,
/// <summary>
/// Ran after processing network packets and pending asynchronous tasks.
/// </summary>
InputPostEngine,
}
}

View File

@@ -305,6 +305,7 @@ Types:
ConcurrentQueue`1: { All: True }
ConcurrentStack`1: { All: True }
System.Collections:
BitArray: { All: True }
IEnumerable: { All: True }
IEnumerator: { All: True }
IReadOnlyList`1: { All: True }

View File

@@ -75,14 +75,7 @@ namespace Robust.Shared.GameObjects
LifeStage = ComponentLifeStage.Initializing;
entManager.EventBus.RaiseComponentEvent(this, type, CompInitInstance);
Initialize();
#if DEBUG
if (LifeStage != ComponentLifeStage.Initialized)
{
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Initialize)} in derived method.");
}
#endif
LifeStage = ComponentLifeStage.Initialized;
}
/// <summary>
@@ -95,14 +88,7 @@ namespace Robust.Shared.GameObjects
LifeStage = ComponentLifeStage.Starting;
entManager.EventBus.RaiseComponentEvent(this, CompStartupInstance);
Startup();
#if DEBUG
if (LifeStage != ComponentLifeStage.Running)
{
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Startup)} in derived method.");
}
#endif
LifeStage = ComponentLifeStage.Running;
}
/// <summary>
@@ -170,26 +156,6 @@ namespace Robust.Shared.GameObjects
private static readonly ComponentShutdown CompShutdownInstance = new();
private static readonly ComponentRemove CompRemoveInstance = new();
/// <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.
/// </summary>
protected virtual void Initialize()
{
LifeStage = ComponentLifeStage.Initialized;
}
/// <summary>
/// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized.
/// </summary>
/// <remarks>
/// Components are allowed to remove themselves in their own Startup function.
/// </remarks>
protected virtual void Startup()
{
LifeStage = ComponentLifeStage.Running;
}
/// <summary>
/// Called when the component is removed from an entity.
/// Shuts down the component.
@@ -208,21 +174,6 @@ namespace Robust.Shared.GameObjects
entManager.Dirty(this);
}
private static readonly ComponentState DefaultComponentState = new();
/// <inheritdoc />
public virtual ComponentState GetComponentState()
{
DebugTools.Assert(
Attribute.GetCustomAttribute(GetType(), typeof(NetworkedComponentAttribute)) != null,
$"Calling base {nameof(GetComponentState)} without being networked.");
return DefaultComponentState;
}
/// <inheritdoc />
public virtual void HandleComponentState(ComponentState? curState, ComponentState? nextState) { }
// these two methods clear the LastModifiedTick/CreationTick to mark it as "not different from prototype load".
// This is used as optimization in the game state system to avoid sending redundant component data.
internal virtual void ClearTicks()

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -23,7 +24,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Mapping of component name to type.
/// </summary>
private readonly Dictionary<string, ComponentRegistration> names = new();
private readonly Dictionary<string, ComponentRegistration> _names = new();
/// <summary>
/// Mapping of lowercase component names to their registration.
@@ -107,11 +108,11 @@ namespace Robust.Shared.GameObjects
IgnoredComponentNames.Remove(name);
}
if (names.ContainsKey(name))
if (_names.ContainsKey(name))
{
if (!overwrite)
{
throw new InvalidOperationException($"{name} is already registered, previous: {names[name]}");
throw new InvalidOperationException($"{name} is already registered, previous: {_names[name]}");
}
RemoveComponent(name);
@@ -130,7 +131,7 @@ namespace Robust.Shared.GameObjects
var registration = new ComponentRegistration(name, type, idx);
names[name] = registration;
_names[name] = registration;
_lowerCaseNames[lowerCaseName] = name;
types[type] = registration;
CompIdx.AssignArray(ref _array, idx, registration);
@@ -213,7 +214,7 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException($"Cannot add {name} to ignored components: It is already registered as ignored");
}
if (names.ContainsKey(name))
if (_names.ContainsKey(name))
{
if (!overwrite)
{
@@ -232,9 +233,9 @@ namespace Robust.Shared.GameObjects
if (_networkedComponents is not null)
throw new ComponentRegistrationLockException();
var registration = names[name];
var registration = _names[name];
names.Remove(registration.Name);
_names.Remove(registration.Name);
_lowerCaseNames.Remove(registration.Name.ToLowerInvariant());
types.Remove(registration.Type);
}
@@ -246,7 +247,7 @@ namespace Robust.Shared.GameObjects
componentName = lowerCaseName;
}
if (names.ContainsKey(componentName))
if (_names.ContainsKey(componentName))
{
return ComponentAvailability.Available;
}
@@ -313,7 +314,7 @@ namespace Robust.Shared.GameObjects
try
{
return names[componentName];
return _names[componentName];
}
catch (KeyNotFoundException)
{
@@ -321,6 +322,7 @@ namespace Robust.Shared.GameObjects
}
}
[Pure]
public string GetComponentName(Type componentType)
{
return GetRegistration(componentType).Name;
@@ -374,7 +376,7 @@ namespace Robust.Shared.GameObjects
componentName = lowerCaseName;
}
if (names.TryGetValue(componentName, out var tempRegistration))
if (_names.TryGetValue(componentName, out var tempRegistration))
{
registration = tempRegistration;
return true;
@@ -474,9 +476,9 @@ namespace Robust.Shared.GameObjects
// component names are 1:1 with component concrete types
// a subset of component names are networked
var networkedRegs = new List<ComponentRegistration>(names.Count);
var networkedRegs = new List<ComponentRegistration>(_names.Count);
foreach (var kvRegistration in names)
foreach (var kvRegistration in _names)
{
var registration = kvRegistration.Value;
if (Attribute.GetCustomAttribute(registration.Type, typeof(NetworkedComponentAttribute)) is NetworkedComponentAttribute)

View File

@@ -94,7 +94,6 @@ namespace Robust.Shared.GameObjects
[Obsolete("Use a system update loop instead")]
public static void SpawnRepeatingTimer(this EntityUid entity, int milliseconds, Action onFired, CancellationToken cancellationToken)
{
var entMan = IoCManager.Resolve<IEntityManager>();
entity
.EnsureTimerComponent()
.SpawnRepeating(milliseconds, onFired, cancellationToken);

View File

@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Physics.Components;
@@ -32,6 +33,8 @@ namespace Robust.Shared.GameObjects
private const int EntityCapacity = 1024;
private const int NetComponentCapacity = 8;
private static readonly ComponentState DefaultComponentState = new();
private readonly Dictionary<EntityUid, Dictionary<ushort, Component>> _netComponents
= new(EntityCapacity);
@@ -700,15 +703,15 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetComponent<T>(EntityUid uid)
public T GetComponent<T>(EntityUid uid) where T : IComponent
{
var dict = _entTraitArray[CompIdx.ArrayIndex<T>()];
DebugTools.Assert(dict != null, $"Unknown component: {typeof(T).Name}");
if (dict!.TryGetValue(uid, out var comp))
{
if (!comp.Deleted)
if (!comp!.Deleted)
{
return (T)(IComponent)comp;
return (T)(IComponent) comp;
}
}
@@ -1034,8 +1037,7 @@ namespace Robust.Shared.GameObjects
where TComp1 : Component
{
var trait1 = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
return new EntityQueryEnumerator<TComp1>(trait1, metaTraits);
return new EntityQueryEnumerator<TComp1>(trait1, _metaQuery);
}
public EntityQueryEnumerator<TComp1, TComp2> EntityQueryEnumerator<TComp1, TComp2>()
@@ -1044,8 +1046,7 @@ namespace Robust.Shared.GameObjects
{
var trait1 = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
var trait2 = _entTraitArray[CompIdx.ArrayIndex<TComp2>()];
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
return new EntityQueryEnumerator<TComp1, TComp2>(trait1, trait2, metaTraits);
return new EntityQueryEnumerator<TComp1, TComp2>(trait1, trait2, _metaQuery);
}
public EntityQueryEnumerator<TComp1, TComp2, TComp3> EntityQueryEnumerator<TComp1, TComp2, TComp3>()
@@ -1056,8 +1057,7 @@ namespace Robust.Shared.GameObjects
var trait1 = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
var trait2 = _entTraitArray[CompIdx.ArrayIndex<TComp2>()];
var trait3 = _entTraitArray[CompIdx.ArrayIndex<TComp3>()];
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
return new EntityQueryEnumerator<TComp1, TComp2, TComp3>(trait1, trait2, trait3, metaTraits);
return new EntityQueryEnumerator<TComp1, TComp2, TComp3>(trait1, trait2, trait3, _metaQuery);
}
public EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4> EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>()
@@ -1070,8 +1070,8 @@ namespace Robust.Shared.GameObjects
var trait2 = _entTraitArray[CompIdx.ArrayIndex<TComp2>()];
var trait3 = _entTraitArray[CompIdx.ArrayIndex<TComp3>()];
var trait4 = _entTraitArray[CompIdx.ArrayIndex<TComp4>()];
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
return new EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>(trait1, trait2, trait3, trait4, metaTraits);
return new EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>(trait1, trait2, trait3, trait4, _metaQuery);
}
/// <inheritdoc />
@@ -1091,15 +1091,11 @@ namespace Robust.Shared.GameObjects
}
else
{
var metaComps = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
foreach (var t1Comp in comps!.Values)
foreach (var t1Comp in comps.Values)
{
if (t1Comp.Deleted || !metaComps.TryGetValue(t1Comp.Owner, out var metaComp)) continue;
if (t1Comp.Deleted || !_metaQuery.TryGetComponentInternal(t1Comp.Owner, out var metaComp)) continue;
var meta = (MetaDataComponent)metaComp;
if (meta.EntityPaused) continue;
if (metaComp.EntityPaused) continue;
yield return (T)(object)t1Comp;
}
@@ -1304,7 +1300,7 @@ namespace Robust.Shared.GameObjects
var getState = new ComponentGetState(session, fromTick);
eventBus.RaiseComponentEvent(component, ref getState);
return getState.State ?? component.GetComponentState();
return getState.State ?? DefaultComponentState;
}
public bool CanGetComponentState(IEventBus eventBus, IComponent component, ICommonSession player)
@@ -1369,6 +1365,7 @@ namespace Robust.Shared.GameObjects
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1 GetComponent(EntityUid uid)
{
if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted)
@@ -1378,6 +1375,7 @@ namespace Robust.Shared.GameObjects
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
{
if (uid == null)
@@ -1390,6 +1388,7 @@ namespace Robust.Shared.GameObjects
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryGetComponent(EntityUid uid, [NotNullWhen(true)] out TComp1? component)
{
if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted)
@@ -1403,12 +1402,14 @@ namespace Robust.Shared.GameObjects
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComponent(EntityUid uid)
{
return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
{
if (component != null)
@@ -1430,6 +1431,7 @@ namespace Robust.Shared.GameObjects
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1? CompOrNull(EntityUid uid)
{
if (TryGetComponent(uid, out var comp))
@@ -1437,6 +1439,103 @@ namespace Robust.Shared.GameObjects
return null;
}
#region Internal
/// <summary>
/// Elides the component.Deleted check of <see cref="GetComponent"/>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
internal TComp1 GetComponentInternal(EntityUid uid)
{
if (_traitDict.TryGetValue(uid, out var comp))
return (TComp1) comp;
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}");
}
/// <summary>
/// Elides the component.Deleted check of <see cref="TryGetComponent(System.Nullable{Robust.Shared.GameObjects.EntityUid},out TComp1?)"/>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
internal bool TryGetComponentInternal([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
{
if (uid == null)
{
component = default;
return false;
}
return TryGetComponentInternal(uid.Value, out component);
}
/// <summary>
/// Elides the component.Deleted check of <see cref="TryGetComponent(System.Nullable{Robust.Shared.GameObjects.EntityUid},out TComp1?)"/>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
internal bool TryGetComponentInternal(EntityUid uid, [NotNullWhen(true)] out TComp1? component)
{
if (_traitDict.TryGetValue(uid, out var comp))
{
component = (TComp1) comp;
return true;
}
component = default;
return false;
}
/// <summary>
/// Elides the component.Deleted check of <see cref="HasComponent"/>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
internal bool HasComponentInternal(EntityUid uid)
{
return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted;
}
/// <summary>
/// Elides the component.Deleted check of <see cref="Resolve"/>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
internal bool ResolveInternal(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
{
if (component != null)
{
DebugTools.Assert(uid == component.Owner, "Specified Entity is not the component's Owner!");
return true;
}
if (_traitDict.TryGetValue(uid, out var comp))
{
component = (TComp1)comp;
return true;
}
if (logMissing)
_sawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {uid}!\n{new StackTrace(1, true)}");
return false;
}
/// <summary>
/// Elides the component.Deleted check of <see cref="CompOrNull"/>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
internal TComp1? CompOrNullInternal(EntityUid uid)
{
if (TryGetComponent(uid, out var comp))
return comp;
return null;
}
#endregion
}
#region Query
@@ -1448,14 +1547,14 @@ namespace Robust.Shared.GameObjects
where TComp1 : Component
{
private Dictionary<EntityUid, Component>.Enumerator _traitDict;
private readonly Dictionary<EntityUid, Component> _metaDict;
private readonly EntityQuery<MetaDataComponent> _metaQuery;
public EntityQueryEnumerator(
Dictionary<EntityUid, Component> traitDict,
Dictionary<EntityUid, Component> metaDict)
EntityQuery<MetaDataComponent> metaQuery)
{
_traitDict = traitDict.GetEnumerator();
_metaDict = metaDict;
_metaQuery = metaQuery;
}
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1)
@@ -1476,7 +1575,7 @@ namespace Robust.Shared.GameObjects
continue;
}
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
{
continue;
}
@@ -1508,16 +1607,16 @@ namespace Robust.Shared.GameObjects
{
private Dictionary<EntityUid, Component>.Enumerator _traitDict;
private readonly Dictionary<EntityUid, Component> _traitDict2;
private readonly Dictionary<EntityUid, Component> _metaDict;
private readonly EntityQuery<MetaDataComponent> _metaQuery;
public EntityQueryEnumerator(
Dictionary<EntityUid, Component> traitDict,
Dictionary<EntityUid, Component> traitDict2,
Dictionary<EntityUid, Component> metaDict)
EntityQuery<MetaDataComponent> metaQuery)
{
_traitDict = traitDict.GetEnumerator();
_traitDict2 = traitDict2;
_metaDict = metaDict;
_metaQuery = metaQuery;
}
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2)
@@ -1539,7 +1638,7 @@ namespace Robust.Shared.GameObjects
continue;
}
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
{
continue;
}
@@ -1579,18 +1678,18 @@ namespace Robust.Shared.GameObjects
private Dictionary<EntityUid, Component>.Enumerator _traitDict;
private readonly Dictionary<EntityUid, Component> _traitDict2;
private readonly Dictionary<EntityUid, Component> _traitDict3;
private readonly Dictionary<EntityUid, Component> _metaDict;
private readonly EntityQuery<MetaDataComponent> _metaQuery;
public EntityQueryEnumerator(
Dictionary<EntityUid, Component> traitDict,
Dictionary<EntityUid, Component> traitDict2,
Dictionary<EntityUid, Component> traitDict3,
Dictionary<EntityUid, Component> metaDict)
EntityQuery<MetaDataComponent> metaQuery)
{
_traitDict = traitDict.GetEnumerator();
_traitDict2 = traitDict2;
_traitDict3 = traitDict3;
_metaDict = metaDict;
_metaQuery = metaQuery;
}
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3)
@@ -1613,7 +1712,7 @@ namespace Robust.Shared.GameObjects
continue;
}
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
{
continue;
}
@@ -1664,20 +1763,20 @@ namespace Robust.Shared.GameObjects
private readonly Dictionary<EntityUid, Component> _traitDict2;
private readonly Dictionary<EntityUid, Component> _traitDict3;
private readonly Dictionary<EntityUid, Component> _traitDict4;
private readonly Dictionary<EntityUid, Component> _metaDict;
private readonly EntityQuery<MetaDataComponent> _metaQuery;
public EntityQueryEnumerator(
Dictionary<EntityUid, Component> traitDict,
Dictionary<EntityUid, Component> traitDict2,
Dictionary<EntityUid, Component> traitDict3,
Dictionary<EntityUid, Component> traitDict4,
Dictionary<EntityUid, Component> metaDict)
EntityQuery<MetaDataComponent> metaQuery)
{
_traitDict = traitDict.GetEnumerator();
_traitDict2 = traitDict2;
_traitDict3 = traitDict3;
_traitDict4 = traitDict4;
_metaDict = metaDict;
_metaQuery = metaQuery;
}
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3, [NotNullWhen(true)] out TComp4? comp4)
@@ -1701,7 +1800,7 @@ namespace Robust.Shared.GameObjects
continue;
}
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
{
continue;
}

View File

@@ -18,6 +18,8 @@ namespace Robust.Shared.GameObjects
{
public delegate void EntityUidQueryCallback(EntityUid uid);
public delegate void ComponentQueryCallback<T>(EntityUid uid, T component) where T : Component;
/// <inheritdoc />
[Virtual]
public partial class EntityManager : IEntityManager
@@ -36,6 +38,9 @@ namespace Robust.Shared.GameObjects
// positions on spawn....
private SharedTransformSystem _xforms = default!;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<TransformComponent> _xformQuery;
#endregion Dependencies
/// <inheritdoc />
@@ -75,7 +80,8 @@ namespace Robust.Shared.GameObjects
public event Action<EntityUid>? EntityDirtied; // only raised after initialization
private string _xformName = string.Empty;
private string _metaName = string.Empty;
private SharedMapSystem _mapSystem = default!;
private ISawmill _sawmill = default!;
private ISawmill _resolveSawmill = default!;
@@ -102,7 +108,6 @@ namespace Robust.Shared.GameObjects
InitializeComponents();
_xformName = _componentFactory.GetComponentName(typeof(TransformComponent));
_metaName = _componentFactory.GetComponentName(typeof(MetaDataComponent));
_sawmill = LogManager.GetSawmill("entity");
_resolveSawmill = LogManager.GetSawmill("resolve");
@@ -114,7 +119,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public bool IsDefault(EntityUid uid)
{
if (!TryGetComponent<MetaDataComponent>(uid, out var metadata) || metadata.EntityPrototype == null)
if (!_metaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null)
return false;
var prototype = metadata.EntityPrototype;
@@ -221,7 +226,10 @@ namespace Robust.Shared.GameObjects
_entitySystemManager.Initialize();
Started = true;
_eventBus.CalcOrdering();
_xforms = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_mapSystem = System<SharedMapSystem>();
_xforms = System<SharedTransformSystem>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
}
public virtual void Shutdown()
@@ -308,7 +316,7 @@ namespace Robust.Shared.GameObjects
if (coordinates.IsValid(this))
{
_xforms.SetCoordinates(newEntity, GetComponent<TransformComponent>(newEntity), coordinates, unanchor: false);
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
}
return newEntity;
@@ -318,7 +326,7 @@ namespace Robust.Shared.GameObjects
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, default, overrides);
var transform = GetComponent<TransformComponent>(newEntity);
var transform = _xformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
{
@@ -335,7 +343,7 @@ namespace Robust.Shared.GameObjects
EntityCoordinates coords;
if (transform.Anchored && _mapManager.TryFindGridAt(coordinates, out var gridUid, out var grid))
{
coords = new EntityCoordinates(gridUid, grid.WorldToLocal(coordinates.Position));
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
_xforms.SetCoordinates(newEntity, transform, coords, unanchor: false);
}
else
@@ -745,7 +753,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(_metaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
}
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
@@ -757,12 +765,12 @@ namespace Robust.Shared.GameObjects
{
try
{
var meta = GetComponent<MetaDataComponent>(entity);
var meta = _metaQuery.GetComponent(entity);
InitializeEntity(entity, meta);
StartEntity(entity);
// If the map we're initializing the entity on is initialized, run map init on it.
if (_mapManager.IsMapInitialized(mapId ?? GetComponent<TransformComponent>(entity).MapID))
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
RunMapInit(entity, meta);
}
catch (Exception e)

View File

@@ -398,7 +398,7 @@ public partial class EntitySystem
/// <inheritdoc cref="IEntityManager.GetComponent&lt;T&gt;"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected T Comp<T>(EntityUid uid) where T : class, IComponent
protected T Comp<T>(EntityUid uid) where T : Component
{
return EntityManager.GetComponent<T>(uid);
}

View File

@@ -1,7 +1,4 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
@@ -63,24 +60,5 @@ namespace Robust.Shared.GameObjects
/// This is the last game tick Dirty() was called.
/// </summary>
GameTick LastModifiedTick { get; }
/// <summary>
/// Get the component's state for replicating on the client.
/// </summary>
/// <returns>ComponentState object</returns>
ComponentState GetComponentState();
/// <summary>
/// Handles an incoming component state from the server.
/// </summary>
/// <remarks>
/// This function should only be called on the client.
/// Both, one, or neither of the two states can be null.
/// On the next tick, curState will be nextState.
/// Passing null for both arguments should do nothing.
/// </remarks>
/// <param name="curState">Current component state for this tick.</param>
/// <param name="nextState">Next component state for the next tick.</param>
void HandleComponentState(ComponentState? curState, ComponentState? nextState);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@@ -153,6 +154,7 @@ namespace Robust.Shared.GameObjects
/// <exception cref="UnknownComponentException">
/// Thrown if no component exists with the given type <see cref="componentType"/>.
/// </exception>
[Pure]
string GetComponentName(Type componentType);
/// <summary>

View File

@@ -236,7 +236,7 @@ namespace Robust.Shared.GameObjects
/// <typeparam name="T">A trait or type of a component to retrieve.</typeparam>
/// <param name="uid">Entity UID to look on.</param>
/// <returns>The component of Type from the Entity.</returns>
T GetComponent<T>(EntityUid uid);
T GetComponent<T>(EntityUid uid) where T : IComponent;
/// <summary>
/// Returns the component of a specific type.

View File

@@ -40,7 +40,7 @@ namespace Robust.Shared.GameObjects
else if (TryComp(uid, out PhysicsComponent? physics))
_physics.SetCanCollide(uid, true, body: physics);
Dirty(component);
Dirty(uid, component);
}
private void OnHandleState(EntityUid uid, CollisionWakeComponent component, ref ComponentHandleState args)

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics;
@@ -26,12 +26,10 @@ public sealed partial class EntityLookupSystem
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
LookupFlags flags)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
var lookup = _broadQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
var localAABB = invMatrix.TransformBox(worldAABB);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -79,12 +77,10 @@ public sealed partial class EntityLookupSystem
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
LookupFlags flags)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
var lookup = _broadQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
// We don't just use CalcBoundingBox because the transformed bounds might be tighter.
var localAABB = invMatrix.TransformBox(worldBounds);
@@ -132,12 +128,10 @@ public sealed partial class EntityLookupSystem
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldAABB);
var lookup = _broadQuery.GetComponent(lookupUid);
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldAABB);
var state = (ignored, found: false);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -197,12 +191,10 @@ public sealed partial class EntityLookupSystem
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldBounds);
var lookup = _broadQuery.GetComponent(lookupUid);
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldBounds);
var state = (ignored, found: false);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -259,35 +251,35 @@ public sealed partial class EntityLookupSystem
return state.found;
}
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd, EntityQuery<TransformComponent> xformQuery)
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd)
{
var childEnumerator = xformQuery.GetComponent(uid).ChildEnumerator;
var childEnumerator = _xformQuery.GetComponent(uid).ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
toAdd.Add(child.Value);
RecursiveAdd(child.Value, ref toAdd, xformQuery);
RecursiveAdd(child.Value, ref toAdd);
}
}
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags, EntityQuery<TransformComponent> xformQuery)
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags)
{
if ((flags & LookupFlags.Contained) == 0x0 || intersecting.Count == 0)
return;
var conQuery = GetEntityQuery<ContainerManagerComponent>();
var toAdd = new ValueList<EntityUid>();
foreach (var uid in intersecting)
{
if (!conQuery.TryGetComponent(uid, out var conManager)) continue;
if (!_containerQuery.TryGetComponent(uid, out var conManager))
continue;
foreach (var con in conManager.GetAllContainers())
{
foreach (var contained in con.ContainedEntities)
{
toAdd.Add(contained);
RecursiveAdd(contained, ref toAdd, xformQuery);
RecursiveAdd(contained, ref toAdd);
}
}
}
@@ -321,11 +313,9 @@ public sealed partial class EntityLookupSystem
float arcWidth,
LookupFlags flags = DefaultFlags)
{
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
{
var angle = new Angle(_transform.GetWorldPosition(entity, xformQuery) - coordinates.Position);
var angle = new Angle(_transform.GetWorldPosition(entity) - coordinates.Position);
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
angle.Degrees > direction.Degrees - arcWidth / 2)
yield return entity;
@@ -340,48 +330,63 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return false;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
var found = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery)) return true;
}
var state = (this, worldAABB, flags, found);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent _, ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found) tuple) =>
{
if (!tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldAABB, tuple.flags))
return true;
tuple.found = true;
return false;
});
if (state.found)
return true;
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
AddEntitiesIntersecting(grid.Owner, intersecting, worldAABB, flags, lookupQuery, xformQuery);
var state = (this, _map, intersecting, worldAABB, flags);
if ((flags & LookupFlags.Static) != 0x0)
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid gridUid, MapGridComponent grid, ref (
EntityLookupSystem lookup, SharedMapSystem _map, HashSet<EntityUid> intersecting,
Box2 worldAABB, LookupFlags flags) tuple) =>
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
tuple.lookup.AddEntitiesIntersecting(gridUid, tuple.intersecting, tuple.worldAABB, tuple.flags);
if ((tuple.flags & LookupFlags.Static) != 0x0)
{
if (Deleted(uid)) continue;
intersecting.Add(uid);
// TODO: Need a struct enumerator version.
foreach (var uid in tuple._map.GetAnchoredEntities(gridUid, grid, tuple.worldAABB))
{
if (tuple.lookup.Deleted(uid))
continue;
tuple.intersecting.Add(uid);
}
}
}
}
return true;
});
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -392,39 +397,55 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
var worldAABB = worldBounds.CalcBoundingBox();
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
{
if (AnyEntitiesIntersecting(grid.Owner, worldBounds, flags, lookupQuery, xformQuery)) return true;
}
const bool found = false;
var state = (this, worldBounds, flags, found);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent grid, ref (EntityLookupSystem lookup, Box2Rotated worldBounds, LookupFlags flags, bool found) tuple) =>
{
if (tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldBounds, tuple.flags))
{
tuple.found = true;
return false;
}
return true;
});
if (state.found)
return true;
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags, lookupQuery, xformQuery);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return intersecting;
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
var state = (this, intersecting, worldBounds, flags);
_mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, static
(EntityUid uid, MapGridComponent _,
ref (EntityLookupSystem lookup,
HashSet<EntityUid> intersecting,
Box2Rotated worldBounds,
LookupFlags flags) tuple) =>
{
AddEntitiesIntersecting(grid.Owner, intersecting, worldBounds, flags, lookupQuery, xformQuery);
}
tuple.lookup.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.worldBounds, tuple.flags);
return true;
});
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -438,50 +459,72 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var worldAABB = GetWorldAABB(uid);
var mapID = Transform(uid).MapID;
var mapID = _xformQuery.GetComponent(uid).MapID;
if (mapID == MapId.Nullspace) return false;
if (mapID == MapId.Nullspace)
return false;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
const bool found = false;
var state = (this, worldAABB, flags, found, uid);
_mapManager.FindGridsIntersecting(mapID, worldAABB, ref state,
static (EntityUid gridUid, MapGridComponent grid,
ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found, EntityUid ignored) tuple) =>
{
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
{
tuple.found = true;
return false;
}
foreach (var grid in _mapManager.FindGridsIntersecting(mapID, worldAABB))
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
});
var mapUid = _mapManager.GetMapEntityId(mapID);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
}
public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return false;
if (mapPos.MapId == MapId.Nullspace)
return false;
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(mapPos.Position - rangeVec, mapPos.Position + rangeVec);
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var grid in _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB))
const bool found = false;
var state = (this, worldAABB, flags, found, uid);
_mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static (
EntityUid gridUid,
MapGridComponent _, ref (
EntityLookupSystem lookup,
Box2 worldAABB,
LookupFlags flags,
bool found,
EntityUid ignored) tuple) =>
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
{
tuple.found = true;
return false;
}
return true;
});
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
}
public HashSet<EntityUid> GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapPos.MapId == MapId.Nullspace)
return new HashSet<EntityUid>();
var intersecting = GetEntitiesInRange(mapPos, range, flags);
intersecting.Remove(uid);
@@ -490,12 +533,13 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var xform = Transform(uid);
var xform = _xformQuery.GetComponent(uid);
var mapId = xform.MapID;
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return new HashSet<EntityUid>();
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var bounds = GetAABBNoContainer(uid, worldPos, worldRot);
var intersecting = GetEntitiesIntersecting(mapId, bounds, flags);
@@ -509,7 +553,8 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(EntityCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
return AnyEntitiesIntersecting(mapPos, flags);
@@ -517,7 +562,8 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
return AnyEntitiesInRange(mapPos, range, flags);
@@ -581,7 +627,8 @@ public sealed partial class EntityLookupSystem
{
DebugTools.Assert(range > 0, "Range must be a positive float");
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return new HashSet<EntityUid>();
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
@@ -601,7 +648,7 @@ public sealed partial class EntityLookupSystem
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookup = Comp<BroadphaseComponent>(gridId);
var lookup = _broadQuery.GetComponent(gridId);
var intersecting = new HashSet<EntityUid>();
var tileSize = grid.TileSize;
@@ -647,8 +694,7 @@ public sealed partial class EntityLookupSystem
}
}
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
@@ -656,8 +702,10 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid 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 new HashSet<EntityUid>();
var lookup = Comp<BroadphaseComponent>(gridId);
if (!_mapManager.TryGetGrid(gridId, out var grid))
return new HashSet<EntityUid>();
var lookup = _broadQuery.GetComponent(gridId);
var tileSize = grid.TileSize;
var aabb = GetLocalBounds(gridIndices, tileSize);
return GetEntitiesIntersecting(lookup, aabb, flags);
@@ -707,22 +755,20 @@ public sealed partial class EntityLookupSystem
}, aabb);
}
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
if (!_mapManager.GridExists(gridId))
return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -731,12 +777,10 @@ public sealed partial class EntityLookupSystem
{
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -754,8 +798,7 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(BroadphaseComponent 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);
var localAABB = _transform.GetInvWorldMatrix(component.Owner).TransformBox(worldAABB);
if ((flags & LookupFlags.Dynamic) != 0x0)
{
@@ -793,7 +836,7 @@ public sealed partial class EntityLookupSystem
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
@@ -838,7 +881,7 @@ public sealed partial class EntityLookupSystem
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
AddContained(intersecting, flags, GetEntityQuery<TransformComponent>());
AddContained(intersecting, flags);
return intersecting;
}
@@ -850,36 +893,24 @@ public sealed partial class EntityLookupSystem
/// <summary>
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2 worldAABB)
public void FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds, ComponentQueryCallback<BroadphaseComponent> callback)
{
if (mapId == MapId.Nullspace) yield break;
if (mapId == MapId.Nullspace)
return;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var mapUid = _mapManager.GetMapEntityId(mapId);
callback(mapUid, _broadQuery.GetComponent(mapUid));
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
var state = (callback, _broadQuery);
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return lookupQuery.GetComponent(grid.Owner);
}
}
/// <summary>
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds)
{
if (mapId == MapId.Nullspace) yield break;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
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.Owner);
}
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref state,
static (EntityUid uid, MapGridComponent grid,
ref (ComponentQueryCallback<BroadphaseComponent> callback, EntityQuery<BroadphaseComponent> _broadQuery)
tuple) =>
{
tuple.callback(uid, tuple._broadQuery.GetComponent(uid));
return true;
});
}
#endregion
@@ -904,8 +935,7 @@ public sealed partial class EntityLookupSystem
if (worldMatrix == null || angle == null)
{
var gridXform = Transform(tileRef.GridUid);
var (_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix();
var (_, wAng, wMat) = _transform.GetWorldPositionRotationMatrix(tileRef.GridUid);
worldMatrix = wMat;
angle = wAng;
}

View File

@@ -69,6 +69,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<BroadphaseComponent> _broadQuery;

View File

@@ -53,7 +53,7 @@ public abstract class OccluderSystem : ComponentTreeSystem<OccluderTreeComponent
return;
comp.BoundingBox = box;
Dirty(comp);
Dirty(uid, comp);
if (comp.TreeUid != null)
QueueTreeUpdate(uid, comp);
@@ -65,7 +65,7 @@ public abstract class OccluderSystem : ComponentTreeSystem<OccluderTreeComponent
return;
comp.Enabled = enabled;
Dirty(comp);
Dirty(uid, comp);
QueueTreeUpdate(uid, comp);
}
#endregion

View File

@@ -46,7 +46,7 @@ public abstract class SharedAppearanceSystem : EntitySystem
DebugTools.Assert(value.GetType().IsValueType || value is ICloneable, "Appearance data values must be cloneable.");
component.AppearanceData[key] = value;
Dirty(component);
Dirty(uid, component);
QueueUpdate(uid, component);
}

View File

@@ -10,11 +10,12 @@ using Robust.Shared.Random;
namespace Robust.Shared.GameObjects;
public abstract class SharedAudioSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IConfigurationManager CfgManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IRobustRandom RandMan = default!;
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
/// <summary>
/// Default max range at which the sound can be heard.
@@ -292,7 +293,7 @@ public abstract class SharedAudioSystem : EntitySystem
protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates)
{
if (_mapManager.TryFindGridAt(mapCoordinates, out var gridUid, out var mapGrid))
return new EntityCoordinates(gridUid, mapGrid.WorldToLocal(mapCoordinates.Position));
return new EntityCoordinates(gridUid, _map.WorldToLocal(gridUid, mapGrid, mapCoordinates.Position));
if (_mapManager.HasMapEntity(mapCoordinates.MapId))
return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId), mapCoordinates.Position);

View File

@@ -0,0 +1,8 @@
using Robust.Shared.IoC;
namespace Robust.Shared.GameObjects;
public abstract class SharedEyeSystem : EntitySystem
{
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
@@ -21,9 +22,9 @@ namespace Robust.Shared.GameObjects
public abstract class SharedGridFixtureSystem : EntitySystem
{
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
protected ISawmill Sawmill = default!;
private bool _enabled;
private float _fixtureEnlargement;
@@ -33,12 +34,17 @@ namespace Robust.Shared.GameObjects
{
base.Initialize();
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
Sawmill = Logger.GetSawmill("physics");
_cfg.OnValueChanged(CVars.GenerateGridFixtures, SetEnabled, true);
_cfg.OnValueChanged(CVars.GridFixtureEnlargement, SetEnlargement, true);
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<RegenerateGridBoundsEvent>(OnGridBoundsRegenerate);
}
private void OnGridBoundsRegenerate(ref RegenerateGridBoundsEvent ev)
{
RegenerateCollision(ev.Entity, ev.ChunkRectangles, ev.RemovedChunks);
}
protected virtual void OnGridInit(GridInitializeEvent ev)
@@ -48,7 +54,7 @@ namespace Robust.Shared.GameObjects
// This will also check for grid splits if applicable.
var grid = Comp<MapGridComponent>(ev.EntityUid);
grid.RegenerateCollision(grid.GetMapChunks().Values.ToHashSet());
_map.RegenerateCollision(ev.EntityUid, grid, _map.GetMapChunks(ev.EntityUid, grid).Values.ToHashSet());
}
public override void Shutdown()
@@ -68,23 +74,24 @@ namespace Robust.Shared.GameObjects
Dictionary<MapChunk, List<Box2i>> mapChunks,
List<MapChunk> removedChunks)
{
if (!_enabled) return;
if (!_enabled)
return;
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out TransformComponent? xform))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
return;
}

View File

@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
@@ -224,7 +227,7 @@ public abstract partial class SharedMapSystem
if (chunkData.IsDeleted())
continue;
var chunk = component.GetOrAddChunk(chunkData.Index);
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == component.ChunkSize * component.ChunkSize);
@@ -237,7 +240,7 @@ public abstract partial class SharedMapSystem
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
SetChunkTile(uid, component, chunk, x, y, tile);
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
}
}
@@ -247,13 +250,13 @@ public abstract partial class SharedMapSystem
{
if (chunkData.IsDeleted())
{
component.RemoveChunk(chunkData.Index);
RemoveChunk(uid, component, chunkData.Index);
continue;
}
var chunk = component.GetOrAddChunk(chunkData.Index);
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
component.RegenerateCollision(chunk);
RegenerateCollision(uid, component, chunk);
}
}
@@ -263,12 +266,12 @@ public abstract partial class SharedMapSystem
foreach (var index in component.Chunks.Keys)
{
if (!state.FullGridData.ContainsKey(index))
component.RemoveChunk(index);
RemoveChunk(uid, component, index);
}
foreach (var (index, tiles) in state.FullGridData)
{
var chunk = component.GetOrAddChunk(index);
var chunk = GetOrAddChunk(uid, component, index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(tiles.Length == component.ChunkSize * component.ChunkSize);
@@ -281,13 +284,13 @@ public abstract partial class SharedMapSystem
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
SetChunkTile(uid, component, chunk, x, y, tile);
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
}
}
chunk.SuppressCollisionRegeneration = false;
component.RegenerateCollision(chunk);
RegenerateCollision(uid, component, chunk);
}
}
@@ -300,7 +303,7 @@ public abstract partial class SharedMapSystem
{
if (args.FromTick <= component.CreationTick)
{
GetFullState(component, ref args);
GetFullState(uid, component, ref args);
return;
}
@@ -324,7 +327,7 @@ public abstract partial class SharedMapSystem
chunkData.Add(ChunkDatum.CreateDeleted(indices));
}
foreach (var (index, chunk) in component.GetMapChunks())
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
if (chunk.LastTileModifiedTick < fromTick)
continue;
@@ -348,11 +351,11 @@ public abstract partial class SharedMapSystem
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
}
private void GetFullState(MapGridComponent component, ref ComponentGetState args)
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
{
var chunkData = new Dictionary<Vector2i, Tile[]>();
foreach (var (index, chunk) in component.GetMapChunks())
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
var tileBuffer = new Tile[component.ChunkSize * (uint)component.ChunkSize];
@@ -385,7 +388,6 @@ public abstract partial class SharedMapSystem
foreach (var chunk in component.Chunks.Values)
{
chunk.TileModified += component.OnTileModified;
chunk.LastTileModifiedTick = curTick;
}
@@ -445,7 +447,7 @@ public abstract partial class SharedMapSystem
if (!Resolve(uid, ref xform))
return new Box2();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, GetEntityQuery<TransformComponent>());
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var aabb = grid.LocalAABB.Translated(worldPos);
return new Box2Rotated(aabb, worldRot, worldPos).CalcBoundingBox();
@@ -456,7 +458,7 @@ public abstract partial class SharedMapSystem
DebugTools.Assert(!EntityManager.HasComponent<MapComponent>(uid));
var aabb = GetWorldAABB(uid, grid);
if (!TryComp<TransformComponent>(uid, out var xform))
if (!_xformQuery.TryGetComponent(uid, out var xform))
return;
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
@@ -486,4 +488,796 @@ public abstract partial class SharedMapSystem
movedGrids.MovedGrids.Remove(uid);
}
}
private void RemoveChunk(EntityUid uid, MapGridComponent grid, Vector2i origin)
{
if (!grid.Chunks.TryGetValue(origin, out var chunk))
return;
if (_netManager.IsServer)
grid.ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
chunk.Fixtures.Clear();
grid.Chunks.Remove(origin);
if (grid.Chunks.Count == 0)
RaiseLocalEvent(uid, new EmptyGridEvent { GridId = uid }, true);
}
/// <summary>
/// Regenerates the chunk local bounds of this chunk.
/// </summary>
private void RegenerateCollision(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
{
RegenerateCollision(uid, grid, new HashSet<MapChunk> { mapChunk });
}
/// <summary>
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
/// </summary>
internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnlySet<MapChunk> chunks)
{
if (HasComp<MapComponent>(uid))
return;
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
var removedChunks = new List<MapChunk>();
foreach (var mapChunk in chunks)
{
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
// generate collision rectangles for this chunk based on filled tiles.
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
mapChunk.CachedBounds = localBounds;
if (mapChunk.FilledTiles > 0)
chunkRectangles.Add(mapChunk, rectangles);
else
{
// Gone. Reduced to atoms
// Need to do this before RemoveChunk because it clears fixtures.
FixturesComponent? manager = null;
PhysicsComponent? body = null;
TransformComponent? xform = null;
foreach (var fixture in mapChunk.Fixtures)
{
_fixtures.DestroyFixture(uid, fixture, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(uid, grid, mapChunk.Indices);
removedChunks.Add(mapChunk);
}
}
grid.LocalAABB = new Box2();
foreach (var chunk in grid.Chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
if (chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (grid.LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
grid.LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
grid.LocalAABB = grid.LocalAABB.Union(gridBounds);
}
}
// May have been deleted from the bulk update above!
if (Deleted(uid))
return;
_physics.WakeBody(uid);
OnGridBoundsChange(uid, grid);
var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks);
RaiseLocalEvent(ref ev);
}
#region TileAccess
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, Vector2i tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, tileCoordinates);
if (!grid.Chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(uid, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return GetTileRef(uid, grid, output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
}
/// <summary>
/// Returns the tile at the given chunk indices.
/// </summary>
/// <param name="mapChunk"></param>
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
internal TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, ushort xIndex, ushort yIndex)
{
if (xIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
return new TileRef(uid, indices, mapChunk.GetTile(xIndex, yIndex));
}
public IEnumerable<TileRef> GetAllTiles(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
foreach (var kvChunk in grid.Chunks)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = chunk.GetTile(x, y);
if (ignoreEmpty && tile.IsEmpty)
continue;
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * grid.ChunkSize;
yield return new TileRef(uid, gridX, gridY, tile);
}
}
}
}
public GridTileEnumerator GetAllTilesEnumerator(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
return new GridTileEnumerator(uid, grid.Chunks.GetEnumerator(), grid.ChunkSize, ignoreEmpty);
}
public void SetTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Tile tile)
{
var localTile = CoordinatesToTile(uid, grid, coords);
SetTile(uid, grid, new Vector2i(localTile.X, localTile.Y), tile);
}
public void SetTile(EntityUid uid, MapGridComponent grid, Vector2i gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
// Yeah it's noodly.
// RegenerateCollision(chunk);
}
public void SetTiles(EntityUid uid, MapGridComponent grid, List<(Vector2i GridIndices, Tile Tile)> tiles)
{
if (tiles.Count == 0)
return;
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
foreach (var chunk in chunks)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(uid, grid, chunks);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
return GetLocalTilesIntersecting(uid, grid, localAABB, ignoreEmpty, predicate);
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tile;
}
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tile;
}
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(uid, grid, new Vector2i(x, y));
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
yield return tile;
}
else if (!ignoreEmpty)
{
var tile = new TileRef(uid, x, y, Tile.Empty);
if (predicate == null || predicate(tile))
yield return tile;
}
}
}
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
var circleGridPos = new EntityCoordinates(uid, WorldToLocal(uid, grid, worldArea.Position));
foreach (var tile in GetTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate))
{
var local = GridTileToLocal(uid, grid, tile.GridIndices);
if (!local.TryDistance(EntityManager, _transform, circleGridPos, out var distance))
{
continue;
}
if (distance <= worldArea.Radius)
{
yield return tile;
}
}
}
private bool TryGetTile(EntityUid uid, MapGridComponent grid, Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
{
// Similar to TryGetTileRef but for the tiles intersecting iterators.
var gridChunk = GridTileToChunkIndices(uid, grid, indices);
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(indices);
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
{
tileRef = null;
return false;
}
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(uid, indices.X, indices.Y, Tile.Empty);
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
tileRef = null;
return false;
}
#endregion TileAccess
#region ChunkAccess
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, int xIndex, int yIndex)
{
return GetOrAddChunk(uid, grid, new Vector2i(xIndex, yIndex));
}
internal bool TryGetChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
{
return grid.Chunks.TryGetValue(chunkIndices, out chunk);
}
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
{
if (grid.Chunks.TryGetValue(chunkIndices, out var output))
return output;
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, grid.ChunkSize)
{
LastTileModifiedTick = _timing.CurTick
};
return grid.Chunks[chunkIndices] = newChunk;
}
public bool HasChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
{
return grid.Chunks.ContainsKey(chunkIndices);
}
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks(EntityUid uid, MapGridComponent grid)
{
return grid.Chunks;
}
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
var localAABB = _transform.GetInvWorldMatrix(uid).TransformBox(worldAABB);
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
return new ChunkEnumerator(grid.Chunks, localArea, grid.ChunkSize);
}
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
#endregion ChunkAccess
#region SnapGridAccess
public int AnchoredEntityCount(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk))
return 0;
var (x, y) = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
// create an entire chunk for it.
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
var chunkTile = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
}
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
var chunkTile = chunk.GridTileToChunkTile(pos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid == null
? AnchoredEntitiesEnumerator.Empty
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
}
public IEnumerable<EntityUid> GetLocalAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localAABB, true, null))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
foreach (var tile in GetTilesIntersecting(uid, grid, worldAABB))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2Rotated worldBounds)
{
foreach (var tile in GetTilesIntersecting(uid, grid, worldBounds))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
#endif
return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords));
}
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordinates worldPos)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == worldPos.MapId);
#endif
var localPos = WorldToLocal(uid, grid, worldPos.Position);
return SnapGridLocalCellFor(uid, grid, localPos);
}
private Vector2i SnapGridLocalCellFor(EntityUid uid, MapGridComponent grid, Vector2 localPos)
{
var x = (int)Math.Floor(localPos.X / grid.TileSize);
var y = (int)Math.Floor(localPos.Y / grid.TileSize);
return new Vector2i(x, y);
}
public bool IsAnchored(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
var tilePos = TileIndicesFor(uid, grid, coords);
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, tilePos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid?.Contains(euid) == true;
}
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
return false;
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
return true;
}
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
return AddToSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
}
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
}
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
RemoveFromSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
}
private (MapChunk, Vector2i) ChunkAndOffsetForTile(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkIndices = GridTileToChunkIndices(uid, grid, pos);
var chunk = GetOrAddChunk(uid, grid, gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
public IEnumerable<EntityUid> GetInDir(EntityUid uid, MapGridComponent grid, EntityCoordinates position, Direction dir)
{
var pos = GetDirection(TileIndicesFor(uid, grid, position), dir);
return GetAnchoredEntities(uid, grid, pos);
}
public IEnumerable<EntityUid> GetOffset(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Vector2i offset)
{
var pos = TileIndicesFor(uid, grid, coords) + offset;
return GetAnchoredEntities(uid, grid, pos);
}
public IEnumerable<EntityUid> GetLocal(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public EntityCoordinates DirectionToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Direction direction)
{
return GridTileToLocal(uid, grid, GetDirection(TileIndicesFor(uid, grid, coords), direction));
}
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
var position = TileIndicesFor(uid, grid, coords);
foreach (var cell in GetAnchoredEntities(uid, grid, position))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, 1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, -1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(1, 0)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(-1, 0)))
yield return cell;
}
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, int n)
{
var position = TileIndicesFor(uid, grid, coords);
for (var y = -n; y <= n; ++y)
for (var x = -n; x <= n; ++x)
{
var enumerator = GetAnchoredEntitiesEnumerator(uid, grid, position + new Vector2i(x, y));
while (enumerator.MoveNext(out var cell))
{
yield return cell.Value;
}
}
}
#endregion
#region Transforms
public Vector2 WorldToLocal(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
{
var matrix = _transform.GetInvWorldMatrix(uid);
return matrix.Transform(posWorld);
}
public EntityCoordinates MapToGrid(EntityUid uid, MapCoordinates posWorld)
{
var mapId = _xformQuery.GetComponent(uid).MapID;
if (posWorld.MapId != mapId)
throw new ArgumentException(
$"Grid {uid} is on map {mapId}, but coords are on map {posWorld.MapId}.",
nameof(posWorld));
if (!TryComp<MapGridComponent>(uid, out var grid))
{
return new EntityCoordinates(MapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
}
return new EntityCoordinates(uid, WorldToLocal(uid, grid, posWorld.Position));
}
public Vector2 LocalToWorld(EntityUid uid, MapGridComponent grid, Vector2 posLocal)
{
var matrix = _transform.GetWorldMatrix(uid);
return matrix.Transform(posLocal);
}
public Vector2i WorldToTile(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
{
var local = WorldToLocal(uid, grid, posWorld);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i LocalToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coordinates)
{
var position = LocalToGrid(uid, grid, coordinates);
return new Vector2i((int) Math.Floor(position.X / grid.TileSize), (int) Math.Floor(position.Y / grid.TileSize));
}
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.MapId);
#endif
var local = WorldToLocal(uid, grid, coords.Position);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
#endif
var local = LocalToGrid(uid, grid, coords);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i LocalToChunkIndices(EntityUid uid, MapGridComponent grid, EntityCoordinates gridPos)
{
var local = LocalToGrid(uid, grid, gridPos);
var x = (int)Math.Floor(local.X / (grid.TileSize * grid.ChunkSize));
var y = (int)Math.Floor(local.Y / (grid.TileSize * grid.ChunkSize));
return new Vector2i(x, y);
}
public Vector2 LocalToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates position)
{
return position.EntityId == uid
? position.Position
: WorldToLocal(uid, grid, position.ToMapPos(EntityManager, _transform));
}
public bool CollidesWithGrid(EntityUid uid, MapGridComponent grid, Vector2i indices)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
return false;
var cTileIndices = chunk.GridTileToChunkTile(indices);
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
}
public Vector2i GridTileToChunkIndices(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float) grid.ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float) grid.ChunkSize);
return new Vector2i(x, y);
}
public EntityCoordinates GridTileToLocal(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
return new(uid,
new Vector2(gridTile.X * grid.TileSize + (grid.TileSize / 2f), gridTile.Y * grid.TileSize + (grid.TileSize / 2f)));
}
public Vector2 GridTileToWorldPos(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var locX = gridTile.X * grid.TileSize + (grid.TileSize / 2f);
var locY = gridTile.Y * grid.TileSize + (grid.TileSize / 2f);
return _transform.GetWorldMatrix(uid).Transform(new Vector2(locX, locY));
}
public MapCoordinates GridTileToWorld(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var parentMapId = _xformQuery.GetComponent(uid).MapID;
return new(GridTileToWorldPos(uid, grid, gridTile), parentMapId);
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2i indices, out TileRef tile)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = GetTileRef(uid, grid, chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
return true;
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords), out tile);
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2 worldPos, out TileRef tile)
{
return TryGetTileRef(uid, grid, WorldToTile(uid, grid, worldPos), out tile);
}
#endregion Transforms
/// <summary>
/// Calculate the world space AABB for this chunk.
/// </summary>
internal Box2 CalcWorldAABB(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
{
var (position, rotation) =
_transform.GetWorldPositionRotation(uid);
var chunkPosition = mapChunk.Indices;
var tileScale = grid.TileSize;
var chunkScale = mapChunk.ChunkSize;
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
return new Box2Rotated(
((Box2)mapChunk.CachedBounds
.Scale(tileScale))
.Translated(worldPos),
rotation, worldPos).CalcBoundingBox();
}
private void OnTileModified(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
bool shapeChanged)
{
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
mapChunk.LastTileModifiedTick = _timing.CurTick;
grid.LastTileModifiedTick = _timing.CurTick;
Dirty(grid);
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
if (!MapManager.SuppressOnTileChanged)
{
var newTileRef = new TileRef(uid, gridTile, newTile);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile);
}
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
{
RegenerateCollision(uid, grid, mapChunk);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public abstract partial class SharedMapSystem
{
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk.</param>
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
internal void SetChunkTile(EntityUid uid, MapGridComponent grid, MapChunk chunk, ushort xIndex, ushort yIndex, Tile tile)
{
if (!chunk.TrySetTile(xIndex, yIndex, tile, out var oldTile, out var shapeChanged))
return;
var tileIndices = new Vector2i(xIndex, yIndex);
OnTileModified(uid, grid, chunk, tileIndices, tile, oldTile, shapeChanged);
}
}

View File

@@ -6,21 +6,30 @@ using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using System.Linq;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
{
[UsedImplicitly]
public abstract partial class SharedMapSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] private readonly IMapManagerInternal _mapInternal = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<TransformComponent> _xformQuery;
public override void Initialize()
{
base.Initialize();
_xformQuery = GetEntityQuery<TransformComponent>();
InitializeMap();
InitializeGrid();

View File

@@ -37,7 +37,7 @@ namespace Robust.Shared.GameObjects
comp._enabled = enabled;
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
Dirty(comp);
Dirty(uid, comp);
}
public virtual void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
@@ -46,7 +46,7 @@ namespace Robust.Shared.GameObjects
return;
comp._radius = radius;
Dirty(comp);
Dirty(uid, comp);
}
}
}

View File

@@ -33,8 +33,8 @@ public abstract partial class SharedTransformSystem
EntityQuery<TransformComponent> xformQuery)
{
// Bypass some of the expensive stuff in unanchoring / anchoring.
oldGrid.RemoveFromSnapGridCell(tilePos, uid);
newGrid.AddToSnapGridCell(tilePos, uid);
_map.RemoveFromSnapGridCell(oldGridUid, oldGrid, tilePos, uid);
_map.AddToSnapGridCell(newGridUid, newGrid, tilePos, uid);
// TODO: Could do this re-parent way better.
// Unfortunately we don't want any anchoring events to go out hence... this.
xform._anchored = false;
@@ -77,7 +77,7 @@ public abstract partial class SharedTransformSystem
MapGridComponent grid,
Vector2i tileIndices)
{
if (!grid.AddToSnapGridCell(tileIndices, uid))
if (!_map.AddToSnapGridCell(gridUid, grid, tileIndices, uid))
return false;
var wasAnchored = xform._anchored;
@@ -94,7 +94,7 @@ public abstract partial class SharedTransformSystem
}
// Anchor snapping. If there is a coordinate change, it will dirty the component for us.
var pos = new EntityCoordinates(gridUid, grid.GridTileToLocal(tileIndices).Position);
var pos = new EntityCoordinates(gridUid, _map.GridTileToLocal(gridUid, grid, tileIndices).Position);
SetCoordinates(uid, xform, pos, unanchor: false);
return true;
@@ -102,14 +102,14 @@ public abstract partial class SharedTransformSystem
public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridComponent grid)
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
var tileIndices = _map.TileIndicesFor(grid.Owner, grid, xform.Coordinates);
return AnchorEntity(uid, xform, grid, tileIndices);
}
public bool AnchorEntity(EntityUid uid, TransformComponent xform)
{
return _mapManager.TryGetGrid(xform.GridUid, out var grid)
&& AnchorEntity(uid, xform, grid, grid.TileIndicesFor(xform.Coordinates));
&& AnchorEntity(uid, xform, grid, _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates));
}
public void Unanchor(EntityUid uid, TransformComponent xform, bool setPhysics = true)
@@ -128,8 +128,8 @@ public abstract partial class SharedTransformSystem
if (TryComp(xform.GridUid, out MapGridComponent? grid))
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
}
else if (xform.Initialized)
{
@@ -156,7 +156,7 @@ public abstract partial class SharedTransformSystem
/// </summary>
public bool ContainsEntity(TransformComponent xform, EntityUid entity)
{
return ContainsEntity(xform, entity, GetEntityQuery<TransformComponent>());
return ContainsEntity(xform, entity, _xformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -168,7 +168,7 @@ public abstract partial class SharedTransformSystem
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform)
{
return ContainsEntity(xform, entityTransform, GetEntityQuery<TransformComponent>());
return ContainsEntity(xform, entityTransform, _xformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -227,11 +227,9 @@ public abstract partial class SharedTransformSystem
return value;
}
var xformQuery = GetEntityQuery<TransformComponent>();
if (!component._mapIdInitialized)
{
FindMapIdAndSet(uid, component, EntityManager, xformQuery, _mapManager);
FindMapIdAndSet(uid, component, EntityManager, _xformQuery, _mapManager);
component._mapIdInitialized = true;
}
@@ -241,7 +239,7 @@ public abstract partial class SharedTransformSystem
// Note that _children is a HashSet<EntityUid>,
// so duplicate additions (which will happen) don't matter.
var parentXform = xformQuery.GetComponent(component.ParentUid);
var parentXform = _xformQuery.GetComponent(component.ParentUid);
if (parentXform.LifeStage > ComponentLifeStage.Running || LifeStage(component.ParentUid) > EntityLifeStage.MapInitialized)
{
var msg = $"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(component.ParentUid)}, new parent: {ToPrettyString(uid)}";
@@ -365,7 +363,7 @@ public abstract partial class SharedTransformSystem
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
DebugTools.Assert(gridId == null || HasComp<MapGridComponent>(gridId));
xformQuery ??= GetEntityQuery<TransformComponent>();
xformQuery ??= _xformQuery;
SetGridIdRecursive(uid, xform, gridId, xformQuery.Value);
}
@@ -418,6 +416,16 @@ public abstract partial class SharedTransformSystem
#region Local Rotation
public void SetLocalRotationNoLerp(EntityUid uid, Angle angle)
{
SetLocalRotationNoLerp(_xformQuery.GetComponent(uid), angle);
}
public virtual void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
{
xform.LocalRotation = angle;
}
public void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
{
if (!Resolve(uid, ref xform)) return;
@@ -464,7 +472,7 @@ public abstract partial class SharedTransformSystem
Unanchor(uid, xform);
// Set new values
Dirty(xform);
Dirty(uid, xform);
xform.MatricesDirty = true;
xform._localPosition = value.Position;
@@ -476,8 +484,6 @@ public abstract partial class SharedTransformSystem
// Perform parent change logic
if (value.EntityId != xform._parent)
{
var xformQuery = GetEntityQuery<TransformComponent>();
if (value.EntityId == uid)
{
QueueDel(uid);
@@ -486,7 +492,7 @@ public abstract partial class SharedTransformSystem
if (value.EntityId.IsValid())
{
if (!xformQuery.Resolve(value.EntityId, ref newParent, false))
if (!_xformQuery.Resolve(value.EntityId, ref newParent, false))
{
QueueDel(uid);
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
@@ -520,13 +526,13 @@ public abstract partial class SharedTransformSystem
}
recursiveUid = recursiveXform.ParentUid;
recursiveXform = xformQuery.GetComponent(recursiveUid);
recursiveXform = _xformQuery.GetComponent(recursiveUid);
}
}
}
if (xform._parent.IsValid())
xformQuery.Resolve(xform._parent, ref oldParent);
_xformQuery.Resolve(xform._parent, ref oldParent);
oldParent?._children.Remove(uid);
newParent?._children.Add(uid);
@@ -536,7 +542,7 @@ public abstract partial class SharedTransformSystem
if (newParent != null)
{
xform.ChangeMapId(newParent.MapID, xformQuery);
xform.ChangeMapId(newParent.MapID, _xformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
@@ -549,18 +555,18 @@ public abstract partial class SharedTransformSystem
}
else
{
xform.ChangeMapId(MapId.Nullspace, xformQuery);
xform.ChangeMapId(MapId.Nullspace, _xformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
else
SetGridId(uid, xform, null, xformQuery);
SetGridId(uid, xform, null, _xformQuery);
}
if (xform.Initialized)
{
// preserve world rotation
if (rotation == null && oldParent != null && newParent != null && !xform.NoLocalRotation)
xform._localRotation += GetWorldRotation(oldParent, xformQuery) - GetWorldRotation(newParent, xformQuery);
xform._localRotation += GetWorldRotation(oldParent) - GetWorldRotation(newParent);
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
@@ -588,7 +594,7 @@ public abstract partial class SharedTransformSystem
public void ReparentChildren(EntityUid oldUid, EntityUid uid)
{
ReparentChildren(oldUid, uid, GetEntityQuery<TransformComponent>());
ReparentChildren(oldUid, uid, _xformQuery);
}
/// <summary>
@@ -615,7 +621,7 @@ public abstract partial class SharedTransformSystem
public TransformComponent? GetParent(EntityUid uid)
{
return GetParent(uid, GetEntityQuery<TransformComponent>());
return GetParent(uid, _xformQuery);
}
public TransformComponent? GetParent(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
@@ -625,7 +631,7 @@ public abstract partial class SharedTransformSystem
public TransformComponent? GetParent(TransformComponent xform)
{
return GetParent(xform, GetEntityQuery<TransformComponent>());
return GetParent(xform, _xformQuery);
}
public TransformComponent? GetParent(TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
@@ -636,13 +642,12 @@ public abstract partial class SharedTransformSystem
public void SetParent(EntityUid uid, EntityUid parent)
{
var query = GetEntityQuery<TransformComponent>();
SetParent(uid, query.GetComponent(uid), parent, query);
SetParent(uid, _xformQuery.GetComponent(uid), parent, _xformQuery);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, TransformComponent? parentXform = null)
{
SetParent(uid, xform, parent, GetEntityQuery<TransformComponent>(), parentXform);
SetParent(uid, xform, parent, _xformQuery, parentXform);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
@@ -701,8 +706,8 @@ public abstract partial class SharedTransformSystem
// remove from any old grid lookups
if (xform.Anchored && TryComp(xform.ParentUid, out MapGridComponent? grid))
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.ParentUid, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.ParentUid, grid, tileIndices, uid);
}
// Set anchor state true during the move event unless the entity wasn't and isn't being anchored. This avoids unnecessary entity lookup changes.
@@ -719,8 +724,8 @@ public abstract partial class SharedTransformSystem
{
if (xform.ParentUid == xform.GridUid && TryComp(xform.GridUid, out MapGridComponent? newGrid))
{
var tileIndices = newGrid.TileIndicesFor(xform.Coordinates);
newGrid.AddToSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, newGrid, xform.Coordinates);
_map.AddToSnapGridCell(xform.GridUid.Value, newGrid, tileIndices, uid);
}
else
{
@@ -768,8 +773,7 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetWorldMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldMatrix(query.GetComponent(uid), query);
return GetWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
// Temporary until it's moved here
@@ -777,7 +781,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetWorldMatrix(TransformComponent component)
{
return GetWorldMatrix(component, GetEntityQuery<TransformComponent>());
return GetWorldMatrix(component, _xformQuery);
}
[Pure]
@@ -835,6 +839,12 @@ public abstract partial class SharedTransformSystem
return GetWorldPosition(component);
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
{
return GetWorldPositionRotation(_xformQuery.GetComponent(uid));
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(TransformComponent component)
{
@@ -937,7 +947,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
{
SetWorldPosition(component, worldPos, GetEntityQuery<TransformComponent>());
SetWorldPosition(component, worldPos, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -963,8 +973,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldRotation(query.GetComponent(uid), query);
return GetWorldRotation(_xformQuery.GetComponent(uid), _xformQuery);
}
// Temporary until it's moved here
@@ -972,7 +981,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(TransformComponent component)
{
return GetWorldRotation(component, GetEntityQuery<TransformComponent>());
return GetWorldRotation(component, _xformQuery);
}
[Pure]
@@ -1040,7 +1049,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot)
{
SetWorldPositionRotation(component, worldPos, worldRot, GetEntityQuery<TransformComponent>());
SetWorldPositionRotation(component, worldPos, worldRot, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1105,15 +1114,14 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetInvWorldMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetInvWorldMatrix(query.GetComponent(uid), query);
return GetInvWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetInvWorldMatrix(TransformComponent component)
{
return GetInvWorldMatrix(component, GetEntityQuery<TransformComponent>());
return GetInvWorldMatrix(component, _xformQuery);
}
[Pure]
@@ -1138,15 +1146,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrix(query.GetComponent(uid), query);
return GetWorldPositionRotationMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(TransformComponent xform)
{
return GetWorldPositionRotationMatrix(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationMatrix(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1176,7 +1183,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(TransformComponent xform)
{
return GetWorldPositionRotationInvMatrix(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationInvMatrix(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1200,15 +1207,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrixWithInv(query.GetComponent(uid), query);
return GetWorldPositionRotationMatrixWithInv(_xformQuery.GetComponent(uid), _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(TransformComponent xform)
{
return GetWorldPositionRotationMatrixWithInv(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationMatrixWithInv(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1231,9 +1237,8 @@ public abstract partial class SharedTransformSystem
#region AttachToGridOrMap
public void AttachToGridOrMap(EntityUid uid, TransformComponent? xform = null)
{
var query = GetEntityQuery<TransformComponent>();
if (query.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, query);
if (_xformQuery.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, _xformQuery);
}
public void AttachToGridOrMap(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> query)
@@ -1271,18 +1276,17 @@ public abstract partial class SharedTransformSystem
public bool TryGetMapOrGridCoordinates(EntityUid uid, [NotNullWhen(true)] out EntityCoordinates? coordinates, TransformComponent? xform = null)
{
var query = GetEntityQuery<TransformComponent>();
coordinates = null;
if (!query.Resolve(uid, ref xform))
if (!_xformQuery.Resolve(uid, ref xform))
return false;
if (!xform.ParentUid.IsValid())
return false;
EntityUid newParent;
var oldPos = GetWorldPosition(xform, query);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, query, out var gridUid, out _))
var oldPos = GetWorldPosition(xform, _xformQuery);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, _xformQuery, out var gridUid, out _))
{
newParent = gridUid;
}
@@ -1295,7 +1299,7 @@ public abstract partial class SharedTransformSystem
return false;
}
coordinates = new(newParent, GetInvWorldMatrix(newParent, query).Transform(oldPos));
coordinates = new(newParent, GetInvWorldMatrix(newParent, _xformQuery).Transform(oldPos));
return true;
}
#endregion
@@ -1332,8 +1336,8 @@ public abstract partial class SharedTransformSystem
if (xform.Anchored && _metaQuery.TryGetComponent(xform.GridUid, out var meta) && meta.EntityLifeStage <= EntityLifeStage.MapInitialized)
{
var grid = Comp<MapGridComponent>(xform.GridUid.Value);
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
xform._anchored = false;
var anchorStateChangedEvent = new AnchorStateChangedEvent(xform, true);
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
@@ -1352,7 +1356,7 @@ public abstract partial class SharedTransformSystem
{
if (LifeStage(uid) > EntityLifeStage.Initialized)
{
SetGridId(uid, component, uid, GetEntityQuery<TransformComponent>());
SetGridId(uid, component, uid, _xformQuery);
return;
}
component._gridInitialized = true;

View File

@@ -19,6 +19,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
private EntityQuery<MapGridComponent> _gridQuery;
@@ -254,7 +255,7 @@ namespace Robust.Shared.GameObjects
return GetWorldPosition(xform).Floored();
// We're on a grid, need to convert the coordinates to grid tiles.
return _mapManager.GetGrid(xform.GridUid.Value).CoordinatesToTile(xform.Coordinates);
return _map.CoordinatesToTile(xform.GridUid.Value, Comp<MapGridComponent>(xform.GridUid.Value), xform.Coordinates);
}
}

View File

@@ -1,12 +1,12 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Unicode;
using System.Timers;
using JetBrains.Annotations;
using Serilog.Events;
using TerraFX.Interop.Windows;
namespace Robust.Shared.Log
{
@@ -85,9 +85,7 @@ namespace Robust.Shared.Log
};
}
#if DEBUG
[UsedImplicitly]
#endif
public static void TryDetachFromConsoleWindow()
{
if (OperatingSystem.IsWindows())
@@ -200,14 +198,15 @@ namespace Robust.Shared.Log
internal static class WindowsConsole
{
public static bool TryEnableVirtualTerminalProcessing()
public static unsafe bool TryEnableVirtualTerminalProcessing()
{
try
{
var stdHandle = NativeMethods.GetStdHandle(-11);
NativeMethods.GetConsoleMode(stdHandle, out var mode);
NativeMethods.SetConsoleMode(stdHandle, mode | 4);
NativeMethods.GetConsoleMode(stdHandle, out mode);
var stdHandle = Windows.GetStdHandle(unchecked((uint)-11));
uint mode;
Windows.GetConsoleMode(stdHandle, &mode);
Windows.SetConsoleMode(stdHandle, mode | 4);
Windows.GetConsoleMode(stdHandle, &mode);
return (mode & 4) == 4;
}
catch (DllNotFoundException)
@@ -226,7 +225,7 @@ namespace Robust.Shared.Log
public static void TryDetachFromConsoleWindow()
{
if (NativeMethods.GetConsoleWindow() == default
if (Windows.GetConsoleWindow() == default
|| Debugger.IsAttached
|| System.Console.IsOutputRedirected
|| System.Console.IsErrorRedirected
@@ -235,27 +234,12 @@ namespace Robust.Shared.Log
return;
}
_freedConsole = NativeMethods.FreeConsole();
_freedConsole = Windows.FreeConsole();
}
internal static class NativeMethods
{
public const int CodePageUtf8 = 65001;
[DllImport("kernel32", SetLastError = true)]
internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, int mode);
[DllImport("kernel32", SetLastError = true)]
internal static extern bool GetConsoleMode(IntPtr handle, out int mode);
[DllImport("kernel32", SetLastError = true)]
internal static extern IntPtr GetStdHandle(int handle);
[DllImport("kernel32", SetLastError = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32", SetLastError = true)]
internal static extern IntPtr GetConsoleWindow();
}
}

View File

@@ -1,20 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
@@ -27,10 +20,8 @@ namespace Robust.Shared.Map.Components
[NetworkedComponent]
public sealed class MapGridComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedMapSystem MapSystem => _entManager.System<SharedMapSystem>();
// This field is used for deserialization internally in the map loader.
// If you want to remove this, you would have to restructure the map save file.
@@ -40,6 +31,9 @@ namespace Robust.Shared.Map.Components
[DataField("chunkSize")] internal ushort ChunkSize = 16;
[ViewVariables]
public int ChunkCount => Chunks.Count;
/// <summary>
/// The length of the side of a square tile in world units.
/// </summary>
@@ -70,7 +64,7 @@ namespace Robust.Shared.Map.Components
internal readonly Dictionary<Vector2i, MapChunk> Chunks = new();
[ViewVariables]
public Box2 LocalAABB { get; private set; }
public Box2 LocalAABB { get; internal set; }
/// <summary>
/// Set to enable or disable grid splitting.
@@ -79,849 +73,221 @@ namespace Robust.Shared.Map.Components
[ViewVariables(VVAccess.ReadWrite), DataField("canSplit")]
public bool CanSplit = true;
internal void RemoveChunk(Vector2i origin)
{
if (!Chunks.TryGetValue(origin, out var chunk))
return;
if (_netManager.IsServer)
ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
chunk.Fixtures.Clear();
Chunks.Remove(origin);
if (Chunks.Count == 0)
_entMan.EventBus.RaiseLocalEvent(Owner, new EmptyGridEvent { GridId = Owner }, true);
}
/// <summary>
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
/// </summary>
internal void RegenerateCollision(IReadOnlySet<MapChunk> chunks)
{
if (_entMan.HasComponent<MapComponent>(Owner))
return;
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
var removedChunks = new List<MapChunk>();
var fixtureSystem = _entMan.EntitySysManager.GetEntitySystem<FixtureSystem>();
_entMan.EntitySysManager.TryGetEntitySystem(out SharedGridFixtureSystem? system);
foreach (var mapChunk in chunks)
{
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
// generate collision rectangles for this chunk based on filled tiles.
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
mapChunk.CachedBounds = localBounds;
if (mapChunk.FilledTiles > 0)
chunkRectangles.Add(mapChunk, rectangles);
else
{
// Gone. Reduced to atoms
// Need to do this before RemoveChunk because it clears fixtures.
FixturesComponent? manager = null;
PhysicsComponent? body = null;
TransformComponent? xform = null;
foreach (var fixture in mapChunk.Fixtures)
{
fixtureSystem.DestroyFixture(Owner, fixture, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(mapChunk.Indices);
removedChunks.Add(mapChunk);
}
}
LocalAABB = new Box2();
foreach (var chunk in Chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
if (chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalAABB = LocalAABB.Union(gridBounds);
}
}
// May have been deleted from the bulk update above!
if (_entMan.Deleted(Owner))
return;
// TODO: Move this to the component when we combine.
_entMan.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>().WakeBody(Owner);
_entMan.EntitySysManager.GetEntitySystem<SharedMapSystem>().OnGridBoundsChange(Owner, this);
system?.RegenerateCollision(Owner, chunkRectangles, removedChunks);
}
/// <summary>
/// Regenerates the chunk local bounds of this chunk.
/// </summary>
internal void RegenerateCollision(MapChunk mapChunk)
{
RegenerateCollision(new HashSet<MapChunk> { mapChunk });
}
#region TileAccess
/// <inheritdoc />
public TileRef GetTileRef(MapCoordinates coords)
{
return GetTileRef(CoordinatesToTile(coords));
return MapSystem.GetTileRef(Owner, this, coords);
}
/// <inheritdoc />
public TileRef GetTileRef(EntityCoordinates coords)
{
return GetTileRef(CoordinatesToTile(coords));
return MapSystem.GetTileRef(Owner, this, coords);
}
/// <inheritdoc />
public TileRef GetTileRef(Vector2i tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(tileCoordinates);
if (!Chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(Owner, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return GetTileRef(output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
return MapSystem.GetTileRef(Owner, this, tileCoordinates);
}
/// <summary>
/// Returns the tile at the given chunk indices.
/// </summary>
/// <param name="mapChunk"></param>
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
internal TileRef GetTileRef(MapChunk mapChunk, ushort xIndex, ushort yIndex)
{
if (xIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
return new TileRef(Owner, indices, mapChunk.GetTile(xIndex, yIndex));
}
/// <inheritdoc />
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
{
foreach (var kvChunk in Chunks)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < ChunkSize; x++)
{
for (ushort y = 0; y < ChunkSize; y++)
{
var tile = chunk.GetTile(x, y);
if (ignoreEmpty && tile.IsEmpty)
continue;
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * ChunkSize;
yield return new TileRef(Owner, gridX, gridY, tile);
}
}
}
return MapSystem.GetAllTiles(Owner, this, ignoreEmpty);
}
/// <inheritdoc />
public GridTileEnumerator GetAllTilesEnumerator(bool ignoreEmpty = true)
{
return new GridTileEnumerator(Owner, Chunks.GetEnumerator(), ChunkSize, ignoreEmpty);
return MapSystem.GetAllTilesEnumerator(Owner, this, ignoreEmpty);
}
/// <inheritdoc />
public void SetTile(EntityCoordinates coords, Tile tile)
{
var localTile = CoordinatesToTile(coords);
SetTile(new Vector2i(localTile.X, localTile.Y), tile);
MapSystem.SetTile(Owner, this, coords, tile);
}
/// <inheritdoc />
public void SetTile(Vector2i gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
// Yeah it's noodly.
// RegenerateCollision(chunk);
MapSystem.SetTile(Owner, this, gridIndices, tile);
}
/// <inheritdoc />
public void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles)
{
if (tiles.Count == 0) return;
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
foreach (var chunk in chunks)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(chunks);
MapSystem.SetTiles(Owner, this, tiles);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
return GetLocalTilesIntersecting(localAABB, ignoreEmpty, predicate);
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2Rotated worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
{
yield return tile;
}
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
{
yield return tile;
}
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2 localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(new Vector2i(x, y));
if (Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
yield return tile;
}
else if (!ignoreEmpty)
{
var tile = new TileRef(Owner, x, y, new Tile());
if (predicate == null || predicate(tile))
yield return tile;
}
}
}
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Circle worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
var circleGridPos = new EntityCoordinates(Owner, WorldToLocal(worldArea.Position));
foreach (var tile in GetTilesIntersecting(aabb, ignoreEmpty, predicate))
{
var local = GridTileToLocal(tile.GridIndices);
if (!local.TryDistance(_entMan, circleGridPos, out var distance))
{
continue;
}
if (distance <= worldArea.Radius)
{
yield return tile;
}
}
}
private bool TryGetTile(Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
{
// Similar to TryGetTileRef but for the tiles intersecting iterators.
var gridChunk = GridTileToChunkIndices(indices);
if (Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(indices);
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
{
tileRef = null;
return false;
}
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(Owner, indices.X, indices.Y, Tile.Empty);
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
tileRef = null;
return false;
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
#endregion TileAccess
#region ChunkAccess
/// <summary>
/// The total number of allocated chunks in the grid.
/// </summary>
public int ChunkCount => Chunks.Count;
/// <inheritdoc />
internal MapChunk GetOrAddChunk(int xIndex, int yIndex)
{
return GetOrAddChunk(new Vector2i(xIndex, yIndex));
}
internal bool TryGetChunk(Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
{
return Chunks.TryGetValue(chunkIndices, out chunk);
return MapSystem.TryGetChunk(Owner, this, chunkIndices, out chunk);
}
/// <inheritdoc />
internal MapChunk GetOrAddChunk(Vector2i chunkIndices)
{
if (Chunks.TryGetValue(chunkIndices, out var output))
return output;
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, ChunkSize);
newChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
if (Initialized)
newChunk.TileModified += OnTileModified;
return Chunks[chunkIndices] = newChunk;
}
/// <inheritdoc />
public bool HasChunk(Vector2i chunkIndices)
{
return Chunks.ContainsKey(chunkIndices);
}
/// <inheritdoc />
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks()
{
return Chunks;
return MapSystem.GetMapChunks(Owner, this);
}
/// <inheritdoc />
internal ChunkEnumerator GetMapChunks(Box2 worldAABB)
{
var localAABB = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix
.TransformBox(worldAABB);
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
}
/// <inheritdoc />
internal ChunkEnumerator GetMapChunks(Box2Rotated worldArea)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
return new ChunkEnumerator(Chunks, localArea, ChunkSize);
}
internal ChunkEnumerator GetLocalMapChunks(Box2 localAABB)
{
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
return MapSystem.GetMapChunks(Owner, this, worldArea);
}
#endregion ChunkAccess
#region SnapGridAccess
/// <inheritdoc />
public int AnchoredEntityCount(Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return 0;
var (x, y) = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(MapCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
return MapSystem.GetAnchoredEntities(Owner, this, coords);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(EntityCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Vector2i pos)
{
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
// create an entire chunk for it.
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
var chunkTile = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
return MapSystem.GetAnchoredEntities(Owner, this, pos);
}
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
var chunkTile = chunk.GridTileToChunkTile(pos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid == null
? AnchoredEntitiesEnumerator.Empty
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
return MapSystem.GetAnchoredEntitiesEnumerator(Owner, this, pos);
}
public IEnumerable<EntityUid> GetLocalAnchoredEntities(Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(localAABB, true, null))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
return MapSystem.GetLocalAnchoredEntities(Owner, this, localAABB);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Box2 worldAABB)
{
foreach (var tile in GetTilesIntersecting(worldAABB))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
return MapSystem.GetAnchoredEntities(Owner, this, worldAABB);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Box2Rotated worldBounds)
{
foreach (var tile in GetTilesIntersecting(worldBounds))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
}
/// <inheritdoc />
public Vector2i TileIndicesFor(EntityCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
#endif
return SnapGridLocalCellFor(LocalToGrid(coords));
return MapSystem.TileIndicesFor(Owner, this, coords);
}
/// <inheritdoc />
public Vector2i TileIndicesFor(MapCoordinates worldPos)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == worldPos.MapId);
#endif
var localPos = WorldToLocal(worldPos.Position);
return SnapGridLocalCellFor(localPos);
return MapSystem.TileIndicesFor(Owner, this, worldPos);
}
private Vector2i SnapGridLocalCellFor(Vector2 localPos)
{
var x = (int)Math.Floor(localPos.X / TileSize);
var y = (int)Math.Floor(localPos.Y / TileSize);
return new Vector2i(x, y);
}
public bool IsAnchored(EntityCoordinates coords, EntityUid euid)
{
var tilePos = TileIndicesFor(coords);
var (chunk, chunkTile) = ChunkAndOffsetForTile(tilePos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid?.Contains(euid) == true;
}
/// <inheritdoc />
public bool AddToSnapGridCell(Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
return false;
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
return true;
}
/// <inheritdoc />
public bool AddToSnapGridCell(EntityCoordinates coords, EntityUid euid)
{
return AddToSnapGridCell(TileIndicesFor(coords), euid);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(EntityCoordinates coords, EntityUid euid)
{
RemoveFromSnapGridCell(TileIndicesFor(coords), euid);
}
private (MapChunk, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
{
var gridChunkIndices = GridTileToChunkIndices(pos);
var chunk = GetOrAddChunk(gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetInDir(EntityCoordinates position, Direction dir)
{
var pos = SharedMapSystem.GetDirection(TileIndicesFor(position), dir);
return GetAnchoredEntities(pos);
return MapSystem.GetInDir(Owner, this, position, dir);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetOffset(EntityCoordinates coords, Vector2i offset)
{
var pos = TileIndicesFor(coords) + offset;
return GetAnchoredEntities(pos);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetLocal(EntityCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
return MapSystem.GetLocal(Owner, this, coords);
}
/// <inheritdoc />
public EntityCoordinates DirectionToGrid(EntityCoordinates coords, Direction direction)
{
return GridTileToLocal(SharedMapSystem.GetDirection(TileIndicesFor(coords), direction));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityCoordinates coords)
{
var position = TileIndicesFor(coords);
foreach (var cell in GetAnchoredEntities(position))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, 1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, -1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(1, 0)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(-1, 0)))
yield return cell;
return MapSystem.GetCardinalNeighborCells(Owner, this, coords);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityCoordinates coords, int n)
{
var position = TileIndicesFor(coords);
for (var y = -n; y <= n; ++y)
for (var x = -n; x <= n; ++x)
{
var enumerator = GetAnchoredEntitiesEnumerator(position + new Vector2i(x, y));
while (enumerator.MoveNext(out var cell))
{
yield return cell.Value;
}
}
return MapSystem.GetCellsInSquareArea(Owner, this, coords, n);
}
#endregion
#region Transforms
/// <inheritdoc />
public Vector2 WorldToLocal(Vector2 posWorld)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
return matrix.Transform(posWorld);
return MapSystem.WorldToLocal(Owner, this, posWorld);
}
/// <inheritdoc />
public EntityCoordinates MapToGrid(MapCoordinates posWorld)
{
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
if (posWorld.MapId != mapId)
throw new ArgumentException(
$"Grid {Owner} is on map {mapId}, but coords are on map {posWorld.MapId}.",
nameof(posWorld));
if (!_mapManager.TryGetGrid(Owner, out var grid))
{
return new EntityCoordinates(_mapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
}
return new EntityCoordinates(((Component) grid).Owner, WorldToLocal(posWorld.Position));
return MapSystem.MapToGrid(Owner, posWorld);
}
/// <inheritdoc />
public Vector2 LocalToWorld(Vector2 posLocal)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).WorldMatrix;
return matrix.Transform(posLocal);
return MapSystem.LocalToWorld(Owner, this, posLocal);
}
public Vector2i WorldToTile(Vector2 posWorld)
{
var local = WorldToLocal(posWorld);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
return MapSystem.WorldToTile(Owner, this, posWorld);
}
public Vector2i LocalToTile(EntityCoordinates coordinates)
{
var position = LocalToGrid(coordinates);
return new Vector2i((int) Math.Floor(position.X / TileSize), (int) Math.Floor(position.Y / TileSize));
return MapSystem.LocalToTile(Owner, this, coordinates);
}
/// <inheritdoc />
public Vector2i CoordinatesToTile(MapCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.MapId);
#endif
var local = WorldToLocal(coords.Position);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
}
/// <inheritdoc />
public Vector2i CoordinatesToTile(EntityCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
#endif
var local = LocalToGrid(coords);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
}
/// <inheritdoc />
public Vector2i LocalToChunkIndices(EntityCoordinates gridPos)
{
var local = LocalToGrid(gridPos);
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
return new Vector2i(x, y);
}
public Vector2 LocalToGrid(EntityCoordinates position)
{
return position.EntityId == Owner
? position.Position
: WorldToLocal(position.ToMapPos(_entMan));
return MapSystem.CoordinatesToTile(Owner, this, coords);
}
public bool CollidesWithGrid(Vector2i indices)
{
var chunkIndices = GridTileToChunkIndices(indices);
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
return false;
var cTileIndices = chunk.GridTileToChunkTile(indices);
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
return MapSystem.CollidesWithGrid(Owner, this, indices);
}
/// <inheritdoc />
public Vector2i GridTileToChunkIndices(Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
return new Vector2i(x, y);
return MapSystem.GridTileToChunkIndices(Owner, this, gridTile);
}
/// <inheritdoc />
public EntityCoordinates GridTileToLocal(Vector2i gridTile)
{
return new(Owner,
new Vector2(gridTile.X * TileSize + (TileSize / 2f), gridTile.Y * TileSize + (TileSize / 2f)));
return MapSystem.GridTileToLocal(Owner, this, gridTile);
}
public Vector2 GridTileToWorldPos(Vector2i gridTile)
{
var locX = gridTile.X * TileSize + (TileSize / 2f);
var locY = gridTile.Y * TileSize + (TileSize / 2f);
var xform = _entMan.GetComponent<TransformComponent>(Owner);
return xform.WorldMatrix.Transform(new Vector2(locX, locY));
return MapSystem.GridTileToWorldPos(Owner, this, gridTile);
}
public MapCoordinates GridTileToWorld(Vector2i gridTile)
{
var parentMapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
return new(GridTileToWorldPos(gridTile), parentMapId);
return MapSystem.GridTileToWorld(Owner, this, gridTile);
}
/// <inheritdoc />
public bool TryGetTileRef(Vector2i indices, out TileRef tile)
{
var chunkIndices = GridTileToChunkIndices(indices);
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = GetTileRef(chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
return true;
return MapSystem.TryGetTileRef(Owner, this, indices, out tile);
}
/// <inheritdoc />
public bool TryGetTileRef(EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(CoordinatesToTile(coords), out tile);
}
/// <inheritdoc />
public bool TryGetTileRef(Vector2 worldPos, out TileRef tile)
{
return TryGetTileRef(WorldToTile(worldPos), out tile);
}
#endregion Transforms
/// <summary>
/// Calculate the world space AABB for this chunk.
/// </summary>
internal Box2 CalcWorldAABB(MapChunk mapChunk)
{
var (position, rotation) =
_entMan.GetComponent<TransformComponent>(Owner).GetWorldPositionRotation();
var chunkPosition = mapChunk.Indices;
var tileScale = TileSize;
var chunkScale = mapChunk.ChunkSize;
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
return new Box2Rotated(
((Box2)mapChunk.CachedBounds
.Scale(tileScale))
.Translated(worldPos),
rotation, worldPos).CalcBoundingBox();
}
internal void OnTileModified(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
bool shapeChanged)
{
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
mapChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
LastTileModifiedTick = _mapManager.GameTiming.CurTick;
_entMan.Dirty(this);
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
if (!_mapManager.SuppressOnTileChanged)
{
var newTileRef = new TileRef(Owner, gridTile, newTile);
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
}
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
{
RegenerateCollision(mapChunk);
}
return MapSystem.TryGetTileRef(Owner, this, coords, out tile);
}
}

View File

@@ -24,66 +24,67 @@ namespace Robust.Shared.Map
IoCManager.Resolve(ref entityManager, ref mapManager);
var gridId = coords.GetGridUid(entityManager);
var mapSystem = entityManager.System<SharedMapSystem>();
if (mapManager.TryGetGrid(gridId, out var mapGrid))
{
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
return mapSystem.GridTileToLocal(gridId.Value, mapGrid, mapSystem.CoordinatesToTile(gridId.Value, mapGrid, coords));
}
else
var mapCoords = coords.ToMap(entityManager);
if (mapManager.TryFindGridAt(mapCoords, out var gridUid, out mapGrid))
{
var mapCoords = coords.ToMap(entityManager);
if (mapManager.TryFindGridAt(mapCoords, out _, out mapGrid))
{
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
}
// create a box around the cursor
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
// find grids in search box
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
// find closest grid intersecting our search box.
MapGridComponent? closest = null;
var distance = float.PositiveInfinity;
var intersect = new Box2();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
foreach (var grid in gridsInArea)
{
var gridXform = xformQuery.GetComponent(grid.Owner);
// TODO: Use CollisionManager to get nearest edge.
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
if (gridDist >= distance)
continue;
distance = gridDist;
closest = grid;
intersect = gridIntersect;
}
if (closest != null) // stick to existing grid
{
// round to nearest cardinal dir
var normal = mapCoords.Position - intersect.Center;
// round coords to center of tile
var tileIndices = closest.WorldToTile(intersect.Center);
var tileCenterWorld = closest.GridTileToWorldPos(tileIndices);
// move mouse one tile out along normal
var newTilePos = tileCenterWorld + normal * closest.TileSize;
coords = new EntityCoordinates(closest.Owner, closest.WorldToLocal(newTilePos));
}
//else free place
return mapSystem.GridTileToLocal(gridUid, mapGrid, mapSystem.CoordinatesToTile(gridUid, mapGrid, coords));
}
// create a box around the cursor
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
// find grids in search box
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
// find closest grid intersecting our search box.
gridUid = EntityUid.Invalid;
MapGridComponent? closest = null;
var distance = float.PositiveInfinity;
var intersect = new Box2();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
foreach (var grid in gridsInArea)
{
var gridXform = xformQuery.GetComponent(grid.Owner);
// TODO: Use CollisionManager to get nearest edge.
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
if (gridDist >= distance)
continue;
gridUid = grid.Owner;
distance = gridDist;
closest = grid;
intersect = gridIntersect;
}
if (closest != null) // stick to existing grid
{
// round to nearest cardinal dir
var normal = mapCoords.Position - intersect.Center;
// round coords to center of tile
var tileIndices = mapSystem.WorldToTile(gridUid, closest, intersect.Center);
var tileCenterWorld = mapSystem.GridTileToWorldPos(gridUid, closest, tileIndices);
// move mouse one tile out along normal
var newTilePos = tileCenterWorld + normal * closest.TileSize;
coords = new EntityCoordinates(gridUid, mapSystem.WorldToLocal(gridUid, closest, newTilePos));
}
//else free place
return coords;
}
}

View File

@@ -191,10 +191,12 @@ namespace Robust.Shared.Map
if(!IsValid(entityManager))
return new Vector2i();
var mapSystem = entityManager.System<SharedMapSystem>();
var gridIdOpt = GetGridUid(entityManager);
if (gridIdOpt is { } gridId && gridId.IsValid())
{
return mapManager.GetGrid(gridId).GetTileRef(this).GridIndices;
var grid = mapManager.GetGrid(gridId);
return mapSystem.GetTileRef(gridId, grid, this).GridIndices;
}
var vec = ToMapPos(entityManager, transformSystem);

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Shared.Map.Events;
/// <summary>
/// Raised directed on a grid to get its bounds.
/// </summary>
/// <remarks>
/// Really this exists to get around test dependency creeping.
/// </remarks>
[ByRefEvent]
internal readonly record struct RegenerateGridBoundsEvent(EntityUid Entity, Dictionary<MapChunk, List<Box2i>> ChunkRectangles, List<MapChunk> RemovedChunks)
{
public readonly EntityUid Entity = Entity;
public readonly Dictionary<MapChunk, List<Box2i>> ChunkRectangles = ChunkRectangles;
public readonly List<MapChunk> RemovedChunks = RemovedChunks;
}

View File

@@ -22,15 +22,9 @@ namespace Robust.Shared.Map
private readonly Vector2i _gridIndices;
[ViewVariables]
private readonly Tile[,] _tiles;
[ViewVariables] internal readonly Tile[,] Tiles;
private readonly SnapGridCell[,] _snapGrid;
/// <summary>
/// Invoked when a tile is modified on this chunk.
/// </summary>
public event TileModifiedDelegate? TileModified;
/// <summary>
/// Keeps a running count of the number of filled tiles in this chunk.
/// </summary>
@@ -38,7 +32,7 @@ namespace Robust.Shared.Map
/// This will always be between 1 and <see cref="ChunkSize"/>^2.
/// </remarks>
[ViewVariables]
internal int FilledTiles { get; private set; }
internal int FilledTiles { get; set; }
/// <summary>
/// Chunk-local AABB of this chunk.
@@ -73,7 +67,7 @@ namespace Robust.Shared.Map
_gridIndices = new Vector2i(x, y);
ChunkSize = chunkSize;
_tiles = new Tile[ChunkSize, ChunkSize];
Tiles = new Tile[ChunkSize, ChunkSize];
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
}
@@ -113,58 +107,12 @@ namespace Robust.Shared.Map
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
return _tiles[xIndex, yIndex];
return Tiles[xIndex, yIndex];
}
public Tile GetTile(Vector2i indices)
{
return _tiles[indices.X, indices.Y];
}
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk.</param>
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
public void SetTile(ushort xIndex, ushort yIndex, Tile tile)
{
if (xIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
// same tile, no point to continue
if (_tiles[xIndex, yIndex] == tile)
return;
var oldTile = _tiles[xIndex, yIndex];
var oldFilledTiles = FilledTiles;
if (oldTile.IsEmpty != tile.IsEmpty)
{
if (oldTile.IsEmpty)
{
FilledTiles += 1;
}
else
{
FilledTiles -= 1;
}
}
var shapeChanged = oldFilledTiles != FilledTiles;
DebugTools.Assert(FilledTiles >= 0);
_tiles[xIndex, yIndex] = tile;
var tileIndices = new Vector2i(xIndex, yIndex);
// God I hate C# events sometimes.
DebugTools.Assert(TileModified == null || TileModified.GetInvocationList().Length <= 1);
TileModified?.Invoke(this, tileIndices, tile, oldTile, shapeChanged);
return Tiles[indices.X, indices.Y];
}
/// <summary>
@@ -266,6 +214,48 @@ namespace Robust.Shared.Map
{
public List<EntityUid>? Center;
}
/// <summary>
/// Sets the tile without any callbacks.
/// Do not call this unless you know what you are doing.
/// </summary>
internal bool TrySetTile(ushort xIndex, ushort yIndex, Tile tile, out Tile oldTile, out bool shapeChanged)
{
if (xIndex >= Tiles.Length)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= Tiles.Length)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
// same tile, no point to continue
if (Tiles[xIndex, yIndex] == tile)
{
oldTile = Tile.Empty;
shapeChanged = false;
return false;
}
oldTile = Tiles[xIndex, yIndex];
var oldFilledTiles = FilledTiles;
if (oldTile.IsEmpty != tile.IsEmpty)
{
if (oldTile.IsEmpty)
{
FilledTiles += 1;
}
else
{
FilledTiles -= 1;
}
}
shapeChanged = oldFilledTiles != FilledTiles;
DebugTools.Assert(FilledTiles >= 0);
Tiles[xIndex, yIndex] = tile;
return true;
}
}
/// <summary>
@@ -276,5 +266,5 @@ namespace Robust.Shared.Map
/// <param name="newTile">New version of the tile.</param>
/// <param name="oldTile">Old version of the tile.</param>
/// <param name="chunkShapeChanged">If changing this tile changed the shape of the chunk.</param>
internal delegate void TileModifiedDelegate(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
internal delegate void TileModifiedDelegate(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
}

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Robust.Shared.Map;
@@ -29,24 +27,20 @@ internal partial class MapManager
return;
}
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state = (worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
var state = (worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
gridTree.Tree.Query(ref state,
static (ref (Box2 worldAABB,
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
GridCallback callback,
bool approx,
EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery,
MapManager mapManager,
SharedTransformSystem xformSystem) tuple,
DynamicTree.Proxy proxy) =>
{
var data = tuple.gridTree.GetUserData(proxy);
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
{
return true;
}
@@ -77,10 +71,7 @@ internal partial class MapManager
callback(mapUid, grid, ref state);
}
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
gridTree.Tree.Query(ref state2, static (ref (
TState state,
@@ -88,15 +79,13 @@ internal partial class MapManager
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
GridCallback<TState> callback,
bool approx,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery,
MapManager mapManager,
SharedTransformSystem xformSystem) tuple,
DynamicTree.Proxy proxy) =>
{
var data = tuple.gridTree.GetUserData(proxy);
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
{
return true;
}
@@ -119,22 +108,18 @@ internal partial class MapManager
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, callback, approx, includeMap);
}
private static bool IsIntersecting(
private bool IsIntersecting(
Box2 aabb,
EntityUid gridUid,
MapGridComponent grid,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSystem)
MapGridComponent grid)
{
var xformComp = xformQuery.GetComponent(gridUid);
var (worldPos, worldRot, matrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(xformComp, xformQuery);
var (worldPos, worldRot, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridUid);
var overlap = matrix.TransformBox(grid.LocalAABB).Intersect(aabb);
var localAABB = invMatrix.TransformBox(overlap);
if (physicsQuery.HasComponent(gridUid))
if (_physicsQuery.HasComponent(gridUid))
{
var enumerator = grid.GetLocalMapChunks(localAABB);
var enumerator = _mapSystem.GetLocalMapChunks(gridUid, grid, localAABB);
var transform = new Transform(worldPos, worldRot);
@@ -186,14 +171,13 @@ internal partial class MapManager
uid = EntityUid.Invalid;
grid = null;
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state = (uid, grid, worldPos, xformQuery, xformSystem);
var state = (uid, grid, worldPos, _mapSystem, _transformSystem);
FindGridsIntersecting(mapId, aabb, ref state, static (EntityUid iUid, MapGridComponent iGrid, ref (
EntityUid uid,
MapGridComponent? grid,
Vector2 worldPos,
EntityQuery<TransformComponent> xformQuery,
SharedMapSystem mapSystem,
SharedTransformSystem xformSystem) tuple) =>
{
// Turn the worldPos into a localPos and work out the relevant chunk we need to check
@@ -201,7 +185,7 @@ internal partial class MapManager
// (though now we need some extra calcs up front).
// Doesn't use WorldBounds because it's just an AABB.
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid, tuple.xformQuery);
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid);
var localPos = matrix.Transform(tuple.worldPos);
// NOTE:
@@ -209,9 +193,10 @@ internal partial class MapManager
// you account for the fact that fixtures are shrunk slightly!
var chunkIndices = SharedMapSystem.GetChunkIndices(localPos, iGrid.ChunkSize);
if (!iGrid.HasChunk(chunkIndices)) return true;
if (!tuple.mapSystem.HasChunk(iUid, iGrid, chunkIndices))
return true;
var chunk = iGrid.GetOrAddChunk(chunkIndices);
var chunk = tuple.mapSystem.GetOrAddChunk(iUid, iGrid, chunkIndices);
var chunkRelative = SharedMapSystem.GetChunkRelative(localPos, iGrid.ChunkSize);
var chunkTile = chunk.GetTile(chunkRelative);
@@ -241,8 +226,7 @@ internal partial class MapManager
/// </summary>
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, out EntityUid uid, [NotNullWhen(true)] out MapGridComponent? grid)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
return TryFindGridAt(mapId, worldPos, xformQuery, out uid, out grid);
return TryFindGridAt(mapId, worldPos, _xformQuery, out uid, out grid);
}
/// <summary>

View File

@@ -4,6 +4,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -20,9 +21,17 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
private ISawmill _sawmill = default!;
private SharedMapSystem _mapSystem = default!;
private SharedTransformSystem _transformSystem = default!;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
/// <inheritdoc />
public void Initialize()
{
_physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
_xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
_sawmill = Logger.GetSawmill("map");
#if DEBUG
@@ -36,6 +45,9 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
/// <inheritdoc />
public void Startup()
{
_transformSystem = EntityManager.System<SharedTransformSystem>();
_mapSystem = EntityManager.System<SharedMapSystem>();
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
_dbgGuardRunning = true;
@@ -52,9 +64,12 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
#endif
_sawmill.Debug("Stopping...");
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
// TODO: AllEntityQuery instead???
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
while (query.MoveNext(out var uid, out _))
{
EntityManager.DeleteEntity(mapComp.Owner);
EntityManager.DeleteEntity(uid);
}
}
@@ -65,9 +80,11 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
// Don't just call Shutdown / Startup because we don't want to touch the subscriptions on gridtrees
// Restart can be called any time during a game, whereas shutdown / startup are typically called upon connection.
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
while (query.MoveNext(out var uid, out _))
{
EntityManager.DeleteEntity(mapComp.Owner);
EntityManager.DeleteEntity(uid);
}
}

View File

@@ -845,7 +845,13 @@ namespace Robust.Shared.Network
return true;
}
var channel = _channels[msg.SenderConnection];
if (!_channels.TryGetValue(msg.SenderConnection, out var channel))
{
_logger.Warning($"{msg.SenderConnection.RemoteEndPoint}: Got unexpected data packet before handshake completion.");
msg.SenderConnection.Disconnect("Unexpected packet before handshake completion");
return true;
}
var encryption = IsServer ? channel.Encryption : _clientEncryption;

View File

@@ -179,7 +179,7 @@ public abstract class Joint : IEquatable<Joint>
[DataField("breakpoint")]
private float _breakpoint = float.MaxValue;
private double _breakpointSquared = Double.MaxValue;
private double _breakpointSquared = double.MaxValue;
// TODO: Later nerd
// serializer.DataField(this, x => x.BodyA, "bodyA", EntityUid.Invalid);
@@ -191,10 +191,10 @@ public abstract class Joint : IEquatable<Joint>
IoCManager.Resolve(ref entMan);
if (entMan.TryGetComponent(BodyAUid, out PhysicsComponent? physics))
entMan.Dirty(physics);
entMan.Dirty(BodyAUid, physics);
if (entMan.TryGetComponent(BodyBUid, out physics))
entMan.Dirty(physics);
entMan.Dirty(BodyBUid, physics);
}
protected Joint() {}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Physics.Components;
namespace Robust.Shared.Physics.Events
{
/// <summary>
/// These events are broadcast (not directed) whenever an entity's ability to collide changes.
/// </summary>
[ByRefEvent]
public readonly struct CollisionLayerChangeEvent
{
public readonly PhysicsComponent Body;
public CollisionLayerChangeEvent(PhysicsComponent body)
{
Body = body;
}
}
}

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Log;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Events;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -355,6 +356,9 @@ namespace Robust.Shared.Physics.Systems
if (resetMass)
_physics.ResetMassData(uid, manager, body);
// Save the old layer to see if an event should be raised later.
var oldLayer = body.CollisionLayer;
// Normally this method is called when fixtures need to be dirtied anyway so no point in returning early I think
body.CollisionMask = mask;
body.CollisionLayer = layer;
@@ -363,6 +367,12 @@ namespace Robust.Shared.Physics.Systems
if (manager.FixtureCount == 0)
_physics.SetCanCollide(uid, false, manager: manager, body: body);
if (oldLayer != layer)
{
var ev = new CollisionLayerChangeEvent(body);
RaiseLocalEvent(ref ev);
}
if (dirty)
Dirty(manager);
}

View File

@@ -20,15 +20,19 @@ namespace Robust.Shared.Physics.Systems
{
public abstract class SharedBroadphaseSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedGridTraversalSystem _traversal = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _logger = default!;
private EntityQuery<BroadphaseComponent> _broadphaseQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
/*
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
@@ -53,9 +57,12 @@ namespace Robust.Shared.Physics.Systems
{
base.Initialize();
_logger = Logger.GetSawmill("physics");
UpdatesOutsidePrediction = true;
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
UpdatesOutsidePrediction = true;
UpdatesAfter.Add(typeof(SharedTransformSystem));
_cfg.OnValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand, true);
@@ -79,27 +86,24 @@ namespace Robust.Shared.Physics.Systems
PhysicsMapComponent component,
MapId mapId,
HashSet<EntityUid> movedGrids,
Dictionary<FixtureProxy, Box2> gridMoveBuffer,
EntityQuery<BroadphaseComponent> broadQuery,
EntityQuery<TransformComponent> xformQuery)
Dictionary<FixtureProxy, Box2> gridMoveBuffer)
{
// None moved this tick
if (movedGrids.Count == 0) return;
var mapBroadphase = broadQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
var mapBroadphase = _broadphaseQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
// This is so that if we're on a broadphase that's moving (e.g. a grid) we need to make sure anything
// we move over is getting checked for collisions, and putting it on the movebuffer is the easiest way to do so.
var moveBuffer = component.MoveBuffer;
var gridQuery = GetEntityQuery<MapGridComponent>();
foreach (var gridUid in movedGrids)
{
var grid = gridQuery.GetComponent(gridUid);
var xform = xformQuery.GetComponent(gridUid);
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
var worldAABB = _transform.GetWorldMatrix(xform, xformQuery).TransformBox(grid.LocalAABB);
var worldAABB = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB);
var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand);
var state = (moveBuffer, gridMoveBuffer);
@@ -111,7 +115,7 @@ namespace Robust.Shared.Physics.Systems
{
moveBuffer[proxy] = worldAABB;
// If something is in our AABB then try grid traversal for it
_traversal.CheckTraverse(proxy.Entity, xformQuery.GetComponent(proxy.Entity));
_traversal.CheckTraverse(proxy.Entity, _xformQuery.GetComponent(proxy.Entity));
}
}
@@ -157,12 +161,8 @@ namespace Robust.Shared.Physics.Systems
var movedGrids = Comp<MovedGridsComponent>(mapUid).MovedGrids;
var gridMoveBuffer = new Dictionary<FixtureProxy, Box2>();
var broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Find any entities being driven over that might need to be considered
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer, broadphaseQuery, xformQuery);
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer);
// There is some mariana trench levels of bullshit going on.
// We essentially need to re-create Box2D's FindNewContacts but in a way that allows us to check every
@@ -174,7 +174,7 @@ namespace Robust.Shared.Physics.Systems
// to cache a bunch of stuff to make up for it.
// Handle grids first as they're not stored on map broadphase at all.
HandleGridCollisions(mapId, movedGrids, physicsQuery, xformQuery);
HandleGridCollisions(mapId, movedGrids);
// EZ
if (moveBuffer.Count == 0)
@@ -212,7 +212,7 @@ namespace Robust.Shared.Physics.Systems
var proxyBody = proxy.Body;
DebugTools.Assert(!proxyBody.Deleted);
var state = (this, proxy, worldAABB, buffer, xformQuery, broadphaseQuery);
var state = (this, proxy, worldAABB, buffer);
// Get every broadphase we may be intersecting.
_mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), ref state,
@@ -220,18 +220,16 @@ namespace Robust.Shared.Physics.Systems
SharedBroadphaseSystem system,
FixtureProxy proxy,
Box2 worldAABB,
List<FixtureProxy> pairBuffer,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<BroadphaseComponent> broadphaseQuery) tuple) =>
List<FixtureProxy> pairBuffer) tuple) =>
{
ref var buffer = ref tuple.pairBuffer;
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer, tuple.xformQuery, tuple.broadphaseQuery);
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
return true;
});
// Struct ref moment, I have no idea what's fastest.
buffer = state.buffer;
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer, xformQuery, broadphaseQuery);
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer);
}
});
@@ -272,27 +270,23 @@ namespace Robust.Shared.Physics.Systems
private void HandleGridCollisions(
MapId mapId,
HashSet<EntityUid> movedGrids,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery)
HashSet<EntityUid> movedGrids)
{
var gridQuery = GetEntityQuery<MapGridComponent>();
foreach (var gridUid in movedGrids)
{
var grid = gridQuery.GetComponent(gridUid);
var xform = xformQuery.GetComponent(gridUid);
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform, xformQuery);
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform);
var aabb = new Box2Rotated(grid.LocalAABB, worldRot).CalcBoundingBox().Translated(worldPos);
// TODO: Need to handle grids colliding with non-grid entities with the same layer
// (nothing in SS14 does this yet).
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: xformQuery);
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _physicsSystem, _transform, physicsQuery, xformQuery);
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: _xformQuery);
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _map, _physicsSystem, _transform, _physicsQuery, _xformQuery);
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
static (EntityUid uid, MapGridComponent component,
@@ -301,6 +295,7 @@ namespace Robust.Shared.Physics.Systems
Transform transform,
Matrix3 worldMatrix,
Matrix3 invWorldMatrix,
SharedMapSystem _map,
SharedPhysicsSystem _physicsSystem,
SharedTransformSystem xformSystem,
EntityQuery<PhysicsComponent> physicsQuery,
@@ -320,7 +315,7 @@ namespace Robust.Shared.Physics.Systems
var aabb1 = tuple.grid.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds));
// TODO: AddPair has a nasty check in there that's O(n) but that's also a general physics problem.
var ourChunks = tuple.grid.GetLocalMapChunks(aabb1);
var ourChunks = tuple._map.GetLocalMapChunks(tuple.gridUid, tuple.grid, aabb1);
var physicsA = tuple.physicsQuery.GetComponent(tuple.gridUid);
var physicsB = tuple.physicsQuery.GetComponent(uid);
@@ -331,7 +326,7 @@ namespace Robust.Shared.Physics.Systems
tuple.worldMatrix.TransformBox(
ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.ChunkSize));
var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld);
var collidingChunks = component.GetLocalMapChunks(ourChunkOtherRef);
var collidingChunks = tuple._map.GetLocalMapChunks(uid, component, ourChunkOtherRef);
while (collidingChunks.MoveNext(out var collidingChunk))
{
@@ -372,14 +367,12 @@ namespace Robust.Shared.Physics.Systems
FixtureProxy proxy,
Box2 worldAABB,
EntityUid broadphase,
List<FixtureProxy> pairBuffer,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<BroadphaseComponent> broadphaseQuery)
List<FixtureProxy> pairBuffer)
{
DebugTools.Assert(proxy.Body.CanCollide);
// Broadphase can't intersect with entities on itself so skip.
if (proxy.Entity == broadphase || !xformQuery.TryGetComponent(proxy.Entity, out var xform))
if (proxy.Entity == broadphase || !_xformQuery.TryGetComponent(proxy.Entity, out var xform))
{
return;
}
@@ -389,7 +382,7 @@ namespace Robust.Shared.Physics.Systems
DebugTools.AssertNotNull(xform.Broadphase);
if (!_lookup.TryGetCurrentBroadphase(xform, out var proxyBroad))
{
_logger.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
Log.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
DebugTools.Assert(false);
return;
}
@@ -401,10 +394,10 @@ namespace Robust.Shared.Physics.Systems
}
else
{
aabb = _transform.GetInvWorldMatrix(broadphase, xformQuery).TransformBox(worldAABB);
aabb = _transform.GetInvWorldMatrix(broadphase).TransformBox(worldAABB);
}
var broadphaseComp = broadphaseQuery.GetComponent(broadphase);
var broadphaseComp = _broadphaseQuery.GetComponent(broadphase);
var state = (pairBuffer, proxy);
QueryBroadphase(broadphaseComp.DynamicTree, state, aabb);
@@ -512,7 +505,7 @@ namespace Robust.Shared.Physics.Systems
}
// Won't worry about accurate bounds checks as it's probably slower in most use cases.
var chunkEnumerator = mapGrid.GetMapChunks(aabb);
var chunkEnumerator = _map.GetMapChunks(bUid, mapGrid, aabb);
if (chunkEnumerator.MoveNext(out _))
{

View File

@@ -79,7 +79,7 @@ public abstract partial class SharedJointSystem
if (relayTarget.Relayed.Remove(uid))
{
// TODO: Comp cleanup.
Dirty(relayTarget);
Dirty(component.Relay.Value, relayTarget);
}
}
@@ -91,10 +91,10 @@ public abstract partial class SharedJointSystem
if (relayTarget.Relayed.Add(uid))
{
_physics.WakeBody(relay.Value);
Dirty(relayTarget);
Dirty(relay.Value, relayTarget);
}
}
Dirty(component);
Dirty(uid, component);
}
}

View File

@@ -11,11 +11,6 @@ namespace Robust.Shared.Replays;
public interface IReplayRecordingManager
{
/// <summary>
/// Initializes the replay manager.
/// </summary>
void Initialize();
/// <summary>
/// Whether or not a replay recording can currently be started.
/// </summary>
@@ -51,6 +46,14 @@ public interface IReplayRecordingManager
/// </summary>
bool IsRecording { get; }
/// <summary>
/// Gets the <c>state</c> object passed into <see cref="TryStartRecording"/> for the current recording.
/// </summary>
/// <remarks>
/// Returns <see langword="null"/> if there is no active replay recording.
/// </remarks>
public object? ActiveRecordingState { get; }
/// <summary>
/// Processes pending write tasks and saves the replay data for the current tick. This should be called even if a
/// replay is not currently being recorded.
@@ -62,20 +65,20 @@ public interface IReplayRecordingManager
/// to the recording's metadata file, as well as to provide serializable messages that get replayed when the replay
/// is initially loaded. E.g., this should contain networked events that would get sent to a newly connected client.
/// </summary>
event Action<MappingDataNode, List<object>>? RecordingStarted;
event Action<MappingDataNode, List<object>> RecordingStarted;
/// <summary>
/// This gets invoked whenever a replay recording is stopping. Subscribers can use this to add extra yaml data to the
/// recording's metadata file.
/// </summary>
event Action<MappingDataNode>? RecordingStopped;
event Action<MappingDataNode> RecordingStopped;
/// <summary>
/// This gets invoked after a replay recording has finished and provides information about where the replay data
/// was saved. Note that this only means that all write tasks have started, however some of the file tasks may not
/// have finished yet. See <see cref="WaitWriteTasks"/>.
/// </summary>
event Action<IWritableDirProvider, ResPath>? RecordingFinished;
event Action<ReplayRecordingFinished> RecordingFinished;
/// <summary>
/// Tries to starts a replay recording.
@@ -93,12 +96,16 @@ public interface IReplayRecordingManager
/// <param name="duration">
/// Optional time limit for the recording.
/// </param>
/// <param name="state">
/// An arbitrary object that is available in <see cref="ActiveRecordingState"/> and <see cref="RecordingFinished"/>.
/// </param>
/// <returns>Returns true if the recording was successfully started.</returns>
bool TryStartRecording(
IWritableDirProvider directory,
string? name = null,
bool overwrite = false,
TimeSpan? duration = null);
TimeSpan? duration = null,
object? state = null);
/// <summary>
/// Stops an ongoing replay recording.
@@ -106,10 +113,9 @@ public interface IReplayRecordingManager
void StopRecording();
/// <summary>
/// Returns information about the currently ongoing replay recording, including the currently elapsed time and the
/// compressed replay size.
/// Returns information about the currently ongoing replay recording.
/// </summary>
(float Minutes, int Ticks, float Size, float UncompressedSize) GetReplayStats();
ReplayRecordingStats GetReplayStats();
/// <summary>
/// Returns a task that will wait for all the current writing tasks to finish.
@@ -119,3 +125,36 @@ public interface IReplayRecordingManager
/// </exception>
Task WaitWriteTasks();
}
/// <summary>
/// Event data for <see cref="IReplayRecordingManager.RecordingFinished"/>.
/// </summary>
/// <param name="Directory">The writable dir provider in which the replay is being recorded.</param>
/// <param name="Path">The path to the replay in <paramref name="Directory"/>.</param>
/// <param name="State">The state object passed to <see cref="IReplayRecordingManager.TryStartRecording"/>.</param>
public record ReplayRecordingFinished(IWritableDirProvider Directory, ResPath Path, object? State);
/// <summary>
/// Statistics for an active replay recording.
/// </summary>
/// <param name="Time">The simulation time the replay has been recording for.</param>
/// <param name="Ticks">The amount of simulation ticks the replay has recorded.</param>
/// <param name="Size">The total compressed size of the replay data blobs.</param>
/// <param name="UncompressedSize">The total uncompressed size of the replay data blobs.</param>
public record struct ReplayRecordingStats(TimeSpan Time, uint Ticks, long Size, long UncompressedSize);
/// <summary>
/// Engine-internal functions for <see cref="IReplayRecordingManager"/>.
/// </summary>
internal interface IReplayRecordingManagerInternal : IReplayRecordingManager
{
/// <summary>
/// Initializes the replay manager.
/// </summary>
void Initialize();
/// <summary>
/// Shut down any active replay recording, at engine shutdown.
/// </summary>
void Shutdown();
}

View File

@@ -99,11 +99,15 @@ internal sealed class ReplayStatsCommand : LocalizedCommands
{
if (_replay.IsRecording)
{
var (time, tick, size, _) = _replay.GetReplayStats();
var stats = _replay.GetReplayStats();
var sizeMb = stats.Size / (1024f * 1024f);
var minutes = stats.Time.TotalMinutes;
shell.WriteLine(Loc.GetString("cmd-replay-recording-stats-result",
("time", time.ToString("F1")),
("ticks", tick), ("size", size.ToString("F1")),
("rate", (size/time).ToString("F2"))));
("time", minutes.ToString("F1")),
("ticks", stats.Ticks),
("size", sizeMb.ToString("F1")),
("rate", (sizeMb / minutes).ToString("F2"))));
}
else
shell.WriteLine(Loc.GetString("cmd-replay-recording-stop-not-recording"));

View File

@@ -21,13 +21,14 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Channels;
using System.Threading.Tasks;
using Robust.Shared.Asynchronous;
using Robust.Shared.Network;
using YamlDotNet.RepresentationModel;
using static Robust.Shared.Replays.ReplayConstants;
namespace Robust.Shared.Replays;
internal abstract partial class SharedReplayRecordingManager : IReplayRecordingManager
internal abstract partial class SharedReplayRecordingManager : IReplayRecordingManagerInternal
{
// date format for default replay names. Like the sortable template, but without colons.
public const string DefaultReplayNameFormat = "yyyy-MM-dd_HH-mm-ss";
@@ -38,10 +39,11 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
public event Action<MappingDataNode, List<object>>? RecordingStarted;
public event Action<MappingDataNode>? RecordingStopped;
public event Action<IWritableDirProvider, ResPath>? RecordingFinished;
public event Action<ReplayRecordingFinished>? RecordingFinished;
private ISawmill _sawmill = default!;
private List<object> _queuedMessages = new();
@@ -53,9 +55,9 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
private bool _enabled;
public bool IsRecording => _recState != null;
public object? ActiveRecordingState => _recState?.State;
private RecordingState? _recState;
/// <inheritdoc/>
public virtual void Initialize()
{
_sawmill = _logManager.GetSawmill("replay");
@@ -66,6 +68,18 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
NetConf.OnValueChanged(CVars.NetPVSCompressLevel, OnCompressionChanged);
}
public void Shutdown()
{
if (IsRecording)
{
StopRecording();
DebugTools.Assert(!IsRecording);
}
_taskManager.BlockWaitOnTask(WaitWriteTasks());
}
public virtual bool CanStartRecording()
{
return !IsRecording && _enabled;
@@ -137,7 +151,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
IWritableDirProvider directory,
string? name = null,
bool overwrite = false,
TimeSpan? duration = null)
TimeSpan? duration = null,
object? state = null)
{
if (!CanStartRecording())
return false;
@@ -152,7 +167,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
filePath = filePath.WithName(filePath.Filename + ".zip");
var basePath = new ResPath(NetConf.GetCVar(CVars.ReplayDirectory)).ToRootedPath();
filePath = basePath / filePath.ToRelativePath();
filePath = basePath / filePath;
// Make sure to create parent directory.
directory.CreateDir(filePath.Directory);
@@ -203,7 +218,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
commandQueue.Writer,
writeTask,
directory,
filePath
filePath,
state
);
try
@@ -348,8 +364,10 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
var document = new YamlDocument(yamlMetadata.ToYaml());
WriteYaml(recState, ReplayZipFolder / FileMetaFinal, document);
UpdateWriteTasks();
RecordingFinished?.Invoke(recState.DestDir, recState.DestPath);
Reset();
var finishedData = new ReplayRecordingFinished(recState.DestDir, recState.DestPath, recState.State);
RecordingFinished?.Invoke(finishedData);
}
private void WriteContentBundleInfo(RecordingState recState)
@@ -403,17 +421,17 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
return info;
}
public (float Minutes, int Ticks, float Size, float UncompressedSize) GetReplayStats()
public ReplayRecordingStats GetReplayStats()
{
if (_recState == null)
throw new InvalidOperationException("Not recording replay!");
var time = (Timing.CurTime - _recState.StartTime).TotalMinutes;
var time = Timing.CurTime - _recState.StartTime;
var tick = Timing.CurTick.Value - _recState.StartTick.Value;
var size = _recState.CompressedSize / (1024f * 1024f);
var altSize = _recState.UncompressedSize / (1024f * 1024f);
var size = _recState.CompressedSize;
var altSize = _recState.UncompressedSize;
return ((float)time, (int)tick, size, altSize);
return new ReplayRecordingStats(time, tick, size, altSize);
}
/// <summary>
@@ -428,6 +446,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
public readonly Task WriteTask;
public readonly IWritableDirProvider DestDir;
public readonly ResPath DestPath;
public readonly object? State;
// Tick and time when the recording was started.
public readonly GameTick StartTick;
@@ -437,8 +456,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
public readonly TimeSpan? EndTime;
public int Index;
public int CompressedSize;
public int UncompressedSize;
public long CompressedSize;
public long UncompressedSize;
public RecordingState(
ZipArchive zip,
@@ -450,11 +469,13 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
ChannelWriter<Action> writeCommandChannel,
Task writeTask,
IWritableDirProvider destDir,
ResPath destPath)
ResPath destPath,
object? state)
{
WriteTask = writeTask;
DestDir = destDir;
DestPath = destPath;
State = state;
Zip = zip;
Buffer = buffer;
CompressionContext = compressionContext;

View File

@@ -20,6 +20,7 @@
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />

View File

@@ -2,13 +2,13 @@ using System;
using System.Threading;
using Robust.Shared.Log;
using Robust.Shared.Exceptions;
using Robust.Shared.Maths;
using Prometheus;
using Robust.Shared.Configuration;
using Robust.Shared.Profiling;
namespace Robust.Shared.Timing
{
public interface IGameLoop
internal interface IGameLoop
{
event EventHandler<FrameEventArgs> Input;
event EventHandler<FrameEventArgs> Tick;
@@ -46,8 +46,10 @@ namespace Robust.Shared.Timing
/// <summary>
/// Manages the main game loop for a GameContainer.
/// </summary>
public sealed class GameLoop : IGameLoop
internal sealed class GameLoop : IGameLoop
{
private static readonly TimeSpan DelayTime = TimeSpan.FromMilliseconds(1);
public const string ProfTextStartFrame = "Start Frame";
private static readonly Histogram _frameTimeHistogram = Metrics.CreateHistogram(
@@ -100,18 +102,27 @@ namespace Robust.Shared.Timing
private readonly ProfManager _prof;
private readonly ISawmill _sawmill;
private readonly PrecisionSleep _precisionSleep;
#if EXCEPTION_TOLERANCE
private int _tickExceptions;
private const int MaxSoftLockExceptions = 10;
#endif
public GameLoop(IGameTiming timing, IRuntimeLog runtimeLog, ProfManager prof, ISawmill sawmill)
public GameLoop(
IGameTiming timing,
IRuntimeLog runtimeLog,
ProfManager prof,
ISawmill sawmill,
GameLoopOptions options)
{
_timing = timing;
_runtimeLog = runtimeLog;
_prof = prof;
_sawmill = sawmill;
_precisionSleep = options.Precise ? PrecisionSleep.Create() : new PrecisionSleepUniversal();
}
/// <summary>
@@ -208,6 +219,8 @@ namespace Robust.Shared.Timing
using var tickGroup = _prof.Group("Tick");
_prof.WriteValue("Tick", ProfData.Int64(_timing.CurTick.Value));
// System.Console.WriteLine($"Tick started at: {_timing.RealTime - _timing.LastTick}");
if (EnableMetrics)
{
using (_frameTimeHistogram.NewTimer())
@@ -304,12 +317,36 @@ namespace Robust.Shared.Timing
// Set sleep to 1 if you want to be nice and give the rest of the timeslice up to the os scheduler.
// Set sleep to 0 if you want to use 100% cpu, but still cooperate with the scheduler.
// do not call sleep if you want to be 'that thread' and hog 100% cpu.
if (SleepMode != SleepMode.None)
Thread.Sleep((int)SleepMode);
switch (SleepMode)
{
case SleepMode.Yield:
Thread.Sleep(0);
break;
case SleepMode.Delay:
// We try to sleep exactly until the next tick.
// But no longer than 1ms so input can keep processing.
var timeToSleep = (_timing.LastTick + _timing.TickPeriod) - _timing.RealTime;
if (timeToSleep > DelayTime)
timeToSleep = DelayTime;
if (timeToSleep.Ticks > 0)
_precisionSleep.Sleep(timeToSleep);
break;
}
}
}
}
internal sealed record GameLoopOptions(bool Precise)
{
public static GameLoopOptions FromCVars(IConfigurationManager cfg)
{
return new GameLoopOptions(cfg.GetCVar(CVars.SysPreciseSleep));
}
}
/// <summary>
/// Methods the GameLoop can use to limit the Update rate.
/// </summary>

View File

@@ -78,7 +78,8 @@ namespace Robust.Shared.Timing
GameTick CurTick { get; set; }
/// <summary>
/// Timespan for the last tick.
/// Time, relative to <see cref="RealTime"/>, the last tick started at.
/// If we're currently in simulation, that's THIS tick.
/// </summary>
TimeSpan LastTick { get; set; }

View File

@@ -0,0 +1,154 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using TerraFX.Interop.Windows;
namespace Robust.Shared.Timing;
/// <summary>
/// Helper for more precise sleeping functionality than <see cref="Thread.Sleep(int)"/>.
/// </summary>
internal abstract class PrecisionSleep : IDisposable
{
/// <summary>
/// Sleep for the specified amount of time.
/// </summary>
public abstract void Sleep(TimeSpan time);
/// <summary>
/// Create the most optimal optimization for the current platform.
/// </summary>
public static PrecisionSleep Create()
{
// Check Windows 10 1803
if (OperatingSystem.IsWindows() && Environment.OSVersion.Version.Build >= 17134)
return new PrecisionSleepWindowsHighResolution();
if (OperatingSystem.IsLinux())
return new PrecisionSleepLinuxNanosleep();
return new PrecisionSleepUniversal();
}
public virtual void Dispose()
{
}
}
/// <summary>
/// Universal cross-platform implementation of <see cref="PrecisionSleep"/>. Not very precise!
/// </summary>
internal sealed class PrecisionSleepUniversal : PrecisionSleep
{
public override void Sleep(TimeSpan time)
{
Thread.Sleep(time);
}
}
/// <summary>
/// High-precision implementation of <see cref="PrecisionSleep"/> that is available since Windows 10 1803.
/// </summary>
internal sealed unsafe class PrecisionSleepWindowsHighResolution : PrecisionSleep
{
private HANDLE _timerHandle;
public PrecisionSleepWindowsHighResolution()
{
// CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is only supported since Windows 10 1803
_timerHandle = Windows.CreateWaitableTimerExW(
null,
null,
CREATE.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
Windows.TIMER_ALL_ACCESS);
if (_timerHandle == HANDLE.NULL)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
public override void Sleep(TimeSpan time)
{
LARGE_INTEGER due;
// negative = relative time.
due.QuadPart = -time.Ticks;
var success = Windows.SetWaitableTimer(
_timerHandle,
&due,
0,
null,
null,
BOOL.FALSE
);
if (!success)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
var waitResult = Windows.WaitForSingleObject(_timerHandle, Windows.INFINITE);
if (waitResult == WAIT.WAIT_FAILED)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
GC.KeepAlive(this);
}
private void DisposeCore()
{
Windows.CloseHandle(_timerHandle);
_timerHandle = default;
}
public override void Dispose()
{
DisposeCore();
GC.SuppressFinalize(this);
}
~PrecisionSleepWindowsHighResolution()
{
DisposeCore();
}
}
/// <summary>
/// High-precision implementation of <see cref="PrecisionSleep"/> that is available on Linux.
/// </summary>
internal sealed unsafe class PrecisionSleepLinuxNanosleep : PrecisionSleep
{
public override void Sleep(TimeSpan time)
{
timespec timeSpec;
timeSpec.tv_sec = Math.DivRem(time.Ticks, TimeSpan.TicksPerSecond, out var ticksRem);
timeSpec.tv_nsec = ticksRem * TimeSpan.NanosecondsPerTick;
while (true)
{
timespec rem;
var result = nanosleep(&timeSpec, &rem);
if (result == 0)
return;
var error = Marshal.GetLastSystemError();
if (error != 4) // EINTR
throw new Exception($"nanosleep failed: {error}");
timeSpec = rem;
}
}
[DllImport("libc.so.6", SetLastError=true)]
private static extern int nanosleep(timespec* req, timespec* rem);
private struct timespec
{
public long tv_sec;
public long tv_nsec;
}
private struct timeval
{
public long tv_sec;
public long tv_usec;
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using TerraFX.Interop.Windows;
namespace Robust.Shared.Utility;
internal static class FileHelper
{
/// <summary>
/// Try to open a file for reading. If the file does not exist, the operation fails without exception.
/// </summary>
/// <remarks>
/// This API is not atomic and can thus be vulnerable to TOCTOU attacks. Don't use it if that's relevant.
/// </remarks>
/// <param name="path">The path to try to open.</param>
/// <param name="stream">The resulting file stream.</param>
/// <returns>True if the file existed and was opened.</returns>
public static bool TryOpenFileRead(string path, [NotNullWhen(true)] out FileStream? stream)
{
// On Windows, the separate File.Exists() call alone adds a ton of weight.
// The alternative however (opening the file and catching the error) is extremely slow because of .NET exceptions.
// So we manually call the windows API and make the file handle from that. Problem solved!
if (OperatingSystem.IsWindows())
return TryGetFileWindows(path, out stream);
if (!File.Exists(path))
{
stream = null;
return false;
}
stream = File.OpenRead(path);
return true;
}
private static unsafe bool TryGetFileWindows(string path, [NotNullWhen(true)] out FileStream? stream)
{
HANDLE file;
fixed (char* pPath = path)
{
file = Windows.CreateFileW(
(ushort*)pPath,
Windows.GENERIC_READ,
FILE.FILE_SHARE_READ,
null,
OPEN.OPEN_EXISTING,
FILE.FILE_ATTRIBUTE_NORMAL,
HANDLE.NULL);
}
if (file == HANDLE.INVALID_VALUE)
{
var lastError = Marshal.GetLastWin32Error();
if (lastError is ERROR.ERROR_FILE_NOT_FOUND or ERROR.ERROR_PATH_NOT_FOUND)
{
stream = null;
return false;
}
Marshal.ThrowExceptionForHR(Windows.HRESULT_FROM_WIN32(lastError));
}
var sf = new SafeFileHandle(file, ownsHandle: true);
stream = new FileStream(sf, FileAccess.Read);
return true;
}
}

View File

@@ -1,7 +1,8 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.Windows;
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
// ReSharper disable CommentTypo
@@ -49,34 +50,15 @@ namespace Robust.Shared.Utility
PROCESS_MEMORY_COUNTERS_EX counters;
if (GetProcessMemoryInfo(process.Handle, &counters, sizeof(PROCESS_MEMORY_COUNTERS_EX)) == 0)
if (Windows.GetProcessMemoryInfo(
(HANDLE)process.Handle,
(PROCESS_MEMORY_COUNTERS*)(&counters),
(uint)sizeof(PROCESS_MEMORY_COUNTERS_EX)) == 0)
return 0;
var count = counters.PrivateUsage;
return count;
return (long)count;
}
private struct PROCESS_MEMORY_COUNTERS_EX
{
public int cb;
public int PageFaultCount;
public nint PeakWorkingSetSize;
public nint WorkingSetSize;
public nint QuotaPeakPagedPoolUsage;
public nint QuotaPagedPoolUsage;
public nint QuotaPeakNonPagedPoolUsage;
public nint QuotaNonPagedPoolUsage;
public nint PagefileUsage;
public nint PeakPagefileUsage;
public nint PrivateUsage;
}
// ReSharper disable once StringLiteralTypo
[DllImport("psapi.dll", SetLastError = true)]
private static extern int GetProcessMemoryInfo(
IntPtr Process,
PROCESS_MEMORY_COUNTERS_EX* ppsmemCounters,
int cb);
}
}

View File

@@ -41,6 +41,7 @@ namespace Robust.UnitTesting.Client.GameObjects.Components
var (sim, gridIdA, gridIdB) = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
var xformSystem = entMan.System<SharedTransformSystem>();
var gridA = mapMan.GetGrid(gridIdA);
var gridB = mapMan.GetGrid(gridIdB);
@@ -51,18 +52,22 @@ namespace Robust.UnitTesting.Client.GameObjects.Components
var child = entMan.SpawnEntity(null, initialPos);
var parentTrans = entMan.GetComponent<TransformComponent>(parent);
var childTrans = entMan.GetComponent<TransformComponent>(child);
ComponentHandleState handleState;
var compState = new TransformComponentState(new Vector2(5, 5), new Angle(0), gridB.Owner, false, false);
parentTrans.HandleComponentState(compState, null);
handleState = new ComponentHandleState(compState, null);
xformSystem.OnHandleState(parent, parentTrans, ref handleState);
compState = new TransformComponentState(new Vector2(6, 6), new Angle(0), gridB.Owner, false, false);
childTrans.HandleComponentState(compState, null);
handleState = new ComponentHandleState(compState, null);
xformSystem.OnHandleState(child, childTrans, ref handleState);
// World pos should be 6, 6 now.
// Act
var oldWpos = childTrans.WorldPosition;
compState = new TransformComponentState(new Vector2(1, 1), new Angle(0), parent, false, false);
childTrans.HandleComponentState(compState, null);
handleState = new ComponentHandleState(compState, null);
xformSystem.OnHandleState(child, childTrans, ref handleState);
var newWpos = childTrans.WorldPosition;
// Assert

View File

@@ -27,6 +27,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using EyeComponent = Robust.Server.GameObjects.EyeComponent;
using MapSystem = Robust.Server.GameObjects.MapSystem;
namespace Robust.UnitTesting
{
@@ -86,6 +87,7 @@ namespace Robust.UnitTesting
var systems = deps.Resolve<IEntitySystemManager>();
// Required systems
systems.LoadExtraSystemType<MapSystem>();
systems.LoadExtraSystemType<EntityLookupSystem>();
// uhhh so maybe these are the wrong system for the client, but I CBF adding sprite system and all the rest,

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