mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5069b0ccf9 | ||
|
|
12cfdb2175 | ||
|
|
b5e079815d | ||
|
|
ca3a3279c5 | ||
|
|
003752a161 | ||
|
|
55e51cba9c | ||
|
|
2cd829f4f6 | ||
|
|
43138669ec | ||
|
|
3fe30bc00f | ||
|
|
3ccbdeac6a | ||
|
|
9eb9c91da6 | ||
|
|
28d2b47a2c | ||
|
|
049ffa05e4 | ||
|
|
2f36a0a5fc | ||
|
|
41d03db59d | ||
|
|
68df887a65 | ||
|
|
e0bbcd7b08 | ||
|
|
525815427e | ||
|
|
70224ac100 | ||
|
|
dabb090dc2 | ||
|
|
ace8334a3e | ||
|
|
d8e70b4d52 | ||
|
|
2fca0e03ee | ||
|
|
b6980964b6 | ||
|
|
34d02256fd | ||
|
|
34637fb430 | ||
|
|
d905ef2a50 | ||
|
|
c3f7ef1b5c | ||
|
|
ced2a5c6cd | ||
|
|
9ec927543f | ||
|
|
215fc8c229 | ||
|
|
f82ff9e581 | ||
|
|
906f4598a2 | ||
|
|
0eb3c37bd8 | ||
|
|
9cc7cf80ba | ||
|
|
7ba02b5ca6 | ||
|
|
3ca7121f5b | ||
|
|
d3b31c1d58 | ||
|
|
92b5bb4660 | ||
|
|
33caf9c1ba | ||
|
|
58da8a6001 | ||
|
|
962f5dc650 | ||
|
|
c324562513 | ||
|
|
f5a2a710f0 | ||
|
|
357283e2bc | ||
|
|
5991bfa106 | ||
|
|
4160b120e0 | ||
|
|
98a1fa1fba | ||
|
|
fb08451849 | ||
|
|
ebea0d7572 | ||
|
|
eb6f28cce0 | ||
|
|
a1d02d7c55 | ||
|
|
777ab85cff | ||
|
|
d33a8465b0 | ||
|
|
6572fdb404 | ||
|
|
6273b1b80d | ||
|
|
a09a60efe9 | ||
|
|
d3339964ee | ||
|
|
3ffef625ec | ||
|
|
4fd9b2bc3b |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
178
RELEASE-NOTES.md
178
RELEASE-NOTES.md
@@ -54,6 +54,184 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 189.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Use the base AudioParams for networking not the z-offset adjusted ones.
|
||||
* Modulate SpriteView sprites by the control's color modulation.
|
||||
|
||||
### New features
|
||||
|
||||
* Improve YAML linter error messages for parent nodes.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Client clientside entity error spam.
|
||||
|
||||
### Internal
|
||||
|
||||
* Set priorGain to 0 where no EFX is supported for audio rather than 0.5.
|
||||
* Try to hotfix MIDI lock contention more via a semaphore.
|
||||
|
||||
|
||||
## 188.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Return null buffered audio if there's an exception and use the dummy instance internally.
|
||||
* Use entity name then suffix for entity spawn window ordering.
|
||||
* Change MidiManager volume to gain.
|
||||
* Remove EntityQuery from the MapVelocity API.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Potentially fix some audio issues by setting gain to half where EFX not found and the prior gain was 0.
|
||||
* Log errors upon trying to spawn audio attached to deleted entities instead of trying to spawn them and erroring later.
|
||||
* Fixed predicted audio spawns not applying the adjusted audio params.
|
||||
* Fix GetDimensions for the screenhandle where the text is only a single line.
|
||||
|
||||
|
||||
## 187.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added a cancellable bool to physics sleeping events where we may wish to cancel it.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix corrupted physics awake state leading to client mispredicts.
|
||||
|
||||
|
||||
## 187.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Hotfix contact nullrefs if they're modified during manifold generation.
|
||||
|
||||
|
||||
## 187.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert physics solver job to fix crashes until box2d v3 rolls around.
|
||||
* Don't RegenerateContacts if the body isn't collidable to avoid putting non-collidable proxies on the movebuffer.
|
||||
|
||||
|
||||
## 187.1.0
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Apply default audio params to all audio sources not just non-buffered ones.
|
||||
* Avoid re-allocating broadphase job every tick and maybe fix a rare nullref for it.
|
||||
|
||||
|
||||
## 187.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Improved error message for network failing to initialize.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix not being able to add multiple PVS session overrides in a single tick without overwriting each one. This should fix issues with audio filters.
|
||||
|
||||
### Other
|
||||
|
||||
* Changed toolshed initialisation logs to verbose.
|
||||
|
||||
|
||||
## 186.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add public method to get PVS session overrides for a specific session.
|
||||
|
||||
### Internal
|
||||
|
||||
* Add temporary audio debugging.
|
||||
|
||||
|
||||
## 186.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Global audio is now stored on its own map to avoid contamination issues with nullspace.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix MIDIs playing cross-map
|
||||
* Only dispose audio on game closure and don't stop playing if it's disposed elsewhere i.e. MIDIs.
|
||||
|
||||
|
||||
## 185.2.0
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Bandaid deleted MIDI source entities spamming velocity error logs.
|
||||
|
||||
### Other
|
||||
|
||||
* Reverted MIDI audio not updating every frame due to lock contention with the MIDI renderer for now.
|
||||
|
||||
|
||||
## 185.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix Z-Offset for audio not being applied on initialization.
|
||||
|
||||
### Internal
|
||||
|
||||
* Flag some internal queries as approximate to avoid unnecessary AABB checks. Some of these are already covered off with TestOverlap calls and the rest will need updating to do so in a future update.
|
||||
|
||||
|
||||
## 185.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Audio listener's velocity is set using the attached entity's velocity rather than ignored.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix imprecision on audio position
|
||||
|
||||
|
||||
## 185.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Added a flag for grid-based audio rather than implicitly doing it.
|
||||
|
||||
### New features
|
||||
|
||||
* Added IRobustJob and IParallelRobustJob (which splits out into IRobustJob). These can be passed to ParallelManager for work to be run on the threadpool without relying upon Task.Run / Parallel.For which can allocate significantly more. It also has conveniences such as being able to specify batch sizing via the interface implementation.
|
||||
|
||||
|
||||
## 184.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add API to get gain / volume for a provided value on SharedAudioSystem.
|
||||
* Make GetOcclusion public for AudioSystem.
|
||||
* Add SharedAudioSystem.SetGain to complement SharedAudioSystem.SetVolume
|
||||
|
||||
|
||||
## 184.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Update MIDI position and occlusion every frame instead of at set intervals.
|
||||
* Fix global audio not being global.
|
||||
|
||||
|
||||
## 184.0.0
|
||||
|
||||
### Internal
|
||||
|
||||
* Add RobustMemoryManager with RecyclableIOMemoryStream to significantly reduce MsgState allocations until better memory management is implemented.
|
||||
|
||||
|
||||
## 183.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -60,6 +60,12 @@ internal partial class AudioManager
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
AL.Listener(ALListener3f.Velocity, velocity.X, velocity.Y, 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
@@ -208,9 +214,9 @@ internal partial class AudioManager
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, newVolume);
|
||||
AL.Listener(ALListenerf.Gain, newGain);
|
||||
}
|
||||
|
||||
public void SetAttenuation(Attenuation attenuation)
|
||||
@@ -272,25 +278,37 @@ internal partial class AudioManager
|
||||
|
||||
var audioSource = new AudioSource(this, source, stream);
|
||||
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
|
||||
ApplyDefaultParams(audioSource);
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
/// <inheritdoc/>
|
||||
public IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
{
|
||||
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
||||
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);
|
||||
_bufferedAudioSources.Add(source, new WeakReference<BufferedAudioSource>(audioSource));
|
||||
ApplyDefaultParams(audioSource);
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
private void ApplyDefaultParams(IAudioSource source)
|
||||
{
|
||||
source.MaxDistance = AudioParams.Default.MaxDistance;
|
||||
source.Pitch = AudioParams.Default.Pitch;
|
||||
source.ReferenceDistance = AudioParams.Default.ReferenceDistance;
|
||||
source.RolloffFactor = AudioParams.Default.RolloffFactor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopAllAudio()
|
||||
{
|
||||
@@ -318,7 +336,6 @@ internal partial class AudioManager
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -329,7 +346,6 @@ internal partial class AudioManager
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
|
||||
}
|
||||
|
||||
internal bool IsMainThread()
|
||||
|
||||
@@ -69,7 +69,7 @@ public sealed class AudioOverlay : Overlay
|
||||
|
||||
var screenPos = args.ViewportControl.WorldToScreen(audioPos);
|
||||
var distance = audioPos - listenerPos.Position;
|
||||
var posOcclusion = _audio.GetOcclusion(uid, listenerPos, distance, distance.Length());
|
||||
var posOcclusion = _audio.GetOcclusion(listenerPos, distance, distance.Length(), uid);
|
||||
|
||||
output.Clear();
|
||||
output.AppendLine("Audio Source");
|
||||
|
||||
@@ -2,9 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -23,7 +23,6 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
@@ -34,6 +33,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
* but exposing the whole thing in an easy way is a lot of effort.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
@@ -49,25 +49,52 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
private readonly List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> _streams = new();
|
||||
private EntityUid? _listenerGrid;
|
||||
private UpdateAudioJob _updateAudioJob;
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
|
||||
public override float ZOffset
|
||||
{
|
||||
get => _zOffset;
|
||||
protected set
|
||||
{
|
||||
_zOffset = value;
|
||||
_audio.SetZOffset(value);
|
||||
|
||||
var query = AllEntityQuery<AudioComponent>();
|
||||
|
||||
while (query.MoveNext(out var audio))
|
||||
{
|
||||
// Pythagoras back to normal then adjust.
|
||||
var maxDistance = GetAudioDistance(audio.Params.MaxDistance);
|
||||
var refDistance = GetAudioDistance(audio.Params.ReferenceDistance);
|
||||
|
||||
audio.MaxDistance = maxDistance;
|
||||
audio.ReferenceDistance = refDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float _zOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_updateAudioJob = new UpdateAudioJob
|
||||
{
|
||||
System = this,
|
||||
Streams = _streams,
|
||||
};
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
// Need to run after Eye updates so we have an accurate listener position.
|
||||
UpdatesAfter.Add(typeof(EyeSystem));
|
||||
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<AudioComponent, ComponentStartup>(OnAudioStartup);
|
||||
SubscribeLocalEvent<AudioComponent, ComponentShutdown>(OnAudioShutdown);
|
||||
@@ -103,13 +130,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
public void SetMasterVolume(float value)
|
||||
{
|
||||
_audio.SetMasterVolume(value);
|
||||
}
|
||||
|
||||
protected override void SetZOffset(float value)
|
||||
{
|
||||
base.SetZOffset(value);
|
||||
_audio.SetZOffset(value);
|
||||
_audio.SetMasterGain(value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -140,7 +161,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
if (!TryGetAudio(component.FileName, out var audioResource))
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}");
|
||||
component.Source = new DummyAudioSource();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,20 +170,22 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}");
|
||||
DebugTools.Assert(false);
|
||||
source = new DummyAudioSource();
|
||||
source = component.Source;
|
||||
}
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
component.Source = source;
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Global = component.Global;
|
||||
source.Global = component.Global;
|
||||
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % GetAudioLength(component.FileName).TotalSeconds;
|
||||
|
||||
if (offset != 0)
|
||||
if (offset > 0)
|
||||
{
|
||||
component.PlaybackPosition = (float) offset;
|
||||
}
|
||||
@@ -188,11 +210,19 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var eye = _eyeManager.CurrentEye;
|
||||
var localEntity = _playerManager.LocalEntity;
|
||||
Vector2 listenerVelocity;
|
||||
|
||||
if (localEntity != null)
|
||||
listenerVelocity = _physics.GetMapLinearVelocity(localEntity.Value);
|
||||
else
|
||||
listenerVelocity = Vector2.Zero;
|
||||
|
||||
_audio.SetVelocity(listenerVelocity);
|
||||
_audio.SetRotation(eye.Rotation);
|
||||
_audio.SetPosition(eye.Position.Position);
|
||||
|
||||
var ourPos = eye.Position;
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
|
||||
var ourPos = GetListenerCoordinates();
|
||||
|
||||
var query = AllEntityQuery<AudioComponent, TransformComponent>();
|
||||
_streams.Clear();
|
||||
@@ -207,7 +237,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
try
|
||||
{
|
||||
Parallel.ForEach(_streams, opts, comp => ProcessStream(comp.Entity, comp.Component, comp.Xform, ourPos));
|
||||
_updateAudioJob.OurPosition = ourPos;
|
||||
_parMan.ProcessNow(_updateAudioJob, _streams.Count);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -216,6 +247,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
}
|
||||
|
||||
public MapCoordinates GetListenerCoordinates()
|
||||
{
|
||||
return _eyeManager.CurrentEye.Position;
|
||||
}
|
||||
|
||||
private void ProcessStream(EntityUid entity, AudioComponent component, TransformComponent xform, MapCoordinates listener)
|
||||
{
|
||||
// TODO:
|
||||
@@ -253,7 +289,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var gridUid = xform.ParentUid;
|
||||
|
||||
// Handle grid audio differently by using nearest-edge instead of entity centre.
|
||||
if (_gridQuery.HasComponent(gridUid))
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
{
|
||||
// It's our grid so max volume.
|
||||
if (_listenerGrid == gridUid)
|
||||
@@ -309,8 +345,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
var occlusion = GetOcclusion(entity, listener, delta, distance);
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
|
||||
// Update audio positions.
|
||||
@@ -321,12 +364,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
|
||||
// inefficient.
|
||||
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform, _xformQuery, _physicsQuery);
|
||||
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform);
|
||||
component.Velocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
internal float GetOcclusion(EntityUid entity, MapCoordinates listener, Vector2 delta, float distance)
|
||||
/// <summary>
|
||||
/// Gets the audio occlusion from the target audio entity to the listener's position.
|
||||
/// </summary>
|
||||
public float GetOcclusion(MapCoordinates listener, Vector2 delta, float distance, EntityUid? ignoredEnt = null)
|
||||
{
|
||||
float occlusion = 0;
|
||||
|
||||
@@ -334,7 +380,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
var rayLength = MathF.Min(distance, _maxRayLength);
|
||||
var ray = new CollisionRay(listener.Position, delta / distance, OcclusionCollisionMask);
|
||||
occlusion = _physics.IntersectRayPenetration(listener.MapId, ray, rayLength, entity);
|
||||
occlusion = _physics.IntersectRayPenetration(listener.MapId, ray, rayLength, ignoredEnt);
|
||||
}
|
||||
|
||||
return occlusion;
|
||||
@@ -452,6 +498,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
@@ -487,6 +539,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
return playing;
|
||||
@@ -559,6 +617,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
|
||||
ApplyAudioParams(audioP, comp);
|
||||
comp.Params = audioP;
|
||||
source.StartPlaying();
|
||||
@@ -573,8 +632,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
source.Pitch = audioParams.Pitch;
|
||||
source.Volume = audioParams.Volume;
|
||||
source.RolloffFactor = audioParams.RolloffFactor;
|
||||
source.MaxDistance = audioParams.MaxDistance;
|
||||
source.ReferenceDistance = audioParams.ReferenceDistance;
|
||||
source.MaxDistance = GetAudioDistance(audioParams.MaxDistance);
|
||||
source.ReferenceDistance = GetAudioDistance(audioParams.ReferenceDistance);
|
||||
source.Looping = audioParams.Loop;
|
||||
}
|
||||
|
||||
@@ -597,4 +656,25 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
return _resourceCache.GetResource<AudioResource>(filename).AudioStream.Length;
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
private record struct UpdateAudioJob : IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => 2;
|
||||
|
||||
public AudioSystem System;
|
||||
|
||||
public MapCoordinates OurPosition;
|
||||
public List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> Streams;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var comp = Streams[index];
|
||||
|
||||
System.ProcessStream(comp.Entity, comp.Component, comp.Xform, OurPosition);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -35,11 +35,16 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
public IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
@@ -51,7 +56,7 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetMasterVolume(float value)
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Audio;
|
||||
/// <summary>
|
||||
/// Handles clientside audio.
|
||||
/// </summary>
|
||||
internal interface IAudioInternal
|
||||
internal interface IAudioInternal : IAudioManager
|
||||
{
|
||||
void InitializePostWindowing();
|
||||
void Shutdown();
|
||||
@@ -23,7 +23,16 @@ internal interface IAudioInternal
|
||||
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
|
||||
IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
/// <summary>
|
||||
/// Returns a buffered audio source.
|
||||
/// </summary>
|
||||
/// <returns>null if unable to create the source.</returns>
|
||||
IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the velocity for the audio listener.
|
||||
/// </summary>
|
||||
void SetVelocity(Vector2 velocity);
|
||||
|
||||
/// <summary>
|
||||
/// Sets position for the audio listener.
|
||||
@@ -35,7 +44,6 @@ internal interface IAudioInternal
|
||||
/// </summary>
|
||||
void SetRotation(Angle angle);
|
||||
|
||||
void SetMasterVolume(float value);
|
||||
void SetAttenuation(Attenuation attenuation);
|
||||
|
||||
/// <summary>
|
||||
|
||||
9
Robust.Client/Audio/IAudioManager.cs
Normal file
9
Robust.Client/Audio/IAudioManager.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Public audio API for stuff that can't go through <see cref="AudioSystem"/>
|
||||
/// </summary>
|
||||
public interface IAudioManager
|
||||
{
|
||||
void SetMasterGain(float gain);
|
||||
}
|
||||
@@ -17,11 +17,9 @@ public interface IMidiManager
|
||||
bool IsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// Gain of audio.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
float Gain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method tries to return a midi renderer ready to be used.
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
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;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -20,7 +18,6 @@ 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;
|
||||
@@ -34,14 +31,7 @@ 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 IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
@@ -50,9 +40,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
[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 AudioSystem _audioSys = default!;
|
||||
private SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
private SharedTransformSystem _xformSystem = default!;
|
||||
|
||||
public IReadOnlyList<IMidiRenderer> Renderers
|
||||
{
|
||||
@@ -79,24 +70,32 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
[ViewVariables] private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
// To avoid lock contention until some kind of MIDI refactor.
|
||||
private TimeSpan _nextUpdate;
|
||||
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(0.1f);
|
||||
|
||||
private SemaphoreSlim _updateSemaphore = new(1);
|
||||
|
||||
private bool _alive = true;
|
||||
[ViewVariables] private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private float _gain = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
public float Gain
|
||||
{
|
||||
get => _volume;
|
||||
get => _gain;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseToPercent(_volume, value))
|
||||
var clamped = Math.Clamp(value, 0f, 1f);
|
||||
|
||||
if (MathHelper.CloseToPercent(_gain, clamped))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -133,10 +132,9 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _fluidsynthSawmill = default!;
|
||||
private float _maxCastLength;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
private MidiUpdateJob _updateJob;
|
||||
|
||||
|
||||
public MidiManager()
|
||||
{
|
||||
@@ -149,19 +147,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_gain = 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;
|
||||
@@ -215,8 +204,17 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_updateJob = new MidiUpdateJob()
|
||||
{
|
||||
Manager = this,
|
||||
Renderers = _renderers,
|
||||
};
|
||||
|
||||
_audioSys = _entityManager.EntitySysManager.GetEntitySystem<AudioSystem>();
|
||||
_broadPhaseSystem = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
|
||||
_cfgMan.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
_xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
_entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
_entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
@@ -233,11 +231,6 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
|
||||
private void OnRaycastLengthChanged(float value)
|
||||
{
|
||||
_maxCastLength = value;
|
||||
}
|
||||
|
||||
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
|
||||
{
|
||||
var rLevel = level switch
|
||||
@@ -352,7 +345,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
renderer.Source.Volume = _volume;
|
||||
renderer.Source.Gain = _gain;
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
@@ -373,44 +366,33 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
// Update positions of streams every frame.
|
||||
if (_nextUpdate > _timing.RealTime)
|
||||
return;
|
||||
|
||||
_nextUpdate = _timing.RealTime + _updateFrequency;
|
||||
|
||||
// Update positions of streams occasionally.
|
||||
// This has a lot of code duplication with AudioSystem.FrameUpdate(), and they should probably be combined somehow.
|
||||
// so TRUE
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
if (_renderers.Count == 0)
|
||||
return;
|
||||
_updateJob.OurPosition = _audioSys.GetListenerCoordinates();
|
||||
|
||||
var transQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parallel.ParallelProcessCount };
|
||||
// This semaphore is here to avoid lock contention as much as possible.
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
if (_renderers.Count > _minRendererParallel)
|
||||
{
|
||||
Parallel.ForEach(_renderers, opts, renderer => UpdateRenderer(renderer, transQuery, physicsQuery));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
UpdateRenderer(renderer, transQuery, physicsQuery);
|
||||
}
|
||||
}
|
||||
// The ONLY time this should be contested is with ThreadUpdate.
|
||||
// If that becomes NOT the case then just lock this, remove the semaphore, and drop the update frequency even harder.
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
_parallel.ProcessNow(_updateJob, _renderers.Count);
|
||||
|
||||
}
|
||||
|
||||
if (_nextOcclusionUpdate < _timing.RealTime)
|
||||
_nextOcclusionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_occlusionUpdateDelay));
|
||||
|
||||
if (_nextPositionUpdate < _timing.RealTime)
|
||||
_nextPositionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_positionUpdateDelay));
|
||||
_updateSemaphore.Release();
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
private void UpdateRenderer(IMidiRenderer renderer, EntityQuery<TransformComponent> transQuery,
|
||||
EntityQuery<PhysicsComponent> physicsQuery)
|
||||
|
||||
private void UpdateRenderer(IMidiRenderer renderer, MapCoordinates listener)
|
||||
{
|
||||
// TODO: This should be sharing more code with AudioSystem.
|
||||
try
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
@@ -418,7 +400,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
if (_volumeDirty)
|
||||
{
|
||||
renderer.Source.Volume = Volume;
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
if (!renderer.Mono)
|
||||
@@ -427,66 +409,82 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
if (_nextPositionUpdate < _timing.RealTime)
|
||||
MapCoordinates mapPos;
|
||||
|
||||
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
|
||||
{
|
||||
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
|
||||
{
|
||||
renderer.TrackingCoordinates = transQuery.GetComponent(renderer.TrackingEntity!.Value).MapPosition;
|
||||
}
|
||||
else if (renderer.TrackingCoordinates == null)
|
||||
renderer.TrackingCoordinates = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
|
||||
|
||||
// Pause it if the attached entity is paused.
|
||||
if (_entityManager.IsPaused(renderer.TrackingEntity))
|
||||
{
|
||||
renderer.Source.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
var position = renderer.TrackingCoordinates.Value;
|
||||
|
||||
if (position.MapId == MapId.Nullspace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.Source.Position = position.Position;
|
||||
|
||||
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value,
|
||||
xformQuery: transQuery, physicsQuery: physicsQuery);
|
||||
|
||||
renderer.Source.Velocity = vel;
|
||||
}
|
||||
else if (renderer.TrackingCoordinates == null)
|
||||
{
|
||||
renderer.Source.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderer.TrackingCoordinates != null && renderer.TrackingCoordinates.Value.MapId == _eyeManager.CurrentMap)
|
||||
mapPos = renderer.TrackingCoordinates.Value;
|
||||
|
||||
// If it's on a different map then just mute it, not pause.
|
||||
if (mapPos.MapId == MapId.Nullspace || mapPos.MapId != listener.MapId)
|
||||
{
|
||||
if (_nextOcclusionUpdate >= _timing.RealTime)
|
||||
return;
|
||||
renderer.Source.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = renderer.TrackingCoordinates.Value;
|
||||
// Was previously muted maybe so try unmuting it?
|
||||
if (renderer.Source.Gain == 0f)
|
||||
{
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
var worldPos = mapPos.Position;
|
||||
var delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
renderer.Source.Occlusion = occlusion;
|
||||
// Update position
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > renderer.Source.MaxDistance)
|
||||
{
|
||||
// Still keeps the source playing, just with no volume.
|
||||
renderer.Source.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Same imprecision suppression as audiosystem.
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
renderer.Source.Position = worldPos;
|
||||
|
||||
// Update velocity (doppler).
|
||||
if (!_entityManager.Deleted(renderer.TrackingEntity))
|
||||
{
|
||||
var velocity = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity.Value);
|
||||
renderer.Source.Velocity = velocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Source.Occlusion = float.MaxValue;
|
||||
renderer.Source.Velocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
// Update occlusion
|
||||
var occlusion = _audioSys.GetOcclusion(listener, delta, distance, renderer.TrackingEntity);
|
||||
renderer.Source.Occlusion = occlusion;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_runtime.LogException(ex, _midiSawmill.Name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -498,21 +496,39 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
var toRemove = new ValueList<IMidiRenderer>();
|
||||
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
lock (renderer)
|
||||
{
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
toRemove.Add(renderer);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
foreach (var renderer in toRemove)
|
||||
{
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
|
||||
_updateSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,4 +699,31 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
private record struct MidiUpdateJob : IParallelRobustJob
|
||||
{
|
||||
public int MinimumBatchParallel => 2;
|
||||
|
||||
public int BatchSize => 1;
|
||||
|
||||
public MidiManager Manager;
|
||||
|
||||
public MapCoordinates OurPosition;
|
||||
public List<IMidiRenderer> Renderers;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
// The indices shouldn't be able to be touched while this job is running, just the renderer itself getting locked.
|
||||
var renderer = Renderers[index];
|
||||
|
||||
lock (renderer)
|
||||
{
|
||||
Manager.UpdateRenderer(renderer, OurPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_taskManager = taskManager;
|
||||
_midiSawmill = midiSawmill;
|
||||
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true);
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true) ?? DummyBufferedAudioSource.Instance;
|
||||
Source.SampleRate = SampleRate;
|
||||
_settings = settings;
|
||||
_soundFontLoader = soundFontLoader;
|
||||
|
||||
@@ -5,6 +5,7 @@ using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
@@ -164,10 +165,10 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
get
|
||||
{
|
||||
var gain = Gain;
|
||||
var volume = 10f * MathF.Log10(gain);
|
||||
var volume = SharedAudioSystem.GainToVolume(gain);
|
||||
return volume;
|
||||
}
|
||||
set => Gain = MathF.Pow(10, value / 10);
|
||||
set => Gain = SharedAudioSystem.VolumeToGain(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -187,7 +188,8 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
// Default to 0 to avoid spiking audio, just means it will be muted for a frame in this case.
|
||||
priorOcclusion = _gain == 0 ? 0f : priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = value;
|
||||
|
||||
@@ -20,8 +20,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
private bool _float = false;
|
||||
private int FilterHandle;
|
||||
|
||||
private float _gain;
|
||||
|
||||
public int SampleRate { get; set; } = 44100;
|
||||
|
||||
private bool IsEfxSupported => _master.IsEfxSupported;
|
||||
@@ -37,7 +35,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
BufferMap[bufferHandle] = i;
|
||||
}
|
||||
_float = floatAudio;
|
||||
AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, ClydeHeadless>();
|
||||
deps.Register<IClipboardManager, ClydeHeadless>();
|
||||
deps.Register<IClydeInternal, ClydeHeadless>();
|
||||
deps.Register<IAudioManager, HeadlessAudioManager>();
|
||||
deps.Register<IAudioInternal, HeadlessAudioManager>();
|
||||
deps.Register<IInputManager, InputManager>();
|
||||
deps.Register<IFileDialogManager, DummyFileDialogManager>();
|
||||
@@ -116,6 +117,7 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, Clyde>();
|
||||
deps.Register<IClipboardManager, Clyde>();
|
||||
deps.Register<IClydeInternal, Clyde>();
|
||||
deps.Register<IAudioManager, AudioManager>();
|
||||
deps.Register<IAudioInternal, AudioManager>();
|
||||
deps.Register<IInputManager, ClydeInputManager>();
|
||||
deps.Register<IFileDialogManager, FileDialogManager>();
|
||||
|
||||
@@ -119,6 +119,7 @@ namespace Robust.Client
|
||||
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
|
||||
|
||||
_taskManager.Initialize();
|
||||
_parallelMgr.Initialize();
|
||||
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
|
||||
// Load optional Robust modules.
|
||||
@@ -360,7 +361,6 @@ namespace Robust.Client
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_parallelMgr.Initialize();
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -8,6 +9,13 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
// AudioSystem sets eye position and rotation so rely on those.
|
||||
UpdatesAfter.Add(typeof(AudioSystem));
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
@@ -174,19 +174,21 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void OnComponentAdded(AddedComponentEventArgs args)
|
||||
{
|
||||
if (_resettingPredictedEntities)
|
||||
{
|
||||
var comp = args.ComponentType;
|
||||
if (!_resettingPredictedEntities)
|
||||
return;
|
||||
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
var comp = args.ComponentType;
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} with net id {comp.NetID} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
}
|
||||
if (_entityManager.IsClientSide(args.BaseArgs.Owner))
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} to entity {_entityManager.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -116,7 +116,6 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
baseLine.Y += lineHeight;
|
||||
advanceTotal.Y += lineHeight;
|
||||
advanceTotal.X = Math.Max(advanceTotal.X, baseLine.X);
|
||||
baseLine.X = 0f;
|
||||
continue;
|
||||
}
|
||||
@@ -128,6 +127,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
var advance = metrics.Value.Advance;
|
||||
baseLine += new Vector2(advance, 0);
|
||||
advanceTotal.X = MathF.Max(baseLine.X, advanceTotal.X);
|
||||
}
|
||||
|
||||
return advanceTotal;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Client.Graphics
|
||||
Vector2 scale,
|
||||
Angle? worldRot,
|
||||
Angle eyeRotation = default,
|
||||
Shared.Maths.Direction? overrideDirection = null,
|
||||
Direction? overrideDirection = null,
|
||||
SpriteComponent? sprite = null,
|
||||
TransformComponent? xform = null,
|
||||
SharedTransformSystem? xformSystem = null);
|
||||
|
||||
@@ -213,7 +213,12 @@ public sealed class EntitySpawningUIController : UIController
|
||||
_shownEntities.Add(prototype);
|
||||
}
|
||||
|
||||
_shownEntities.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
_shownEntities.Sort((a, b) => {
|
||||
var namesComparation = string.Compare(a.Name, b.Name, StringComparison.Ordinal);
|
||||
if (namesComparation == 0)
|
||||
return string.Compare(a.EditorSuffix, b.EditorSuffix, StringComparison.Ordinal);
|
||||
return namesComparation;
|
||||
});
|
||||
|
||||
_window.PrototypeList.TotalItemCount = _shownEntities.Count;
|
||||
_window.PrototypeScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
|
||||
@@ -13,17 +13,15 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[Virtual]
|
||||
public class SpriteView : Control
|
||||
{
|
||||
private SpriteSystem? _spriteSystem;
|
||||
private SpriteSystem? _sprite;
|
||||
private SharedTransformSystem? _transform;
|
||||
IEntityManager _entMan;
|
||||
|
||||
[ViewVariables]
|
||||
public SpriteComponent? Sprite { get; private set; }
|
||||
|
||||
public SpriteComponent? Sprite => Entity?.Comp1;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? Entity { get; private set; }
|
||||
|
||||
public Entity<SpriteComponent>? Ent => Entity == null || Sprite == null ? null : (Entity.Value, Sprite);
|
||||
public Entity<SpriteComponent, TransformComponent>? Entity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This field configures automatic scaling of the sprite. This automatic scaling is done before
|
||||
@@ -118,23 +116,30 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public SpriteView()
|
||||
{
|
||||
_entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (_entMan.TryGetComponent(Entity, out SpriteComponent? sprite))
|
||||
{
|
||||
Sprite = sprite;
|
||||
}
|
||||
|
||||
IoCManager.Resolve(ref _entMan);
|
||||
RectClipContent = true;
|
||||
}
|
||||
|
||||
public SpriteView(EntityUid uid, IEntityManager entMan)
|
||||
{
|
||||
_entMan = entMan;
|
||||
RectClipContent = true;
|
||||
SetEntity(uid);
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid? uid)
|
||||
{
|
||||
Entity = uid;
|
||||
if (Entity?.Owner == uid)
|
||||
return;
|
||||
|
||||
if (_entMan.TryGetComponent(Entity, out SpriteComponent? sprite))
|
||||
if (!_entMan.TryGetComponent(uid, out SpriteComponent? sprite)
|
||||
|| !_entMan.TryGetComponent(uid, out TransformComponent? xform))
|
||||
{
|
||||
Sprite = sprite;
|
||||
Entity = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Entity = new(uid.Value, sprite, xform);
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
@@ -146,13 +151,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private void UpdateSize()
|
||||
{
|
||||
if (Entity == null || Sprite == null)
|
||||
if (Entity is not { } ent)
|
||||
{
|
||||
_spriteSize = default;
|
||||
return;
|
||||
}
|
||||
|
||||
var spriteBox = Sprite.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
|
||||
var spriteBox = ent.Comp1.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
|
||||
.CalcBoundingBox();
|
||||
|
||||
if (!SpriteOffset)
|
||||
@@ -194,18 +199,22 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
internal override void DrawInternal(IRenderHandle renderHandle)
|
||||
{
|
||||
if (Entity is not {} uid || Sprite == null)
|
||||
if (Entity == null)
|
||||
return;
|
||||
|
||||
if (Sprite.Deleted)
|
||||
var (uid, sprite, xform) = Entity.Value;
|
||||
|
||||
if (sprite.Deleted)
|
||||
{
|
||||
SetEntity(null);
|
||||
return;
|
||||
}
|
||||
|
||||
_sprite ??= _entMan.System<SpriteSystem>();
|
||||
_transform ??= _entMan.System<TransformSystem>();
|
||||
|
||||
// Ensure the sprite is animated despite possible not being visible in any viewport.
|
||||
_spriteSystem ??= _entMan.System<SpriteSystem>();
|
||||
_spriteSystem.ForceUpdate(uid);
|
||||
_sprite.ForceUpdate(uid);
|
||||
|
||||
var stretchVec = Stretch switch
|
||||
{
|
||||
@@ -217,11 +226,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var offset = SpriteOffset
|
||||
? Vector2.Zero
|
||||
: - (-_eyeRotation).RotateVec(Sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
: - (-_eyeRotation).RotateVec(sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
|
||||
var position = PixelSize / 2 + offset * stretch * UIScale;
|
||||
var scale = Scale * UIScale * stretch;
|
||||
renderHandle.DrawEntity(uid, position, scale, _worldRotation, _eyeRotation, OverrideDirection, Sprite);
|
||||
|
||||
// control modulation is applied automatically to the screen handle, but here we need to use the world handle
|
||||
var world = renderHandle.DrawingHandleWorld;
|
||||
var oldModulate = world.Modulate;
|
||||
world.Modulate *= Modulate * ActualModulateSelf;
|
||||
|
||||
renderHandle.DrawEntity(uid, position, scale, _worldRotation, _eyeRotation, OverrideDirection, sprite, xform, _transform);
|
||||
world.Modulate = oldModulate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -40,6 +41,7 @@ internal partial class UserInterfaceManager
|
||||
private bool _showingTooltip;
|
||||
private Control? _suppliedTooltip;
|
||||
private const float TooltipDelay = 0.25f;
|
||||
private readonly Dictionary<BoundKeyFunction, Control> _focusedControls = new();
|
||||
|
||||
private WindowRoot? _focusedRoot;
|
||||
|
||||
@@ -109,13 +111,13 @@ internal partial class UserInterfaceManager
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
_focusedControls[args.Function] = control;
|
||||
OnKeyBindDown?.Invoke(control);
|
||||
}
|
||||
|
||||
public void KeyBindUp(BoundKeyEventArgs args)
|
||||
{
|
||||
var control = ControlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation);
|
||||
if (control == null)
|
||||
if (!_focusedControls.TryGetValue(args.Function, out var control))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -129,6 +131,7 @@ internal partial class UserInterfaceManager
|
||||
// Always mark this as handled.
|
||||
// The only case it should not be is if we do not have a control to click on,
|
||||
// in which case we never reach this.
|
||||
_focusedControls.Remove(args.Function);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
|
||||
@@ -74,15 +74,18 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var entity = Spawn("Audio", MapCoordinates.Nullspace);
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
AddAudioFilter(entity, audio, playerFilter);
|
||||
|
||||
audio.Global = true;
|
||||
return (entity, audio);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
if (TerminatingOrDeleted(uid))
|
||||
{
|
||||
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
@@ -94,8 +97,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
if (TerminatingOrDeleted(uid))
|
||||
{
|
||||
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
@@ -106,6 +112,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return null;
|
||||
|
||||
@@ -120,6 +132,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return null;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using Prometheus;
|
||||
using Robust.Server.Console;
|
||||
@@ -204,8 +205,6 @@ namespace Robust.Server
|
||||
|
||||
ProfileOptSetup.Setup(_config);
|
||||
|
||||
_parallelMgr.Initialize();
|
||||
|
||||
//Sets up Logging
|
||||
_logHandlerFactory = logHandlerFactory;
|
||||
|
||||
@@ -268,6 +267,7 @@ namespace Robust.Server
|
||||
|
||||
// Has to be done early because this guy's in charge of the main thread Synchronization Context.
|
||||
_taskManager.Initialize();
|
||||
_parallelMgr.Initialize();
|
||||
|
||||
LoadSettings();
|
||||
|
||||
@@ -279,12 +279,15 @@ namespace Robust.Server
|
||||
_network.Initialize(true);
|
||||
_network.StartServer();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
||||
{
|
||||
var port = _network.Port;
|
||||
_logger.Fatal(
|
||||
"Unable to setup networking manager. Check port {0} is not already in use and that all binding addresses are correct!\n{1}",
|
||||
port, e);
|
||||
_logger.Fatal("Unable to setup networking manager. Make sure that you aren't running the server twice and that port {0} is not in use by another application.\n{1}", port, e);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Fatal("Unable to setup networking manager!\n{0}", e);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -226,10 +226,15 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
gridLoc.Add(index);
|
||||
dirtyChunks.Add(gridChunkLocation);
|
||||
break;
|
||||
case SessionOverride sessionOverride:
|
||||
if (!_sessionOverrides.TryGetValue(sessionOverride.Session, out var set))
|
||||
return;
|
||||
set.Add(index);
|
||||
case SessionsOverride sessionOverride:
|
||||
foreach (var sesh in sessionOverride.Sessions)
|
||||
{
|
||||
if (!_sessionOverrides.TryGetValue(sesh, out var set))
|
||||
continue;
|
||||
|
||||
set.Add(index);
|
||||
}
|
||||
|
||||
break;
|
||||
case MapChunkLocation mapChunkLocation:
|
||||
// might be gone due to map-deletions
|
||||
@@ -259,8 +264,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
case GridChunkLocation gridChunkLocation:
|
||||
_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices].Remove(index);
|
||||
break;
|
||||
case SessionOverride sessionOverride:
|
||||
_sessionOverrides.GetValueOrDefault(sessionOverride.Session)?.Remove(index);
|
||||
case SessionsOverride sessionOverride:
|
||||
foreach (var sesh in sessionOverride.Sessions)
|
||||
{
|
||||
_sessionOverrides.GetValueOrDefault(sesh)?.Remove(index);
|
||||
}
|
||||
break;
|
||||
case MapChunkLocation mapChunkLocation:
|
||||
_mapChunkContents[mapChunkLocation.MapId][mapChunkLocation.ChunkIndices].Remove(index);
|
||||
@@ -410,7 +418,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeFromOverride && oldLocation is SessionOverride)
|
||||
if (!removeFromOverride && oldLocation is SessionsOverride)
|
||||
return;
|
||||
|
||||
if (oldLocation is GlobalOverride global &&
|
||||
@@ -433,20 +441,29 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
{
|
||||
if (!TryGetLocation(index, out var oldLocation))
|
||||
{
|
||||
RegisterUpdate(index, new SessionOverride(session));
|
||||
RegisterUpdate(index, new SessionsOverride(new HashSet<ICommonSession>()
|
||||
{
|
||||
session
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeFromOverride && oldLocation is GlobalOverride)
|
||||
return;
|
||||
|
||||
if (oldLocation is SessionOverride local &&
|
||||
(!removeFromOverride || local.Session == session))
|
||||
if (oldLocation is SessionsOverride local)
|
||||
{
|
||||
if (!removeFromOverride || local.Sessions.Contains(session))
|
||||
return;
|
||||
|
||||
local.Sessions.Add(session);
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterUpdate(index, new SessionOverride(session));
|
||||
RegisterUpdate(index, new SessionsOverride(new HashSet<ICommonSession>()
|
||||
{
|
||||
session
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -459,7 +476,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
{
|
||||
if (!removeFromOverride
|
||||
&& TryGetLocation(index, out var oldLocation)
|
||||
&& oldLocation is GlobalOverride or SessionOverride)
|
||||
&& oldLocation is GlobalOverride or SessionsOverride)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -509,7 +526,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
_indexLocations.TryGetValue(index, out var oldLocation);
|
||||
|
||||
//removeFromOverride is false 99% of the time.
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or SessionOverride && !removeFromOverride)
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or SessionsOverride && !removeFromOverride)
|
||||
return;
|
||||
|
||||
if (oldLocation is GridChunkLocation oldGrid &&
|
||||
@@ -540,7 +557,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
_indexLocations.TryGetValue(index, out var oldLocation);
|
||||
|
||||
//removeFromOverride is false 99% of the time.
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or SessionOverride && !removeFromOverride)
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or SessionsOverride && !removeFromOverride)
|
||||
return;
|
||||
|
||||
// Is this entity just returning to its old location?
|
||||
@@ -640,14 +657,17 @@ public struct GlobalOverride : IIndexLocation
|
||||
}
|
||||
}
|
||||
|
||||
public struct SessionOverride : IIndexLocation
|
||||
/// <summary>
|
||||
/// Adds overrides for the specified sessions for this entity.
|
||||
/// </summary>
|
||||
public struct SessionsOverride : IIndexLocation
|
||||
{
|
||||
public SessionOverride(ICommonSession session)
|
||||
public SessionsOverride(HashSet<ICommonSession> sessions)
|
||||
{
|
||||
Session = session;
|
||||
Sessions = sessions;
|
||||
}
|
||||
|
||||
public readonly ICommonSession Session;
|
||||
public readonly HashSet<ICommonSession> Sessions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -11,6 +12,21 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly PvsSystem _pvs = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Yields the NetEntity session overrides for the specified session.
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
public IEnumerable<NetEntity> GetSessionOverrides(ICommonSession session)
|
||||
{
|
||||
var enumerator = _pvs.EntityPVSCollection.GetSessionOverrides(session);
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var current = enumerator.Current;
|
||||
yield return current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to every client. By default this overrides any client-specific overrides.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -31,11 +31,33 @@ internal sealed partial class PvsSystem
|
||||
/// </summary>
|
||||
internal void ProcessQueuedAcks()
|
||||
{
|
||||
var opts = new ParallelOptions {MaxDegreeOfParallelism = _parallelManager.ParallelProcessCount};
|
||||
Parallel.ForEach(PendingAcks, opts, ProcessQueuedAck);
|
||||
if (PendingAcks.Count == 0)
|
||||
return;
|
||||
|
||||
_toAck.Clear();
|
||||
|
||||
foreach (var session in PendingAcks)
|
||||
{
|
||||
_toAck.Add(session);
|
||||
}
|
||||
|
||||
_parallelManager.ProcessNow(_ackJob, _toAck.Count);
|
||||
PendingAcks.Clear();
|
||||
}
|
||||
|
||||
private record struct PvsAckJob : IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => 2;
|
||||
|
||||
public PvsSystem System;
|
||||
public List<ICommonSession> Sessions;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
System.ProcessQueuedAck(Sessions[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a given client's queued ack.
|
||||
/// </summary>
|
||||
|
||||
@@ -61,6 +61,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
/// </summary>
|
||||
private float _viewSize;
|
||||
|
||||
/// <summary>
|
||||
/// Per-tick ack data to avoid re-allocating.
|
||||
/// </summary>
|
||||
private readonly List<ICommonSession> _toAck = new();
|
||||
private PvsAckJob _ackJob;
|
||||
|
||||
/// <summary>
|
||||
/// If PVS disabled then we'll track if we've dumped all entities on the player.
|
||||
/// This way any future ticks can be orders of magnitude faster as we only send what changes.
|
||||
@@ -121,6 +127,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_ackJob = new PvsAckJob()
|
||||
{
|
||||
System = this,
|
||||
Sessions = _toAck,
|
||||
};
|
||||
|
||||
_eyeQuery = GetEntityQuery<EyeComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
@@ -793,7 +805,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
|
||||
var expandEvent = new ExpandPvsEvent(session);
|
||||
RaiseLocalEvent(ref expandEvent);
|
||||
|
||||
if (session.AttachedEntity != null)
|
||||
RaiseLocalEvent(session.AttachedEntity.Value, ref expandEvent, true);
|
||||
else
|
||||
RaiseLocalEvent(ref expandEvent);
|
||||
|
||||
if (expandEvent.Entities != null)
|
||||
{
|
||||
foreach (var entityUid in expandEvent.Entities)
|
||||
|
||||
@@ -270,44 +270,26 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
|
||||
private PvsData? GetPVSData(ICommonSession[] players)
|
||||
{
|
||||
var chunks= _pvs.GetChunks(players, ref _playerChunks, ref _viewerEntities);
|
||||
const int ChunkBatchSize = 2;
|
||||
var chunks = _pvs.GetChunks(players, ref _playerChunks, ref _viewerEntities);
|
||||
var chunksCount = chunks.Count;
|
||||
var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize);
|
||||
var chunkCache =
|
||||
new (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[chunksCount];
|
||||
|
||||
// Update the reused trees sequentially to avoid having to lock the dictionary per chunk.
|
||||
var reuse = ArrayPool<bool>.Shared.Rent(chunksCount);
|
||||
|
||||
Parallel.For(0, chunkBatches,
|
||||
new ParallelOptions { MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount },
|
||||
i =>
|
||||
if (chunksCount > 0)
|
||||
{
|
||||
var chunkJob = new PvsChunkJob()
|
||||
{
|
||||
var start = i * ChunkBatchSize;
|
||||
var end = Math.Min(start + ChunkBatchSize, chunksCount);
|
||||
EntManager = _entityManager,
|
||||
Pvs = _pvs,
|
||||
ChunkCache = chunkCache,
|
||||
Reuse = reuse,
|
||||
Chunks = chunks,
|
||||
};
|
||||
|
||||
for (var j = start; j < end; ++j)
|
||||
{
|
||||
var (visMask, chunkIndexLocation) = chunks[j];
|
||||
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, out var chunk);
|
||||
chunkCache[j] = chunk;
|
||||
|
||||
#if DEBUG
|
||||
if (chunk == null)
|
||||
continue;
|
||||
|
||||
// Each root nodes should simply be a map or a grid entity.
|
||||
DebugTools.Assert(chunk.Value.tree.RootNodes.Count == 1,
|
||||
$"Root node count is {chunk.Value.tree.RootNodes.Count} instead of 1.");
|
||||
var nent = chunk.Value.tree.RootNodes.FirstOrDefault();
|
||||
var ent = _entityManager.GetEntity(nent);
|
||||
DebugTools.Assert(_entityManager.EntityExists(ent), $"Root node does not exist. Node {ent}.");
|
||||
DebugTools.Assert(_entityManager.HasComponent<MapComponent>(ent)
|
||||
|| _entityManager.HasComponent<MapGridComponent>(ent));
|
||||
#endif
|
||||
}
|
||||
});
|
||||
_parallelMgr.ProcessNow(chunkJob, chunksCount);
|
||||
}
|
||||
|
||||
_pvs.RegisterNewPreviousChunkTrees(chunks, chunkCache, reuse);
|
||||
ArrayPool<bool>.Shared.Return(reuse);
|
||||
@@ -438,5 +420,46 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
/// <summary>
|
||||
/// Pre-calculates chunk indices (Robust Tree) to be re-used per-player later on.
|
||||
/// </summary>
|
||||
private record struct PvsChunkJob : IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => 2;
|
||||
|
||||
|
||||
public IEntityManager EntManager;
|
||||
public PvsSystem Pvs;
|
||||
|
||||
public List<(int, IChunkIndexLocation)> Chunks;
|
||||
public bool[] Reuse;
|
||||
public (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] ChunkCache;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var (visMask, chunkIndexLocation) = Chunks[index];
|
||||
Reuse[index] = Pvs.TryCalculateChunk(chunkIndexLocation, visMask, out var chunk);
|
||||
ChunkCache[index] = chunk;
|
||||
|
||||
#if DEBUG
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
// Each root nodes should simply be a map or a grid entity.
|
||||
DebugTools.Assert(chunk.Value.tree.RootNodes.Count == 1,
|
||||
$"Root node count is {chunk.Value.tree.RootNodes.Count} instead of 1.");
|
||||
var nent = chunk.Value.tree.RootNodes.FirstOrDefault();
|
||||
var ent = EntManager.GetEntity(nent);
|
||||
DebugTools.Assert(EntManager.EntityExists(ent), $"Root node does not exist. Node {ent}.");
|
||||
DebugTools.Assert(EntManager.HasComponent<MapComponent>(ent)
|
||||
|| EntManager.HasComponent<MapGridComponent>(ent));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@ namespace Robust.Shared.Audio.Components;
|
||||
/// <summary>
|
||||
/// Stores the audio data for an audio entity.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedAudioSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedAudioSystem))]
|
||||
public sealed partial class AudioComponent : Component, IAudioSource
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField, DataField, Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
public AudioFlags Flags = AudioFlags.None;
|
||||
|
||||
#region Filter
|
||||
|
||||
public override bool SessionSpecific => true;
|
||||
@@ -28,8 +31,6 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
[DataField(customTypeSerializer:typeof(TimeOffsetSerializer)), AutoNetworkedField]
|
||||
public TimeSpan AudioStart;
|
||||
|
||||
#region Filters
|
||||
|
||||
// Don't need to network these as client doesn't care.
|
||||
|
||||
/// <summary>
|
||||
@@ -47,8 +48,6 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
// We can't just start playing on audio creation as we don't have the correct position yet.
|
||||
// As such we'll wait for FrameUpdate before we start playing to avoid the position being cooked.
|
||||
public bool Started = false;
|
||||
@@ -68,7 +67,7 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
/// Audio source that interacts with OpenAL.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
internal IAudioSource Source = default!;
|
||||
internal IAudioSource Source = new DummyAudioSource();
|
||||
|
||||
/// <summary>
|
||||
/// Auxiliary entity to pass audio to.
|
||||
@@ -115,6 +114,7 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
/// <see cref="IAudioSource.Global"/>
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[Access(typeof(SharedAudioSystem))]
|
||||
public bool Global { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -234,3 +234,14 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
Source.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AudioFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Should the audio act as if attached to a grid?
|
||||
/// </summary>
|
||||
GridAudio = 1 << 0,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
@@ -41,7 +43,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
public float ZOffset;
|
||||
public virtual float ZOffset { get; protected set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -59,23 +61,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
CfgManager.UnsubValueChanged(CVars.AudioZOffset, SetZOffset);
|
||||
}
|
||||
|
||||
protected virtual void SetZOffset(float value)
|
||||
protected void SetZOffset(float value)
|
||||
{
|
||||
var query = AllEntityQuery<AudioComponent>();
|
||||
var oldZOffset = ZOffset;
|
||||
ZOffset = value;
|
||||
|
||||
while (query.MoveNext(out var uid, out var audio))
|
||||
{
|
||||
// Pythagoras back to normal then adjust.
|
||||
var maxDistance = MathF.Pow(audio.Params.MaxDistance, 2) - oldZOffset;
|
||||
var refDistance = MathF.Pow(audio.Params.ReferenceDistance, 2) - oldZOffset;
|
||||
|
||||
audio.Params.MaxDistance = maxDistance;
|
||||
audio.Params.ReferenceDistance = refDistance;
|
||||
audio.Params = GetAdjustedParams(audio.Params);
|
||||
Dirty(uid, audio);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnAudioUnpaused(EntityUid uid, AudioComponent component, ref EntityUnpausedEvent args)
|
||||
@@ -87,8 +75,13 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
var playerEnt = args.Player?.AttachedEntity;
|
||||
|
||||
if ((component.ExcludedEntity != null && playerEnt == component.ExcludedEntity) ||
|
||||
(playerEnt != null && component.IncludedEntities != null && !component.IncludedEntities.Contains(playerEnt.Value)))
|
||||
if (component.ExcludedEntity != null && playerEnt == component.ExcludedEntity)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerEnt != null && component.IncludedEntities != null && !component.IncludedEntities.Contains(playerEnt.Value))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
@@ -134,9 +127,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
DebugTools.Assert(!string.IsNullOrEmpty(fileName));
|
||||
audioParams ??= AudioParams.Default;
|
||||
var comp = AddComp<Components.AudioComponent>(uid);
|
||||
var comp = AddComp<AudioComponent>(uid);
|
||||
comp.FileName = fileName;
|
||||
comp.Params = GetAdjustedParams(audioParams.Value);
|
||||
comp.Params = audioParams.Value;
|
||||
comp.AudioStart = Timing.CurTime;
|
||||
|
||||
if (!audioParams.Value.Loop)
|
||||
@@ -151,23 +144,32 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
return comp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accounts for ZOffset on audio distance.
|
||||
/// </summary>
|
||||
private AudioParams GetAdjustedParams(AudioParams audioParams)
|
||||
public static float GainToVolume(float value)
|
||||
{
|
||||
var maxDistance = GetAudioDistance(audioParams.MaxDistance);
|
||||
var refDistance = GetAudioDistance(audioParams.ReferenceDistance);
|
||||
return 10f * MathF.Log10(value);
|
||||
}
|
||||
|
||||
return audioParams
|
||||
.WithMaxDistance(maxDistance)
|
||||
.WithReferenceDistance(refDistance);
|
||||
public static float VolumeToGain(float value)
|
||||
{
|
||||
return MathF.Pow(10, value / 10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the audio params volume for an entity.
|
||||
/// </summary>
|
||||
public void SetVolume(EntityUid? entity, float value, Components.AudioComponent? component = null)
|
||||
public void SetGain(EntityUid? entity, float value, AudioComponent? component = null)
|
||||
{
|
||||
if (entity == null || !Resolve(entity.Value, ref component))
|
||||
return;
|
||||
|
||||
var volume = GainToVolume(value);
|
||||
SetVolume(entity, volume, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the audio params volume for an entity.
|
||||
/// </summary>
|
||||
public void SetVolume(EntityUid? entity, float value, AudioComponent? component = null)
|
||||
{
|
||||
if (entity == null || !Resolve(entity.Value, ref component))
|
||||
return;
|
||||
@@ -176,6 +178,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
return;
|
||||
|
||||
component.Params.Volume = value;
|
||||
component.Volume = value;
|
||||
Dirty(entity.Value, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -1030,7 +1030,7 @@ namespace Robust.Shared
|
||||
/// Master volume for audio output.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AudioMasterVolume =
|
||||
CVarDef.Create("audio.mastervolume", 1.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
CVarDef.Create("audio.mastervolume", 0.50f, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum raycast distance for audio occlusion.
|
||||
@@ -1302,16 +1302,7 @@ 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);
|
||||
CVarDef.Create("midi.volume", 0.50f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* HUB
|
||||
|
||||
46
Robust.Shared/GameObjects/RobustMemoryManager.cs
Normal file
46
Robust.Shared/GameObjects/RobustMemoryManager.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.IO;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Generic memory manager for engine use.
|
||||
/// </summary>
|
||||
internal sealed class RobustMemoryManager
|
||||
{
|
||||
// Let's be real this is a bandaid for pooling bullshit at an engine level and I don't know what
|
||||
// good memory management looks like for PVS or the RobustSerializer.
|
||||
|
||||
private static readonly RecyclableMemoryStreamManager MemStreamManager = new()
|
||||
{
|
||||
ThrowExceptionOnToArray = true,
|
||||
};
|
||||
|
||||
public RobustMemoryManager()
|
||||
{
|
||||
MemStreamManager.StreamDoubleDisposed += (sender, args) =>
|
||||
throw new InvalidOperationException("Found double disposed stream.");
|
||||
|
||||
MemStreamManager.StreamFinalized += (sender, args) =>
|
||||
throw new InvalidOperationException("Stream finalized but not disposed indicating a leak");
|
||||
|
||||
MemStreamManager.StreamOverCapacity += (sender, args) =>
|
||||
throw new InvalidOperationException("Stream over memory capacity");
|
||||
}
|
||||
|
||||
public static MemoryStream GetMemoryStream()
|
||||
{
|
||||
var stream = MemStreamManager.GetStream("RobustMemoryManager");
|
||||
DebugTools.Assert(stream.Position == 0);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static MemoryStream GetMemoryStream(int length)
|
||||
{
|
||||
var stream = MemStreamManager.GetStream("RobustMemoryManager", length);
|
||||
DebugTools.Assert(stream.Position == 0);
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
@@ -53,7 +53,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
@@ -63,7 +63,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
state.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
@@ -73,7 +73,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
state.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -124,7 +124,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -139,7 +139,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -154,7 +154,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
return state.found;
|
||||
@@ -270,7 +270,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
});
|
||||
}, approx: true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -314,7 +314,7 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, approx: true);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
@@ -345,7 +345,7 @@ public sealed partial class EntityLookupSystem
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, approx: true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -373,7 +373,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
tuple.lookup.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.worldBounds, tuple.flags);
|
||||
return true;
|
||||
});
|
||||
}, approx: true);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
@@ -411,7 +411,7 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, approx: true);
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapID);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
|
||||
@@ -446,7 +446,7 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, approx: true);
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
|
||||
@@ -464,6 +464,17 @@ public sealed partial class EntityLookupSystem
|
||||
return intersecting;
|
||||
}
|
||||
|
||||
public void GetEntitiesInRange(EntityUid uid, float range, HashSet<EntityUid> entities, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var mapPos = _transform.GetMapCoordinates(uid);
|
||||
|
||||
if (mapPos.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
GetEntitiesInRange(mapPos.MapId, mapPos.Position, range, entities, flags);
|
||||
entities.Remove(uid);
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
@@ -635,7 +646,7 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
@@ -650,7 +661,7 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
@@ -741,7 +752,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
tuple.callback(uid, tuple._broadQuery.GetComponent(uid));
|
||||
return true;
|
||||
});
|
||||
}, approx: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.intersecting.Add((value.Entity, comp));
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & (LookupFlags.Static)) != 0x0)
|
||||
@@ -52,7 +52,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.intersecting.Add((value.Entity, comp));
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
@@ -64,7 +64,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.intersecting.Add((value, comp));
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
@@ -76,7 +76,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.intersecting.Add((value, comp));
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
state.Intersecting.Add((value.Entity, comp));
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & (LookupFlags.Static)) != 0x0)
|
||||
@@ -144,7 +144,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
state.Intersecting.Add((value.Entity, comp));
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
@@ -184,7 +184,7 @@ public sealed partial class EntityLookupSystem
|
||||
state.Intersecting.Add((value, comp));
|
||||
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
@@ -225,7 +225,7 @@ public sealed partial class EntityLookupSystem
|
||||
state.Intersecting.Add((value, comp));
|
||||
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -267,7 +267,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -283,7 +283,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
@@ -299,7 +299,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, true);
|
||||
}
|
||||
|
||||
return state.found;
|
||||
@@ -439,7 +439,7 @@ public sealed partial class EntityLookupSystem
|
||||
return true;
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, approx: true);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
@@ -489,7 +489,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
tuple.system.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.worldAABB, tuple.flags, tuple.query);
|
||||
return true;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, approx: true);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
@@ -529,7 +529,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
tuple.system.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.worldAABB, tuple.flags, tuple.query);
|
||||
return true;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, approx: true);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
@@ -609,7 +609,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, state.WorldAABB, state.Flags, state.Query);
|
||||
return true;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, approx: true);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
@@ -684,7 +684,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
tuple.system.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.shape, tuple.worldAABB, tuple.flags, tuple.query);
|
||||
return true;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, approx: true);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -17,7 +18,8 @@ namespace Robust.Shared.Network.Messages
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
int length = buffer.ReadVariableInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Text = serializer.Deserialize<FormattedMessage>(stream);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
case EntityMessageType.SystemMessage:
|
||||
{
|
||||
int length = buffer.ReadVariableInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
var length = buffer.ReadVariableInt32();
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
SystemMessage = serializer.Deserialize<EntityEventArgs>(stream);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -28,7 +29,8 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
buffer.ReadPadBits();
|
||||
var length = buffer.ReadVariableInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
serializer.DeserializeDirect(stream, out Echo);
|
||||
serializer.DeserializeDirect(stream, out Response);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -38,29 +37,30 @@ namespace Robust.Shared.Network.Messages
|
||||
// State is compressed.
|
||||
if (compressedLength > 0)
|
||||
{
|
||||
var stream = buffer.ReadAlignedMemory(compressedLength);
|
||||
var stream = RobustMemoryManager.GetMemoryStream(compressedLength);
|
||||
buffer.ReadAlignedMemory(stream, compressedLength);
|
||||
|
||||
using var decompressStream = new ZStdDecompressStream(stream);
|
||||
var decompressedStream = new MemoryStream(uncompressedLength);
|
||||
decompressStream.CopyTo(decompressedStream, uncompressedLength);
|
||||
decompressedStream.Position = 0;
|
||||
finalStream = decompressedStream;
|
||||
finalStream = RobustMemoryManager.GetMemoryStream(uncompressedLength);
|
||||
finalStream.SetLength(uncompressedLength);
|
||||
decompressStream.CopyTo(finalStream, uncompressedLength);
|
||||
finalStream.Position = 0;
|
||||
}
|
||||
// State is uncompressed.
|
||||
else
|
||||
{
|
||||
var stream = buffer.ReadAlignedMemory(uncompressedLength);
|
||||
finalStream = stream;
|
||||
finalStream = RobustMemoryManager.GetMemoryStream(uncompressedLength);
|
||||
buffer.ReadAlignedMemory(finalStream, uncompressedLength);
|
||||
}
|
||||
|
||||
serializer.DeserializeDirect(finalStream, out State);
|
||||
finalStream.Dispose();
|
||||
|
||||
State.PayloadSize = uncompressedLength;
|
||||
finalStream.Dispose();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
var stateStream = new MemoryStream();
|
||||
using var stateStream = RobustMemoryManager.GetMemoryStream();
|
||||
serializer.SerializeDirect(stateStream, State);
|
||||
buffer.WriteVariableInt32((int)stateStream.Length);
|
||||
|
||||
@@ -87,7 +87,6 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
// 0 means that the state isn't compressed.
|
||||
buffer.WriteVariableInt32(0);
|
||||
|
||||
buffer.Write(stateStream.AsSpan());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -42,12 +43,14 @@ namespace Robust.Shared.Network.Messages
|
||||
SessionId = buffer.ReadUInt32();
|
||||
{
|
||||
var length = buffer.ReadInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
PropertyIndex = serializer.Deserialize<object[]>(stream);
|
||||
}
|
||||
{
|
||||
var length = buffer.ReadInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Value = serializer.Deserialize(stream);
|
||||
}
|
||||
ReinterpretValue = buffer.ReadBoolean();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -31,7 +32,8 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
RequestId = buffer.ReadUInt32();
|
||||
var length = buffer.ReadInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Blob = serializer.Deserialize<ViewVariablesBlob>(stream);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -37,7 +38,8 @@ namespace Robust.Shared.Network.Messages
|
||||
RequestId = buffer.ReadUInt32();
|
||||
SessionId = buffer.ReadUInt32();
|
||||
var length = buffer.ReadInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
RequestMeta = serializer.Deserialize<ViewVariablesRequest>(stream);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -32,7 +33,8 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
RequestId = buffer.ReadUInt32();
|
||||
var length = buffer.ReadInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Selector = serializer.Deserialize<ViewVariablesObjectSelector>(stream);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
{
|
||||
@@ -96,16 +97,17 @@ namespace Robust.Shared.Network
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown if the current read position of the message is not byte-aligned.
|
||||
/// </exception>
|
||||
public static MemoryStream ReadAlignedMemory(this NetIncomingMessage message, int length)
|
||||
public static void ReadAlignedMemory(this NetIncomingMessage message, MemoryStream memStream, int length)
|
||||
{
|
||||
if ((message.Position & 7) != 0)
|
||||
{
|
||||
throw new ArgumentException("Read position in message must be byte-aligned", nameof(message));
|
||||
}
|
||||
|
||||
var stream = new MemoryStream(message.Data, message.PositionInBytes, length, false);
|
||||
DebugTools.Assert(memStream.Position == 0);
|
||||
memStream.Write(message.Data, message.PositionInBytes, length);
|
||||
memStream.Position = 0;
|
||||
message.Position += length * 8;
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static TimeSpan ReadTimeSpan(this NetIncomingMessage message)
|
||||
|
||||
@@ -355,21 +355,27 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
internal enum ContactFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Is the contact pending its first manifold generation.
|
||||
/// </summary>
|
||||
PreInit = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Has this contact already been added to an island?
|
||||
/// </summary>
|
||||
Island = 1 << 0,
|
||||
Island = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Does this contact need re-filtering?
|
||||
/// </summary>
|
||||
Filter = 1 << 1,
|
||||
Filter = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Is this a special contact for grid-grid collisions
|
||||
/// </summary>
|
||||
Grid = 1 << 2,
|
||||
Grid = 1 << 3,
|
||||
|
||||
Deleting = 1 << 3,
|
||||
Deleting = 1 << 4,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
[ByRefEvent]
|
||||
public readonly record struct PhysicsWakeEvent(EntityUid Entity, PhysicsComponent Body);
|
||||
namespace Robust.Shared.Physics;
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly record struct PhysicsSleepEvent(EntityUid Entity, PhysicsComponent Body);
|
||||
}
|
||||
[ByRefEvent]
|
||||
public readonly record struct PhysicsWakeEvent(EntityUid Entity, PhysicsComponent Body);
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct PhysicsSleepEvent(EntityUid Entity, PhysicsComponent Body)
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the entity as still being awake and cancels sleeping.
|
||||
/// </summary>
|
||||
public bool Cancelled;
|
||||
};
|
||||
|
||||
@@ -48,15 +48,22 @@ namespace Robust.Shared.Physics.Systems
|
||||
/// </summary>
|
||||
private float _broadphaseExpand;
|
||||
|
||||
private const int PairBufferParallel = 8;
|
||||
|
||||
private ObjectPool<List<FixtureProxy>> _bufferPool =
|
||||
new DefaultObjectPool<List<FixtureProxy>>(new ListPolicy<FixtureProxy>(), 2048);
|
||||
|
||||
private BroadphaseJob _broadphaseJob;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_broadphaseJob = new BroadphaseJob()
|
||||
{
|
||||
Broadphase = this,
|
||||
BroadphaseExpand = _broadphaseExpand,
|
||||
MapManager = _mapManager,
|
||||
};
|
||||
|
||||
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
@@ -188,50 +195,16 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
foreach (var (proxy, aabb) in moveBuffer)
|
||||
{
|
||||
// TODO: This should just be done inline in the job.
|
||||
contactBuffer[idx] = _bufferPool.Get();
|
||||
pMoveBuffer[idx++] = (proxy, aabb);
|
||||
}
|
||||
|
||||
var options = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = _parallel.ParallelProcessCount,
|
||||
};
|
||||
_broadphaseJob.ContactBuffer = contactBuffer;
|
||||
_broadphaseJob.PMoveBuffer = pMoveBuffer;
|
||||
_broadphaseJob.MapId = mapId;
|
||||
|
||||
var batches = (int)MathF.Ceiling((float) count / PairBufferParallel);
|
||||
|
||||
Parallel.For(0, batches, options, i =>
|
||||
{
|
||||
var start = i * PairBufferParallel;
|
||||
var end = Math.Min(start + PairBufferParallel, count);
|
||||
|
||||
for (var j = start; j < end; j++)
|
||||
{
|
||||
var (proxy, worldAABB) = pMoveBuffer[j];
|
||||
var buffer = contactBuffer[j];
|
||||
|
||||
var proxyBody = proxy.Body;
|
||||
DebugTools.Assert(!proxyBody.Deleted);
|
||||
|
||||
var state = (this, proxy, worldAABB, buffer);
|
||||
|
||||
// Get every broadphase we may be intersecting.
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref (
|
||||
SharedBroadphaseSystem system,
|
||||
FixtureProxy proxy,
|
||||
Box2 worldAABB,
|
||||
List<FixtureProxy> pairBuffer) tuple) =>
|
||||
{
|
||||
ref var buffer = ref tuple.pairBuffer;
|
||||
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Struct ref moment, I have no idea what's fastest.
|
||||
buffer = state.buffer;
|
||||
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer);
|
||||
}
|
||||
});
|
||||
_parallel.ProcessNow(_broadphaseJob, count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
@@ -432,6 +405,13 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
public void RegenerateContacts(EntityUid uid, PhysicsComponent body, FixturesComponent? fixtures = null, TransformComponent? xform = null)
|
||||
{
|
||||
// If it can't collide then we can't touch proxies and add them to the movebuffer anyway.
|
||||
if (!body.CanCollide)
|
||||
{
|
||||
// Sleep body may still have contacts around.
|
||||
return;
|
||||
}
|
||||
|
||||
_physicsSystem.DestroyContacts(body);
|
||||
if (!Resolve(uid, ref xform, ref fixtures))
|
||||
return;
|
||||
@@ -514,5 +494,50 @@ namespace Robust.Shared.Physics.Systems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
private record struct BroadphaseJob : IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => 8;
|
||||
|
||||
public IMapManager MapManager;
|
||||
public SharedBroadphaseSystem Broadphase;
|
||||
|
||||
public MapId MapId;
|
||||
public float BroadphaseExpand;
|
||||
public List<FixtureProxy>[] ContactBuffer;
|
||||
public (FixtureProxy Proxy, Box2 AABB)[] PMoveBuffer;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var (proxy, worldAABB) = PMoveBuffer[index];
|
||||
var buffer = ContactBuffer[index];
|
||||
|
||||
var proxyBody = proxy.Body;
|
||||
DebugTools.Assert(!proxyBody.Deleted);
|
||||
|
||||
var state = (Broadphase, proxy, worldAABB, buffer);
|
||||
|
||||
// Get every broadphase we may be intersecting.
|
||||
MapManager.FindGridsIntersecting(MapId, worldAABB.Enlarged(BroadphaseExpand), ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref (
|
||||
SharedBroadphaseSystem system,
|
||||
FixtureProxy proxy,
|
||||
Box2 worldAABB,
|
||||
List<FixtureProxy> pairBuffer) tuple) =>
|
||||
{
|
||||
ref var buffer = ref tuple.pairBuffer;
|
||||
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
|
||||
return true;
|
||||
}, approx: true, includeMap: false);
|
||||
|
||||
// Struct ref moment, I have no idea what's fastest.
|
||||
buffer = state.buffer;
|
||||
Broadphase.FindPairs(proxy, worldAABB, MapManager.GetMapEntityId(MapId), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,12 +399,30 @@ public partial class SharedPhysicsSystem
|
||||
{
|
||||
var ev = new PhysicsSleepEvent(uid, body);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
// Reset the sleep timer.
|
||||
if (ev.Cancelled)
|
||||
{
|
||||
if (updateSleepTime)
|
||||
SetSleepTime(body, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ResetDynamics(body);
|
||||
}
|
||||
|
||||
if (updateSleepTime)
|
||||
SetSleepTime(body, 0);
|
||||
|
||||
if (body.Awake != value)
|
||||
{
|
||||
Log.Error($"Found a corrupted physics awake state for {ToPrettyString(ent)}! Did you forget to cancel the sleep subscription? Forcing body awake");
|
||||
DebugTools.Assert(false);
|
||||
body.Awake = true;
|
||||
}
|
||||
|
||||
UpdateMapAwakeState(uid, body);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,17 +31,15 @@ using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.JavaScript;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Systems;
|
||||
@@ -122,6 +120,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
public bool Return(Contact obj)
|
||||
{
|
||||
SetContact(obj,
|
||||
false,
|
||||
EntityUid.Invalid, EntityUid.Invalid,
|
||||
string.Empty, string.Empty,
|
||||
null, 0,
|
||||
@@ -132,6 +131,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
private static void SetContact(Contact contact,
|
||||
bool enabled,
|
||||
EntityUid uidA, EntityUid uidB,
|
||||
string fixtureAId, string fixtureBId,
|
||||
Fixture? fixtureA, int indexA,
|
||||
@@ -139,9 +139,9 @@ public abstract partial class SharedPhysicsSystem
|
||||
PhysicsComponent? bodyA,
|
||||
PhysicsComponent? bodyB)
|
||||
{
|
||||
contact.Enabled = true;
|
||||
contact.Enabled = enabled;
|
||||
contact.IsTouching = false;
|
||||
contact.Flags = ContactFlags.None;
|
||||
contact.Flags = ContactFlags.None | ContactFlags.PreInit;
|
||||
// TOIFlag = false;
|
||||
|
||||
contact.EntityA = uidA;
|
||||
@@ -229,11 +229,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
// Edge+Polygon is non-symmetrical due to the way Erin handles collision type registration.
|
||||
if ((type1 >= type2 || (type1 == ShapeType.Edge && type2 == ShapeType.Polygon)) && !(type2 == ShapeType.Edge && type1 == ShapeType.Polygon))
|
||||
{
|
||||
SetContact(contact, uidA, uidB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB, bodyA, bodyB);
|
||||
SetContact(contact, true, uidA, uidB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB, bodyA, bodyB);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetContact(contact, uidB, uidA, fixtureBId, fixtureAId, fixtureB, indexB, fixtureA, indexA, bodyB, bodyA);
|
||||
SetContact(contact, true, uidB, uidA, fixtureBId, fixtureAId, fixtureB, indexB, fixtureA, indexA, bodyB, bodyA);
|
||||
}
|
||||
|
||||
contact.Type = _registers[(int)type1, (int)type2];
|
||||
@@ -376,6 +376,12 @@ public abstract partial class SharedPhysicsSystem
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
// It's possible the contact was destroyed by content in which case we just skip it.
|
||||
if (!contact.Enabled)
|
||||
continue;
|
||||
|
||||
// No longer pre-init and can be used in the solver.
|
||||
contact.Flags &= ~ContactFlags.PreInit;
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
Fixture fixtureB = contact.FixtureB!;
|
||||
int indexA = contact.ChildIndexA;
|
||||
@@ -504,6 +510,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
Log.Error($"Insufficient contact length at 429! Index {index} and length is {contacts.Length}. Tell Sloth");
|
||||
}
|
||||
|
||||
contacts[index++] = contact;
|
||||
}
|
||||
|
||||
@@ -524,6 +531,12 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
var contact = contacts[i];
|
||||
|
||||
// It's possible the contact was disabled above if DestroyContact lead to even more being destroyed.
|
||||
if (!contact.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (status[i])
|
||||
{
|
||||
case ContactStatus.StartTouching:
|
||||
@@ -582,24 +595,19 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status, Vector2[] worldPoints)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
var wake = ArrayPool<bool>.Shared.Rent(count);
|
||||
|
||||
if (count > ContactsPerThread * 2)
|
||||
_parallel.ProcessNow(new ManifoldsJob()
|
||||
{
|
||||
var batches = (int) Math.Ceiling((float) count / ContactsPerThread);
|
||||
|
||||
Parallel.For(0, batches, i =>
|
||||
{
|
||||
var start = i * ContactsPerThread;
|
||||
var end = Math.Min(start + ContactsPerThread, count);
|
||||
UpdateContacts(contacts, start, end, status, wake, worldPoints);
|
||||
});
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateContacts(contacts, 0, count, status, wake, worldPoints);
|
||||
}
|
||||
Physics = this,
|
||||
Status = status,
|
||||
WorldPoints = worldPoints,
|
||||
Contacts = contacts,
|
||||
Wake = wake,
|
||||
}, count);
|
||||
|
||||
// Can't do this during UpdateContacts due to IoC threading issues.
|
||||
for (var i = 0; i < count; i++)
|
||||
@@ -620,35 +628,49 @@ public abstract partial class SharedPhysicsSystem
|
||||
ArrayPool<bool>.Shared.Return(wake);
|
||||
}
|
||||
|
||||
private void UpdateContacts(Contact[] contacts, int start, int end, ContactStatus[] status, bool[] wake, Vector2[] worldPoints)
|
||||
private record struct ManifoldsJob : IParallelRobustJob
|
||||
{
|
||||
for (var i = start; i < end; i++)
|
||||
public int BatchSize => ContactsPerThread;
|
||||
|
||||
public SharedPhysicsSystem Physics;
|
||||
|
||||
public Contact[] Contacts;
|
||||
public ContactStatus[] Status;
|
||||
public Vector2[] WorldPoints;
|
||||
public bool[] Wake;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var contact = contacts[i];
|
||||
Physics.UpdateContact(Contacts, index, Status, Wake, WorldPoints);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Temporary measure. When Box2D 3.0 comes out expect a major refactor
|
||||
// of everything
|
||||
if (contact.FixtureA == null || contact.FixtureB == null)
|
||||
{
|
||||
Log.Error($"Found a null contact for contact at {i}");
|
||||
status[i] = ContactStatus.NoContact;
|
||||
wake[i] = false;
|
||||
DebugTools.Assert(false);
|
||||
continue;
|
||||
}
|
||||
private void UpdateContact(Contact[] contacts, int index, ContactStatus[] status, bool[] wake, Vector2[] worldPoints)
|
||||
{
|
||||
var contact = contacts[index];
|
||||
|
||||
var uidA = contact.EntityA;
|
||||
var uidB = contact.EntityB;
|
||||
var bodyATransform = GetPhysicsTransform(uidA, Transform(uidA));
|
||||
var bodyBTransform = GetPhysicsTransform(uidB, Transform(uidB));
|
||||
// TODO: Temporary measure. When Box2D 3.0 comes out expect a major refactor
|
||||
// of everything
|
||||
// It's okay past sloth it can't hurt you anymore.
|
||||
// This can happen if DestroyContact is called and content deletes contacts that were already processed.
|
||||
if (!contact.Enabled)
|
||||
{
|
||||
status[index] = ContactStatus.NoContact;
|
||||
wake[index] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var contactStatus = contact.Update(bodyATransform, bodyBTransform, out wake[i]);
|
||||
status[i] = contactStatus;
|
||||
var uidA = contact.EntityA;
|
||||
var uidB = contact.EntityB;
|
||||
var bodyATransform = GetPhysicsTransform(uidA);
|
||||
var bodyBTransform = GetPhysicsTransform(uidB);
|
||||
|
||||
if (contactStatus == ContactStatus.StartTouching)
|
||||
{
|
||||
worldPoints[i] = Physics.Transform.Mul(bodyATransform, contacts[i].Manifold.LocalPoint);
|
||||
}
|
||||
var contactStatus = contact.Update(bodyATransform, bodyBTransform, out wake[index]);
|
||||
status[index] = contactStatus;
|
||||
|
||||
if (contactStatus == ContactStatus.StartTouching)
|
||||
{
|
||||
worldPoints[index] = Physics.Transform.Mul(bodyATransform, contacts[index].Manifold.LocalPoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -385,8 +385,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
// Has this contact already been added to an island?
|
||||
if ((contact.Flags & ContactFlags.Island) != 0x0) continue;
|
||||
// Has this contact already been added to an island / is it pre-init?
|
||||
if ((contact.Flags & (ContactFlags.Island | ContactFlags.PreInit)) != 0x0) continue;
|
||||
|
||||
// Is this contact solid and touching?
|
||||
if (!contact.Enabled || !contact.IsTouching) continue;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -15,13 +16,18 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// <summary>
|
||||
/// Gets the linear velocity of a particular body at the specified point.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
[PublicAPI]
|
||||
public Vector2 GetLinearVelocity(
|
||||
EntityUid uid,
|
||||
Vector2 point,
|
||||
PhysicsComponent? component = null,
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, ref xform))
|
||||
if (!PhysicsQuery.Resolve(uid, ref component))
|
||||
return Vector2.Zero;
|
||||
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return Vector2.Zero;
|
||||
|
||||
var velocity = component.LinearVelocity;
|
||||
@@ -29,6 +35,43 @@ public abstract partial class SharedPhysicsSystem
|
||||
return velocity + angVelocity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the total rate of change of the coordinate's map position.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
[PublicAPI]
|
||||
public Vector2 GetMapLinearVelocity(EntityCoordinates coordinates)
|
||||
{
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return Vector2.Zero;
|
||||
|
||||
var mapUid = coordinates.GetMapUid(EntityManager);
|
||||
var parent = coordinates.EntityId;
|
||||
var localPos = coordinates.Position;
|
||||
|
||||
var velocity = Vector2.Zero;
|
||||
var angularComponent = Vector2.Zero;
|
||||
|
||||
while (parent != mapUid && parent.IsValid())
|
||||
{
|
||||
// Could make this a method with the below one but ehh
|
||||
// then you get a method bigger than this block with a billion out args and who wants that.
|
||||
var xform = _xformQuery.GetComponent(parent);
|
||||
|
||||
if (PhysicsQuery.TryGetComponent(parent, out var body))
|
||||
{
|
||||
velocity += body.LinearVelocity;
|
||||
angularComponent += Vector2Helpers.Cross(body.AngularVelocity, localPos - body.LocalCenter);
|
||||
angularComponent = xform.LocalRotation.RotateVec(angularComponent);
|
||||
}
|
||||
|
||||
localPos = xform.LocalPosition + xform.LocalRotation.RotateVec(localPos);
|
||||
parent = xform.ParentUid;
|
||||
}
|
||||
|
||||
return velocity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the total rate of change of the entity's map-position, resulting from the linear and angular
|
||||
/// velocities of this entity and any parents.
|
||||
@@ -36,17 +79,16 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// <remarks>
|
||||
/// Use <see cref="GetMapVelocities"/> if you need linear and angular at the same time.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[PublicAPI]
|
||||
public Vector2 GetMapLinearVelocity(
|
||||
EntityUid uid,
|
||||
PhysicsComponent? component = null,
|
||||
TransformComponent? xform = null,
|
||||
EntityQuery<TransformComponent>? xformQuery = null,
|
||||
EntityQuery<PhysicsComponent>? physicsQuery = null)
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
xformQuery ??= EntityManager.GetEntityQuery<TransformComponent>();
|
||||
physicsQuery ??= EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return Vector2.Zero;
|
||||
|
||||
xform ??= xformQuery.Value.GetComponent(uid);
|
||||
var parent = xform.ParentUid;
|
||||
var localPos = xform.LocalPosition;
|
||||
|
||||
@@ -55,9 +97,9 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
while (parent != xform.MapUid && parent.IsValid())
|
||||
{
|
||||
xform = xformQuery.Value.GetComponent(parent);
|
||||
xform = _xformQuery.GetComponent(parent);
|
||||
|
||||
if (physicsQuery.Value.TryGetComponent(parent, out var body))
|
||||
if (PhysicsQuery.TryGetComponent(parent, out var body))
|
||||
{
|
||||
// add linear velocity of parent relative to it's own parent (again, in map coordinates)
|
||||
velocity += body.LinearVelocity;
|
||||
@@ -82,48 +124,48 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// <remarks>
|
||||
/// Consider using <see cref="GetMapVelocities"/> if you need linear and angular at the same time.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[PublicAPI]
|
||||
public float GetMapAngularVelocity(
|
||||
EntityUid uid,
|
||||
PhysicsComponent? component = null,
|
||||
TransformComponent? xform = null,
|
||||
EntityQuery<TransformComponent>? xformQuery = null,
|
||||
EntityQuery<PhysicsComponent>? physicsQuery = null)
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!PhysicsQuery.Resolve(uid, ref component))
|
||||
return 0;
|
||||
|
||||
xformQuery ??= EntityManager.GetEntityQuery<TransformComponent>();
|
||||
physicsQuery ??= EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
|
||||
xform ??= xformQuery.Value.GetComponent(uid);
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return 0f;
|
||||
|
||||
var angularVelocity = component.AngularVelocity;
|
||||
|
||||
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
|
||||
{
|
||||
if (physicsQuery.Value.TryGetComponent(xform.ParentUid, out var body))
|
||||
if (PhysicsQuery.TryGetComponent(xform.ParentUid, out var body))
|
||||
angularVelocity += body.AngularVelocity;
|
||||
|
||||
xform = xformQuery.Value.GetComponent(xform.ParentUid);
|
||||
xform = _xformQuery.GetComponent(xform.ParentUid);
|
||||
}
|
||||
|
||||
return angularVelocity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the linear and angular velocity for this entity in map terms.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
[PublicAPI]
|
||||
public (Vector2, float) GetMapVelocities(
|
||||
EntityUid uid,
|
||||
PhysicsComponent? component = null,
|
||||
TransformComponent? xform = null,
|
||||
EntityQuery<TransformComponent>? xformQuery = null,
|
||||
EntityQuery<PhysicsComponent>? physicsQuery = null)
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!PhysicsQuery.Resolve(uid, ref component))
|
||||
return (Vector2.Zero, 0);
|
||||
|
||||
xformQuery ??= EntityManager.GetEntityQuery<TransformComponent>();
|
||||
physicsQuery ??= EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return (Vector2.Zero, 0);
|
||||
|
||||
xform ??= xformQuery.Value.GetComponent(uid);
|
||||
var parent = xform.ParentUid;
|
||||
|
||||
var localPos = xform.LocalPosition;
|
||||
@@ -134,9 +176,9 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
while (parent != xform.MapUid && parent.IsValid())
|
||||
{
|
||||
xform = xformQuery.Value.GetComponent(parent);
|
||||
xform = _xformQuery.GetComponent(parent);
|
||||
|
||||
if (physicsQuery.Value.TryGetComponent(parent, out var body))
|
||||
if (PhysicsQuery.TryGetComponent(parent, out var body))
|
||||
{
|
||||
angularVelocity += body.AngularVelocity;
|
||||
|
||||
@@ -180,7 +222,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
FixturesComponent? manager = null;
|
||||
|
||||
// for the new velocities (that need to be updated), we can just use the existing function:
|
||||
var (newLinear, newAngular) = GetMapVelocities(uid, physics, xform, xformQuery, physicsQuery);
|
||||
var (newLinear, newAngular) = GetMapVelocities(uid, physics, xform);
|
||||
|
||||
// for the old velocities, we need to re-implement this function while using the old parent and old local position:
|
||||
if (args.OldParent is not { Valid: true } parent)
|
||||
|
||||
@@ -75,8 +75,6 @@ namespace Robust.Shared.Physics.Systems
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
|
||||
SubscribeLocalEvent<PhysicsWakeEvent>(OnWake);
|
||||
SubscribeLocalEvent<PhysicsSleepEvent>(OnSleep);
|
||||
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
|
||||
SubscribeLocalEvent<PhysicsComponent, EntGotRemovedFromContainerMessage>(HandleContainerRemoved);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
@@ -254,26 +252,20 @@ namespace Robust.Shared.Physics.Systems
|
||||
_configManager.UnsubValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
}
|
||||
|
||||
private void OnWake(ref PhysicsWakeEvent @event)
|
||||
private void UpdateMapAwakeState(EntityUid uid, PhysicsComponent body)
|
||||
{
|
||||
var mapId = EntityManager.GetComponent<TransformComponent>(@event.Entity).MapID;
|
||||
|
||||
var mapId = EntityManager.GetComponent<TransformComponent>(uid).MapID;
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var tempQualifier = _mapManager.GetMapEntityId(mapId);
|
||||
AddAwakeBody(@event.Entity, @event.Body, tempQualifier);
|
||||
}
|
||||
|
||||
private void OnSleep(ref PhysicsSleepEvent @event)
|
||||
{
|
||||
var mapId = EntityManager.GetComponent<TransformComponent>(@event.Entity).MapID;
|
||||
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var tempQualifier = _mapManager.GetMapEntityId(mapId);
|
||||
RemoveSleepBody(@event.Entity, @event.Body, tempQualifier);
|
||||
if (body.Awake)
|
||||
{
|
||||
AddAwakeBody(uid, body, tempQualifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveSleepBody(uid, body, tempQualifier);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleContainerRemoved(EntityUid uid, PhysicsComponent physics, EntGotRemovedFromContainerMessage message)
|
||||
|
||||
@@ -523,35 +523,43 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
void ProcessItem(string id, InheritancePushDatum datum)
|
||||
{
|
||||
if (tree.TryGetParents(id, out var parents))
|
||||
try
|
||||
{
|
||||
var parentNodes = new MappingDataNode[parents.Length];
|
||||
for (var i = 0; i < parents.Length; i++)
|
||||
if (tree.TryGetParents(id, out var parents))
|
||||
{
|
||||
parentNodes[i] = results[parents[i]].Result;
|
||||
var parentNodes = new MappingDataNode[parents.Length];
|
||||
for (var i = 0; i < parents.Length; i++)
|
||||
{
|
||||
parentNodes[i] = results[parents[i]].Result;
|
||||
}
|
||||
|
||||
datum.Result = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
parentNodes,
|
||||
datum.Result);
|
||||
}
|
||||
|
||||
datum.Result = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
parentNodes,
|
||||
datum.Result);
|
||||
}
|
||||
|
||||
if (tree.TryGetChildren(id, out var children))
|
||||
{
|
||||
foreach (var child in children)
|
||||
if (tree.TryGetChildren(id, out var children))
|
||||
{
|
||||
var childDatum = results[child];
|
||||
var val = Interlocked.Decrement(ref childDatum.CountParentsRemaining);
|
||||
if (val == 0)
|
||||
foreach (var child in children)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(_ => { ProcessItem(child, childDatum); });
|
||||
var childDatum = results[child];
|
||||
var val = Interlocked.Decrement(ref childDatum.CountParentsRemaining);
|
||||
if (val == 0)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(_ => { ProcessItem(child, childDatum); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
countDown.Signal();
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
countDown.Signal();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Sawmill.Error($"Failed to push composition for {kind.Name} prototype with id: {id}. Exception: {e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
await WaitHandleHelpers.WaitOneAsync(countDown.WaitHandle);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.ILVerification" Version="6.0.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageReference Include="Nett" Version="0.15.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Pidgin" Version="2.5.0" />
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Robust.Shared
|
||||
deps.Register<IParallelManagerInternal, ParallelManager>();
|
||||
deps.Register<ToolshedManager>();
|
||||
deps.Register<HttpClientHolder>();
|
||||
deps.Register<RobustMemoryManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
Robust.Shared/Threading/IParallelRobustJob.cs
Normal file
16
Robust.Shared/Threading/IParallelRobustJob.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Robust.Shared.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// Runs the job with the specified batch size per thread; Execute is still called per index.
|
||||
/// </summary>
|
||||
public interface IParallelRobustJob
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum amount of batches required to engage in parallelism.
|
||||
/// </summary>
|
||||
int MinimumBatchParallel => 2;
|
||||
|
||||
int BatchSize => 1;
|
||||
|
||||
void Execute(int index);
|
||||
}
|
||||
10
Robust.Shared/Threading/IRobustJob.cs
Normal file
10
Robust.Shared/Threading/IRobustJob.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Robust.Shared.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// Implement for code that needs to be runnable on a threadpool.
|
||||
/// </summary>
|
||||
public interface IRobustJob : IThreadPoolWorkItem
|
||||
{
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Threading;
|
||||
|
||||
@@ -18,6 +16,27 @@ public interface IParallelManager
|
||||
/// Add the delegate to <see cref="ParallelCountChanged"/> and immediately invoke it.
|
||||
/// </summary>
|
||||
void AddAndInvokeParallelCountChanged(Action changed);
|
||||
|
||||
/// <summary>
|
||||
/// Takes in a job that gets flushed.
|
||||
/// </summary>
|
||||
/// <param name="job"></param>
|
||||
WaitHandle Process(IRobustJob job);
|
||||
|
||||
/// <summary>
|
||||
/// Takes in a parallel job and runs it the specified amount.
|
||||
/// </summary>
|
||||
void ProcessNow(IParallelRobustJob jobs, int amount);
|
||||
|
||||
/// <summary>
|
||||
/// Processes a robust job sequentially if desired.
|
||||
/// </summary>
|
||||
void ProcessSerialNow(IParallelRobustJob jobs, int amount);
|
||||
|
||||
/// <summary>
|
||||
/// Takes in a parallel job and runs it without blocking.
|
||||
/// </summary>
|
||||
WaitHandle Process(IParallelRobustJob jobs, int amount);
|
||||
}
|
||||
|
||||
internal interface IParallelManagerInternal : IParallelManager
|
||||
@@ -32,6 +51,21 @@ internal sealed class ParallelManager : IParallelManagerInternal
|
||||
public event Action? ParallelCountChanged;
|
||||
public int ParallelProcessCount { get; private set; }
|
||||
|
||||
// Without pooling it's hard to keep task allocations down for classes
|
||||
// This lets us avoid re-allocating the ManualResetEventSlims constantly when we just need a way to signal job completion.
|
||||
|
||||
private readonly ObjectPool<InternalJob> _jobPool =
|
||||
new DefaultObjectPool<InternalJob>(new DefaultPooledObjectPolicy<InternalJob>(), 256);
|
||||
|
||||
private readonly ObjectPool<InternalParallelJob> _parallelPool =
|
||||
new DefaultObjectPool<InternalParallelJob>(new DefaultPooledObjectPolicy<InternalParallelJob>(), 256);
|
||||
|
||||
/// <summary>
|
||||
/// Used internally for Parallel jobs, for external callers it gets garbage collected.
|
||||
/// </summary>
|
||||
private readonly ObjectPool<ParallelTracker> _trackerPool =
|
||||
new DefaultObjectPool<ParallelTracker>(new DefaultPooledObjectPolicy<ParallelTracker>());
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_cfg.OnValueChanged(CVars.ThreadParallelCount, UpdateCVar, true);
|
||||
@@ -43,13 +77,187 @@ internal sealed class ParallelManager : IParallelManagerInternal
|
||||
changed();
|
||||
}
|
||||
|
||||
private InternalJob GetJob(IRobustJob job)
|
||||
{
|
||||
var robustJob = _jobPool.Get();
|
||||
robustJob.Event.Reset();
|
||||
robustJob.Set(job, _jobPool);
|
||||
return robustJob;
|
||||
}
|
||||
|
||||
private InternalParallelJob GetParallelJob(IParallelRobustJob job, int start, int end, ParallelTracker tracker)
|
||||
{
|
||||
var internalJob = _parallelPool.Get();
|
||||
internalJob.Set(job, start, end, tracker, _parallelPool);
|
||||
return internalJob;
|
||||
}
|
||||
|
||||
private void UpdateCVar(int value)
|
||||
{
|
||||
var oldCount = ParallelProcessCount;
|
||||
ParallelProcessCount = value == 0 ? Environment.ProcessorCount : value;
|
||||
ThreadPool.GetAvailableThreads(out var oldWorker, out var oldCompletion);
|
||||
ParallelProcessCount = value == 0 ? oldWorker : value;
|
||||
|
||||
if (oldCount != ParallelProcessCount)
|
||||
{
|
||||
ParallelCountChanged?.Invoke();
|
||||
ThreadPool.SetMaxThreads(ParallelProcessCount, oldCompletion);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitHandle Process(IRobustJob job)
|
||||
{
|
||||
var subJob = GetJob(job);
|
||||
// From what I can tell preferLocal is more of a !forceGlobal flag.
|
||||
// Also UnsafeQueue should be fine as long as we don't use async locals.
|
||||
ThreadPool.UnsafeQueueUserWorkItem(subJob, true);
|
||||
return subJob.Event.WaitHandle;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ProcessNow(IParallelRobustJob job, int amount)
|
||||
{
|
||||
var batches = amount / (float) job.BatchSize;
|
||||
|
||||
// Below the threshold so just do it now.
|
||||
if (batches <= job.MinimumBatchParallel)
|
||||
{
|
||||
ProcessSerialNow(job, amount);
|
||||
return;
|
||||
}
|
||||
|
||||
var tracker = InternalProcess(job, amount);
|
||||
tracker.Event.WaitHandle.WaitOne();
|
||||
_trackerPool.Return(tracker);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ProcessSerialNow(IParallelRobustJob jobs, int amount)
|
||||
{
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
jobs.Execute(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitHandle Process(IParallelRobustJob job, int amount)
|
||||
{
|
||||
var tracker = InternalProcess(job, amount);
|
||||
return tracker.Event.WaitHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a parallel job internally. Used so we can pool the tracker task for ProcessParallelNow
|
||||
/// and not rely on external callers to return it where they don't want to wait.
|
||||
/// </summary>
|
||||
private ParallelTracker InternalProcess(IParallelRobustJob job, int amount)
|
||||
{
|
||||
var batches = (int) MathF.Ceiling(amount / (float) job.BatchSize);
|
||||
var batchSize = job.BatchSize;
|
||||
var tracker = _trackerPool.Get();
|
||||
|
||||
// Need to set this up front to avoid firing too early.
|
||||
tracker.Event.Reset();
|
||||
tracker.PendingTasks = batches;
|
||||
|
||||
for (var i = 0; i < batches; i++)
|
||||
{
|
||||
var start = i * batchSize;
|
||||
var end = Math.Min(start + batchSize, amount);
|
||||
var subJob = GetParallelJob(job, start, end, tracker);
|
||||
|
||||
// From what I can tell preferLocal is more of a !forceGlobal flag.
|
||||
// Also UnsafeQueue should be fine as long as we don't use async locals.
|
||||
ThreadPool.UnsafeQueueUserWorkItem(subJob, true);
|
||||
}
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
/// <summary>
|
||||
/// Runs an <see cref="IRobustJob"/> and handles cleanup.
|
||||
/// </summary>
|
||||
private sealed class InternalJob : IRobustJob
|
||||
{
|
||||
private IRobustJob _robust = default!;
|
||||
|
||||
public readonly ManualResetEventSlim Event = new();
|
||||
private ObjectPool<InternalJob> _parentPool = default!;
|
||||
|
||||
public void Set(IRobustJob job, ObjectPool<InternalJob> parentPool)
|
||||
{
|
||||
_robust = job;
|
||||
_parentPool = parentPool;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
_robust.Execute();
|
||||
Event.Set();
|
||||
_parentPool.Return(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an <see cref="IParallelRobustJob"/> and handles cleanup.
|
||||
/// </summary>
|
||||
private sealed class InternalParallelJob : IRobustJob
|
||||
{
|
||||
private IParallelRobustJob _robust = default!;
|
||||
private int _start;
|
||||
private int _end;
|
||||
|
||||
private ParallelTracker _tracker = default!;
|
||||
private ObjectPool<InternalParallelJob> _parentPool = default!;
|
||||
|
||||
public void Set(IParallelRobustJob robust, int start, int end, ParallelTracker tracker, ObjectPool<InternalParallelJob> parentPool)
|
||||
{
|
||||
_robust = robust;
|
||||
_start = start;
|
||||
_end = end;
|
||||
|
||||
_tracker = tracker;
|
||||
_parentPool = parentPool;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
for (var i = _start; i < _end; i++)
|
||||
{
|
||||
_robust.Execute(i);
|
||||
}
|
||||
|
||||
// Set the event and return it to the pool for re-use.
|
||||
_tracker.Set();
|
||||
_parentPool.Return(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks jobs internally. This is because WaitHandle has a max limit of 64 tasks.
|
||||
/// So we'll just decrement PendingTasks in lieu.
|
||||
/// </summary>
|
||||
private sealed class ParallelTracker
|
||||
{
|
||||
public readonly ManualResetEventSlim Event = new();
|
||||
public int PendingTasks;
|
||||
|
||||
/// <summary>
|
||||
/// Marks the tracker as having 1 less pending task.
|
||||
/// </summary>
|
||||
public void Set()
|
||||
{
|
||||
Interlocked.Decrement(ref PendingTasks);
|
||||
|
||||
if (PendingTasks <= 0)
|
||||
Event.Set();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
@@ -32,19 +32,19 @@ public sealed partial class ToolshedManager
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
_genericTypeParsers.Add(t.GetGenericTypeDefinition(), parserType);
|
||||
_log.Info($"Setting up {parserType.PrettyName()}, {t.GetGenericTypeDefinition().PrettyName()}");
|
||||
_log.Verbose($"Setting up {parserType.PrettyName()}, {t.GetGenericTypeDefinition().PrettyName()}");
|
||||
}
|
||||
else if (t.IsGenericParameter)
|
||||
{
|
||||
_constrainedParsers.Add((t, parserType));
|
||||
_log.Info($"Setting up {parserType.PrettyName()}, for T where T: {string.Join(", ", t.GetGenericParameterConstraints().Select(x => x.PrettyName()))}");
|
||||
_log.Verbose($"Setting up {parserType.PrettyName()}, for T where T: {string.Join(", ", t.GetGenericParameterConstraints().Select(x => x.PrettyName()))}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parser = (ITypeParser) _typeFactory.CreateInstanceUnchecked(parserType, oneOff: true);
|
||||
parser.PostInject();
|
||||
_log.Info($"Setting up {parserType.PrettyName()}, {parser.Parses.PrettyName()}");
|
||||
_log.Verbose($"Setting up {parserType.PrettyName()}, {parser.Parses.PrettyName()}");
|
||||
_consoleTypeParsers.Add(parser.Parses, parser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -156,7 +157,8 @@ internal sealed class MsgViewVariablesListPathReq : MsgViewVariablesPathReq
|
||||
{
|
||||
base.ReadFromBuffer(buffer, serializer);
|
||||
var length = buffer.ReadInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
using var stream = RobustMemoryManager.GetMemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Options = serializer.Deserialize<VVListPathOptions>(stream);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ namespace Robust.UnitTesting.Shared.Timing
|
||||
public void TestCancellation()
|
||||
{
|
||||
var timerManager = IoCManager.Resolve<ITimerManager>();
|
||||
var taskManager = IoCManager.Resolve<ITaskManager>();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var ran = false;
|
||||
|
||||
@@ -1,18 +1,55 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Threading;
|
||||
|
||||
namespace Robust.UnitTesting;
|
||||
|
||||
/// <summary>
|
||||
/// Only allows 1 parallel process for testing purposes.
|
||||
/// </summary>
|
||||
/// </summary>j
|
||||
public sealed class TestingParallelManager : IParallelManager
|
||||
{
|
||||
public event Action? ParallelCountChanged;
|
||||
public int ParallelProcessCount => 1;
|
||||
|
||||
public void AddAndInvokeParallelCountChanged(Action changed)
|
||||
{
|
||||
// Gottem
|
||||
return;
|
||||
}
|
||||
|
||||
WaitHandle IParallelManager.Process(IRobustJob job)
|
||||
{
|
||||
job.Execute();
|
||||
var ev = new ManualResetEventSlim();
|
||||
ev.Set();
|
||||
return ev.WaitHandle;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ProcessNow(IParallelRobustJob jobs, int amount)
|
||||
{
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
jobs.Execute(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ProcessSerialNow(IParallelRobustJob jobs, int amount)
|
||||
{
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
jobs.Execute(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitHandle Process(IParallelRobustJob jobs, int amount)
|
||||
{
|
||||
ProcessSerialNow(jobs, amount);
|
||||
var ev = new ManualResetEventSlim();
|
||||
ev.Set();
|
||||
return ev.WaitHandle;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user