mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbc4668f9c | ||
|
|
ce4016965e | ||
|
|
21e74c9881 | ||
|
|
aa52e8c2ef | ||
|
|
e106d3f72b | ||
|
|
8eae802fb6 | ||
|
|
681feaf0c7 | ||
|
|
0a5a214a06 | ||
|
|
d80be16f6c | ||
|
|
d967bc9fdc | ||
|
|
177ca6b627 | ||
|
|
3c262afaa4 | ||
|
|
bce2901b0f | ||
|
|
c392d4f996 | ||
|
|
d7ee2bccd7 | ||
|
|
4e816fa5e7 | ||
|
|
545e55055e | ||
|
|
6b059ed356 | ||
|
|
b69b4fd8fe | ||
|
|
cb63499ec9 |
@@ -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 -->
|
||||
|
||||
|
||||
@@ -54,6 +54,58 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -144,29 +144,6 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
33
Robust.Client/GameObjects/EntitySystems/EyeSystem.cs
Normal file
33
Robust.Client/GameObjects/EntitySystems/EyeSystem.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class EyeSystem : SharedEyeSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<EyeComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<EyeComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -670,6 +675,8 @@ namespace Robust.Server
|
||||
|
||||
_network.ProcessPackets();
|
||||
_taskManager.ProcessPendingTasks();
|
||||
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.InputPostEngine, args);
|
||||
}
|
||||
|
||||
private void Update(FrameEventArgs frameEventArgs)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +87,5 @@ namespace Robust.Server.GameObjects
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new EyeComponentState(DrawFov, Zoom, Offset, VisibilityMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
Robust.Server/GameObjects/EntitySystems/EyeSystem.cs
Normal file
18
Robust.Server/GameObjects/EntitySystems/EyeSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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} <<";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -208,21 +208,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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -46,6 +49,8 @@ namespace Robust.Shared.GameObjects
|
||||
private UniqueIndexHkm<EntityUid, Component> _entCompIndex =
|
||||
new(ComponentCollectionCapacity);
|
||||
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<AddedComponentEventArgs>? ComponentAdded;
|
||||
|
||||
@@ -63,6 +68,7 @@ namespace Robust.Shared.GameObjects
|
||||
FillComponentDict();
|
||||
_componentFactory.ComponentAdded += OnComponentAdded;
|
||||
_componentFactory.ComponentReferenceAdded += OnComponentReferenceAdded;
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -700,15 +706,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 +1040,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 +1049,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 +1060,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 +1073,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 +1094,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 +1303,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 +1368,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 +1378,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 +1391,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 +1405,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 +1434,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public TComp1? CompOrNull(EntityUid uid)
|
||||
{
|
||||
if (TryGetComponent(uid, out var comp))
|
||||
@@ -1437,6 +1442,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 +1550,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 +1578,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 +1610,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 +1641,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 +1681,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 +1715,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 +1766,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 +1803,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;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ namespace Robust.Shared.GameObjects
|
||||
public event Action<EntityUid>? EntityDirtied; // only raised after initialization
|
||||
|
||||
private string _xformName = string.Empty;
|
||||
private string _metaName = string.Empty;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
private ISawmill _resolveSawmill = default!;
|
||||
@@ -102,7 +101,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 +112,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;
|
||||
|
||||
@@ -398,7 +398,7 @@ public partial class EntitySystem
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.GetComponent<T>"/>
|
||||
[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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
6
Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs
Normal file
6
Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract class SharedEyeSystem : EntitySystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -418,6 +418,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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
154
Robust.Shared/Timing/PrecisionSleep.cs
Normal file
154
Robust.Shared/Timing/PrecisionSleep.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
70
Robust.Shared/Utility/FileHelper.cs
Normal file
70
Robust.Shared/Utility/FileHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -269,7 +270,8 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
public void Container_Serialize()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var containerSys = entManager.System<ContainerSystem>();
|
||||
|
||||
var entity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), new Vector2(0, 0)));
|
||||
var container = containerSys.MakeContainer<Container>(entity, "dummy");
|
||||
@@ -279,10 +281,12 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
container.ShowContents = true;
|
||||
container.Insert(childEnt);
|
||||
|
||||
var containerMan = IoCManager.Resolve<IEntityManager>().GetComponent<IContainerManager>(entity);
|
||||
var state = (ContainerManagerComponent.ContainerManagerComponentState)containerMan.GetComponentState();
|
||||
var containerMan = entManager.GetComponent<IContainerManager>(entity);
|
||||
var getState = new ComponentGetState();
|
||||
entManager.EventBus.RaiseComponentEvent(containerMan, ref getState);
|
||||
var state = (ContainerManagerComponent.ContainerManagerComponentState)getState.State!;
|
||||
|
||||
Assert.That(state.Containers.Count, Is.EqualTo(1));
|
||||
Assert.That(state.Containers, Has.Count.EqualTo(1));
|
||||
var cont = state.Containers.Values.First();
|
||||
Assert.That(cont.Id, Is.EqualTo("dummy"));
|
||||
Assert.That(cont.OccludesLight, Is.True);
|
||||
|
||||
@@ -29,7 +29,12 @@ namespace Robust.UnitTesting.Shared.Timing
|
||||
newStopwatch.SetupGet(p => p.Elapsed).Returns(elapsedVal);
|
||||
var gameTiming = GameTimingFactory(newStopwatch.Object);
|
||||
gameTiming.Paused = false;
|
||||
var loop = new GameLoop(gameTiming, new RuntimeLog(), new ProfManager(), new LogManager().RootSawmill);
|
||||
var loop = new GameLoop(
|
||||
gameTiming,
|
||||
new RuntimeLog(),
|
||||
new ProfManager(),
|
||||
new LogManager().RootSawmill,
|
||||
new GameLoopOptions(false));
|
||||
|
||||
var callCount = 0;
|
||||
loop.Tick += (sender, args) => callCount++;
|
||||
|
||||
Reference in New Issue
Block a user