mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
29 Commits
v0.8.46
...
prototype-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49075a970 | ||
|
|
a67acd453c | ||
|
|
b540f04a7a | ||
|
|
bcaa7001ad | ||
|
|
3f0fba7b4e | ||
|
|
d7d7a53045 | ||
|
|
40daba9adf | ||
|
|
5665f8eb1c | ||
|
|
8f0d562f3e | ||
|
|
f3950e940e | ||
|
|
3fdca65cc9 | ||
|
|
23d0b8a555 | ||
|
|
452b03d5a6 | ||
|
|
c8f2dab381 | ||
|
|
7409df07f8 | ||
|
|
973406b91d | ||
|
|
2e646fe2b6 | ||
|
|
6b902d22d4 | ||
|
|
83e6d52e58 | ||
|
|
025fdcd0b3 | ||
|
|
fe3ace92bd | ||
|
|
317070f167 | ||
|
|
3524363ad4 | ||
|
|
90287dbb09 | ||
|
|
9a2f35f2d3 | ||
|
|
3f10dbe770 | ||
|
|
8fcba93ada | ||
|
|
6a31c5649c | ||
|
|
7bd90578dc |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<PropertyGroup><Version>0.8.46</Version></PropertyGroup>
|
||||
<PropertyGroup><Version>0.8.52</Version></PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -209,8 +209,8 @@ namespace Robust.Client.Debugging
|
||||
|
||||
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody.Owner).Fixtures.Values)
|
||||
{
|
||||
// Invalid shape - Box2D doesn't check for IsSensor
|
||||
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f)
|
||||
// Invalid shape - Box2D doesn't check for IsSensor but we will for sanity.
|
||||
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f && fixture.Hard)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
@@ -14,6 +15,7 @@ public abstract class AppearanceVisualizer
|
||||
/// Initializes an entity to be managed by this appearance controller.
|
||||
/// DO NOT assume this is your only entity. Visualizers are shared.
|
||||
/// </summary>
|
||||
[Obsolete("Subscribe to your component being initialised instead.")]
|
||||
public virtual void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
}
|
||||
@@ -23,6 +25,7 @@ public abstract class AppearanceVisualizer
|
||||
/// Update its visuals here.
|
||||
/// </summary>
|
||||
/// <param name="component">The appearance component of the entity that might need updating.</param>
|
||||
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
||||
public virtual void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -9,39 +10,10 @@ namespace Robust.Client.GameObjects;
|
||||
/// This is the client instance of <see cref="AppearanceComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(AppearanceComponent))]
|
||||
[ComponentReference(typeof(AppearanceComponent)), Friend(typeof(AppearanceSystem))]
|
||||
public sealed class ClientAppearanceComponent : AppearanceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
private bool _appearanceDirty;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("visuals")]
|
||||
internal List<AppearanceVisualizer> Visualizers = new();
|
||||
|
||||
protected override void MarkDirty()
|
||||
{
|
||||
if (_appearanceDirty)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<AppearanceSystem>().EnqueueUpdate(this);
|
||||
_appearanceDirty = true;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var visual in Visualizers)
|
||||
{
|
||||
visual.InitializeEntity(Owner);
|
||||
}
|
||||
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
internal void UnmarkDirty()
|
||||
{
|
||||
_appearanceDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,14 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public void AnchorStateChanged()
|
||||
{
|
||||
SendDirty();
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(Owner);
|
||||
SendDirty(xform);
|
||||
|
||||
if(!_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
if(!xform.Anchored)
|
||||
return;
|
||||
|
||||
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
|
||||
_lastPosition = (_entityManager.GetComponent<TransformComponent>(Owner).GridID, grid.TileIndicesFor(_entityManager.GetComponent<TransformComponent>(Owner).Coordinates));
|
||||
var grid = _mapManager.GetGrid(xform.GridID);
|
||||
_lastPosition = (xform.GridID, grid.TileIndicesFor(xform.Coordinates));
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
@@ -56,9 +57,10 @@ namespace Robust.Client.GameObjects
|
||||
SendDirty();
|
||||
}
|
||||
|
||||
private void SendDirty()
|
||||
private void SendDirty(TransformComponent? xform = null)
|
||||
{
|
||||
if (_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
xform ??= _entityManager.GetComponent<TransformComponent>(Owner);
|
||||
if (xform.Anchored)
|
||||
{
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new OccluderDirtyEvent(Owner, _lastPosition));
|
||||
|
||||
@@ -62,6 +62,12 @@ namespace Robust.Client.GameObjects
|
||||
Play(component, animation, key);
|
||||
}
|
||||
|
||||
public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
|
||||
{
|
||||
component ??= EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
|
||||
Play(component, animation, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start playing an animation.
|
||||
/// </summary>
|
||||
@@ -79,6 +85,14 @@ namespace Robust.Client.GameObjects
|
||||
component.PlayingAnimations.ContainsKey(key);
|
||||
}
|
||||
|
||||
public bool HasRunningAnimation(EntityUid uid, AnimationPlayerComponent? component, string key)
|
||||
{
|
||||
if (component == null)
|
||||
TryComp(uid, out component);
|
||||
|
||||
return component != null && component.PlayingAnimations.ContainsKey(key);
|
||||
}
|
||||
|
||||
public bool HasRunningAnimation(AnimationPlayerComponent component, string key)
|
||||
{
|
||||
return component.PlayingAnimations.ContainsKey(key);
|
||||
@@ -88,6 +102,18 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
}
|
||||
|
||||
public void Stop(EntityUid uid, string key)
|
||||
{
|
||||
if (!TryComp<AnimationPlayerComponent>(uid, out var player)) return;
|
||||
player.PlayingAnimations.Remove(key);
|
||||
}
|
||||
|
||||
public void Stop(EntityUid uid, AnimationPlayerComponent? component, string key)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false)) return;
|
||||
component.PlayingAnimations.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AnimationCompletedEvent : EntityEventArgs
|
||||
|
||||
@@ -1,17 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class AppearanceSystem : EntitySystem
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
|
||||
|
||||
public void EnqueueUpdate(ClientAppearanceComponent component)
|
||||
public override void Initialize()
|
||||
{
|
||||
_queuedUpdates.Enqueue(component);
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ClientAppearanceComponent, ComponentInit>(OnAppearanceInit);
|
||||
SubscribeLocalEvent<ClientAppearanceComponent, ComponentHandleState>(OnAppearanceHandleState);
|
||||
}
|
||||
|
||||
private void OnAppearanceInit(EntityUid uid, ClientAppearanceComponent component, ComponentInit args)
|
||||
{
|
||||
foreach (var visual in component.Visualizers)
|
||||
{
|
||||
visual.InitializeEntity(uid);
|
||||
}
|
||||
|
||||
MarkDirty(component);
|
||||
}
|
||||
|
||||
private void OnAppearanceHandleState(EntityUid uid, ClientAppearanceComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not AppearanceComponentState actualState)
|
||||
return;
|
||||
|
||||
var stateDiff = component.AppearanceData.Count != actualState.Data.Count;
|
||||
|
||||
if (!stateDiff)
|
||||
{
|
||||
foreach (var (key, value) in component.AppearanceData)
|
||||
{
|
||||
if (!actualState.Data.TryGetValue(key, out var stateValue) ||
|
||||
!value.Equals(stateValue))
|
||||
{
|
||||
stateDiff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateDiff) return;
|
||||
|
||||
component.AppearanceData = actualState.Data;
|
||||
MarkDirty(component);
|
||||
}
|
||||
|
||||
public override void MarkDirty(AppearanceComponent component)
|
||||
{
|
||||
if (component.AppearanceDirty)
|
||||
return;
|
||||
|
||||
_queuedUpdates.Enqueue((ClientAppearanceComponent) component);
|
||||
component.AppearanceDirty = true;
|
||||
}
|
||||
|
||||
internal void UnmarkDirty(ClientAppearanceComponent component)
|
||||
{
|
||||
component.AppearanceDirty = false;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -22,7 +76,7 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
|
||||
OnChangeData(appearance.Owner, appearance);
|
||||
appearance.UnmarkDirty();
|
||||
UnmarkDirty(appearance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +84,30 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
if (!Resolve(uid, ref appearanceComponent, false)) return;
|
||||
|
||||
var ev = new AppearanceChangeEvent
|
||||
{
|
||||
Component = appearanceComponent,
|
||||
AppearanceData = appearanceComponent.AppearanceData,
|
||||
};
|
||||
|
||||
// Give it AppearanceData so we can still keep the friend attribute on the component.
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
// Eventually visualizers would be nuked and we'd just make them components instead.
|
||||
foreach (var visualizer in appearanceComponent.Visualizers)
|
||||
{
|
||||
visualizer.OnChangeData(appearanceComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised whenever the appearance data for an entity changes.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct AppearanceChangeEvent
|
||||
{
|
||||
public AppearanceComponent Component = default!;
|
||||
public IReadOnlyDictionary<object, object> AppearanceData = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,11 @@ namespace Robust.Client.GameObjects
|
||||
EntityManager.TryGetComponent(sender, out ClientOccluderComponent? iconSmooth)
|
||||
&& iconSmooth.Initialized)
|
||||
{
|
||||
var grid1 = _mapManager.GetGrid(EntityManager.GetComponent<TransformComponent>(sender).GridID);
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(sender).Coordinates;
|
||||
var xform = EntityManager.GetComponent<TransformComponent>(sender);
|
||||
if (!_mapManager.TryGetGrid(xform.GridID, out var grid1))
|
||||
return;
|
||||
|
||||
var coords = xform.Coordinates;
|
||||
|
||||
_dirtyEntities.Enqueue(sender);
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.North));
|
||||
|
||||
@@ -5,6 +5,8 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using static Robust.Shared.Containers.ContainerManagerComponent;
|
||||
|
||||
@@ -131,12 +133,51 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
// If an entity is currently in the shadow realm, it means we probably left PVS and are now getting
|
||||
// back into range. We do not want to directly insert this entity, as IF the container and entity
|
||||
// transform states did not get sent simultaneously, the entity's transform will be modified by the
|
||||
// insert operation. This means it will then be reset to the shadow realm, causing it to be ejected
|
||||
// from the container. It would then subsequently be parented to the container without ever being
|
||||
// re-inserted, leading to the client seeing what should be hidden entities attached to
|
||||
// containers/players.
|
||||
if (Transform(entity).MapID == MapId.Nullspace)
|
||||
{
|
||||
AddExpectedEntity(entity, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!container.ContainedEntities.Contains(entity))
|
||||
container.Insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void HandleParentChanged(ref EntParentChangedMessage message)
|
||||
{
|
||||
base.HandleParentChanged(ref message);
|
||||
|
||||
// If an entity warped in from null-space (i.e., re-entered PVS) and got attached to a container, do the same checks as for newly initialized entities.
|
||||
if (message.OldParent != null && message.OldParent.Value.IsValid())
|
||||
return;
|
||||
|
||||
if (!ExpectedEntities.TryGetValue(message.Entity, out var container))
|
||||
return;
|
||||
|
||||
if (Transform(message.Entity).ParentUid != container.Owner)
|
||||
{
|
||||
// This container is expecting an entity... but it got parented to some other entity???
|
||||
// Ah well, the sever should send a new container state that updates expected entities so just ignore it for now.
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveExpectedEntity(message.Entity);
|
||||
|
||||
if (container.Deleted)
|
||||
return;
|
||||
|
||||
container.Insert(message.Entity);
|
||||
}
|
||||
|
||||
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
|
||||
|
||||
20
Robust.Client/GameObjects/EntitySystems/VisualizerSystem.cs
Normal file
20
Robust.Client/GameObjects/EntitySystems/VisualizerSystem.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// An abstract entity system inheritor for systems that deal with
|
||||
/// appearance data, replacing <see cref="AppearanceVisualizer"/>.
|
||||
/// </summary>
|
||||
public abstract class VisualizerSystem<T> : EntitySystem
|
||||
where T: Component
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<T, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
protected virtual void OnAppearanceChange(EntityUid uid, T component, ref AppearanceChangeEvent args) {}
|
||||
}
|
||||
@@ -25,7 +25,12 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
if (!ReadOnly)
|
||||
{
|
||||
lineEdit.OnTextEntered += e =>
|
||||
ValueChanged(Angle.FromDegrees(double.Parse(e.Text, CultureInfo.InvariantCulture)));
|
||||
{
|
||||
if (!double.TryParse(e.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
return;
|
||||
|
||||
ValueChanged(Angle.FromDegrees(number));
|
||||
};
|
||||
}
|
||||
|
||||
hBox.AddChild(lineEdit);
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Robust.Server.Console.Commands
|
||||
var groundUid = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entityManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
var horizontalFixture = new Fixture(ground, horizontal)
|
||||
{
|
||||
CollisionLayer = 2,
|
||||
@@ -192,7 +192,7 @@ namespace Robust.Server.Console.Commands
|
||||
var groundUid = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entityManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(20, 0), new Vector2(-20, 0));
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
var horizontalFixture = new Fixture(ground, horizontal)
|
||||
{
|
||||
CollisionLayer = 2,
|
||||
|
||||
18
Robust.Server/GameObjects/EntitySystems/AppearanceSystem.cs
Normal file
18
Robust.Server/GameObjects/EntitySystems/AppearanceSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ServerAppearanceComponent, ComponentGetState>(OnAppearanceGetState);
|
||||
}
|
||||
|
||||
private static void OnAppearanceGetState(EntityUid uid, ServerAppearanceComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new AppearanceComponentState(component.AppearanceData);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
<!-- -->
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.4" />
|
||||
<PackageReference Include="prometheus-net" Version="4.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="5.0.0" />
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Robust.Shared.Containers
|
||||
#endregion
|
||||
|
||||
// Eject entities from their parent container if the parent change is done by the transform only.
|
||||
private void HandleParentChanged(ref EntParentChangedMessage message)
|
||||
protected virtual void HandleParentChanged(ref EntParentChangedMessage message)
|
||||
{
|
||||
var oldParentEntity = message.OldParent;
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -21,68 +18,38 @@ namespace Robust.Shared.GameObjects;
|
||||
[ComponentProtoName("Appearance")]
|
||||
public abstract class AppearanceComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
private Dictionary<object, object> _appearanceData = new();
|
||||
[ViewVariables] internal bool AppearanceDirty;
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new AppearanceComponentState(_appearanceData);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
if (curState is not AppearanceComponentState actualState)
|
||||
return;
|
||||
|
||||
var stateDiff = _appearanceData.Count != actualState.Data.Count;
|
||||
|
||||
if (!stateDiff)
|
||||
{
|
||||
foreach (var (key, value) in _appearanceData)
|
||||
{
|
||||
if (!actualState.Data.TryGetValue(key, out var stateValue) ||
|
||||
!value.Equals(stateValue))
|
||||
{
|
||||
stateDiff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateDiff) return;
|
||||
|
||||
_appearanceData = actualState.Data;
|
||||
MarkDirty();
|
||||
}
|
||||
[ViewVariables] internal Dictionary<object, object> AppearanceData = new();
|
||||
|
||||
public void SetData(string key, object value)
|
||||
{
|
||||
if (_appearanceData.TryGetValue(key, out var existing) && existing.Equals(value))
|
||||
if (AppearanceData.TryGetValue(key, out var existing) && existing.Equals(value))
|
||||
return;
|
||||
|
||||
_appearanceData[key] = value;
|
||||
AppearanceData[key] = value;
|
||||
Dirty();
|
||||
MarkDirty();
|
||||
EntitySystem.Get<SharedAppearanceSystem>().MarkDirty(this);
|
||||
}
|
||||
|
||||
public void SetData(Enum key, object value)
|
||||
{
|
||||
if (_appearanceData.TryGetValue(key, out var existing) && existing.Equals(value))
|
||||
if (AppearanceData.TryGetValue(key, out var existing) && existing.Equals(value))
|
||||
return;
|
||||
|
||||
_appearanceData[key] = value;
|
||||
AppearanceData[key] = value;
|
||||
Dirty();
|
||||
MarkDirty();
|
||||
EntitySystem.Get<SharedAppearanceSystem>().MarkDirty(this);
|
||||
}
|
||||
|
||||
public T GetData<T>(string key)
|
||||
{
|
||||
return (T)_appearanceData[key];
|
||||
return (T)AppearanceData[key];
|
||||
}
|
||||
|
||||
public T GetData<T>(Enum key)
|
||||
{
|
||||
return (T)_appearanceData[key];
|
||||
return (T)AppearanceData[key];
|
||||
}
|
||||
|
||||
public bool TryGetData<T>(Enum key, [NotNullWhen(true)] out T data)
|
||||
@@ -95,11 +62,9 @@ public abstract class AppearanceComponent : Component
|
||||
return TryGetDataPrivate(key, out data);
|
||||
}
|
||||
|
||||
protected virtual void MarkDirty() { }
|
||||
|
||||
private bool TryGetDataPrivate<T>(object key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
if (_appearanceData.TryGetValue(key, out var dat))
|
||||
if (AppearanceData.TryGetValue(key, out var dat))
|
||||
{
|
||||
data = (T)dat;
|
||||
return true;
|
||||
@@ -108,15 +73,4 @@ public abstract class AppearanceComponent : Component
|
||||
data = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class AppearanceComponentState : ComponentState
|
||||
{
|
||||
public readonly Dictionary<object, object> Data;
|
||||
|
||||
public AppearanceComponentState(Dictionary<object, object> data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,41 +78,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public int FixtureCount => _entMan.GetComponent<FixturesComponent>(Owner).Fixtures.Count;
|
||||
|
||||
[ViewVariables]
|
||||
public int ContactCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = 0;
|
||||
var edge = ContactEdges;
|
||||
while (edge != null)
|
||||
{
|
||||
edge = edge.Next;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Contact> Contacts
|
||||
{
|
||||
get
|
||||
{
|
||||
var edge = ContactEdges;
|
||||
|
||||
while (edge != null)
|
||||
{
|
||||
yield return edge.Contact!;
|
||||
edge = edge.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
[ViewVariables] public int ContactCount => Contacts.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Linked-list of all of our contacts.
|
||||
/// </summary>
|
||||
internal ContactEdge? ContactEdges { get; set; } = null;
|
||||
internal LinkedList<Contact> Contacts = new();
|
||||
|
||||
public bool IgnorePaused { get; set; }
|
||||
|
||||
@@ -842,15 +813,16 @@ namespace Robust.Shared.GameObjects
|
||||
// TOOD: Need SetTransformIgnoreContacts so we can teleport body and /ignore contacts/
|
||||
public void DestroyContacts()
|
||||
{
|
||||
ContactEdge? contactEdge = ContactEdges;
|
||||
while (contactEdge != null)
|
||||
var node = Contacts.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
var contactEdge0 = contactEdge;
|
||||
contactEdge = contactEdge.Next;
|
||||
PhysicsMap?.ContactManager.Destroy(contactEdge0.Contact!);
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
PhysicsMap?.ContactManager.Destroy(contact);
|
||||
}
|
||||
|
||||
ContactEdges = null;
|
||||
DebugTools.Assert(Contacts.Count == 0);
|
||||
}
|
||||
|
||||
IEnumerable<IPhysBody> IPhysBody.GetCollidingEntities(Vector2 offset, bool approx)
|
||||
|
||||
@@ -508,6 +508,7 @@ namespace Robust.Shared.GameObjects
|
||||
&& netSet.ContainsKey(netId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T EnsureComponent<T>(EntityUid uid) where T : Component, new()
|
||||
{
|
||||
@@ -517,6 +518,20 @@ namespace Robust.Shared.GameObjects
|
||||
return AddComponent<T>(uid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool EnsureComponent<T>(EntityUid entity, out T component) where T : Component, new()
|
||||
{
|
||||
if (TryGetComponent<T>(entity, out var comp))
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
component = AddComponent<T>(entity);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetComponent<T>(EntityUid uid)
|
||||
@@ -707,72 +722,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#region Join Functions
|
||||
|
||||
// Funny struct enumerator equivalent to EntityQuery<T>
|
||||
public struct EntQueryEnumerator<T> : IDisposable where T : Component
|
||||
{
|
||||
private readonly bool _includePaused;
|
||||
private Dictionary<EntityUid, Component>.Enumerator _comps;
|
||||
private Dictionary<EntityUid, Component> _metaData;
|
||||
|
||||
public EntQueryEnumerator(bool includePaused, Dictionary<EntityUid, Component>.Enumerator comps,
|
||||
Dictionary<EntityUid, Component> metaData)
|
||||
{
|
||||
_includePaused = includePaused;
|
||||
_comps = comps;
|
||||
_metaData = metaData;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out T? component)
|
||||
{
|
||||
if (_includePaused)
|
||||
{
|
||||
while (_comps.MoveNext())
|
||||
{
|
||||
component = (T)_comps.Current.Value;
|
||||
|
||||
if (component.Deleted) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (_comps.MoveNext())
|
||||
{
|
||||
component = (T)_comps.Current.Value;
|
||||
|
||||
if (component.Deleted) continue;
|
||||
|
||||
if (!_metaData.TryGetValue(component.Owner, out var metaComp)) continue;
|
||||
|
||||
var meta = (MetaDataComponent)metaComp;
|
||||
|
||||
if (meta.EntityPaused) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
component = null;
|
||||
Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_comps.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public EntQueryEnumerator<T> EntityQueryEnumerator<T>(bool includePaused = false) where T : Component
|
||||
{
|
||||
// Unless you have a profile showing a speed need for the funny struct enumerator just using the IEnumerable is easier.
|
||||
var comps = _entTraitArray[ArrayIndexFor<T>()];
|
||||
var meta = _entTraitArray[ArrayIndexFor<MetaDataComponent>()];
|
||||
var enumerator = new EntQueryEnumerator<T>(includePaused, comps.GetEnumerator(), meta);
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<T> EntityQuery<T>(bool includePaused = false) where T : IComponent
|
||||
{
|
||||
|
||||
@@ -151,6 +151,15 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>The component in question</returns>
|
||||
T EnsureComponent<T>(EntityUid uid) where T : Component, new();
|
||||
|
||||
/// <summary>
|
||||
/// This method will always return a component for a certain entity, adding it if it's not there already.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to modify.</param>
|
||||
/// <param name="component">The output component after being ensured.</param>
|
||||
/// <typeparam name="T">Component to add.</typeparam>
|
||||
/// <returns>The component in question</returns>
|
||||
bool EnsureComponent<T>(EntityUid uid, out T component) where T : Component, new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
@@ -279,13 +288,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>True if the player should get the component state.</returns>
|
||||
bool CanGetComponentState(IEventBus eventBus, IComponent component, ICommonSession player);
|
||||
|
||||
/// <summary>
|
||||
/// Returns ALL component instances of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A trait or type of a component to retrieve.</typeparam>
|
||||
/// <returns>All components that have the specified type.</returns>
|
||||
EntityManager.EntQueryEnumerator<T> EntityQueryEnumerator<T>(bool includePaused = false) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Returns ALL component instances of a specified type.
|
||||
/// </summary>
|
||||
|
||||
@@ -73,7 +73,9 @@ namespace Robust.Shared.GameObjects
|
||||
IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo);
|
||||
|
||||
|
||||
bool UpdateEntityTree(EntityUid entity, TransformComponent xform, Box2? worldAABB = null);
|
||||
|
||||
void RemoveFromEntityTrees(EntityUid entity);
|
||||
|
||||
Box2 GetWorldAabbFromEntity(in EntityUid ent, TransformComponent? xform = null);
|
||||
|
||||
21
Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs
Normal file
21
Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
internal abstract class SharedAppearanceSystem : EntitySystem
|
||||
{
|
||||
public virtual void MarkDirty(AppearanceComponent component) {}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AppearanceComponentState : ComponentState
|
||||
{
|
||||
public readonly Dictionary<object, object> Data;
|
||||
|
||||
public AppearanceComponentState(Dictionary<object, object> data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
@@ -38,4 +39,31 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public Box2 GetHardAABB(PhysicsComponent body, TransformComponent? xform = null, FixturesComponent? fixtures = null)
|
||||
{
|
||||
if (!Resolve(body.Owner, ref xform, ref fixtures))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
|
||||
var transform = new Transform(worldPos, (float) worldRot.Theta);
|
||||
|
||||
var bounds = new Box2(transform.Position, transform.Position);
|
||||
|
||||
foreach (var fixture in fixtures.Fixtures.Values)
|
||||
{
|
||||
if (!fixture.Hard) continue;
|
||||
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
var boundy = fixture.Shape.ComputeAABB(transform, i);
|
||||
bounds = bounds.Union(boundy);
|
||||
}
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,61 +138,21 @@ namespace Robust.Shared.GameObjects
|
||||
return bodies;
|
||||
}
|
||||
|
||||
// TODO: This is mainly just a hack to get rotated grid doors working in SS14 but whenever we do querysystem we'll clean this shit up
|
||||
/// <summary>
|
||||
/// Returns all enabled physics bodies intersecting this body.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not consider CanCollide on the provided body.
|
||||
/// </remarks>
|
||||
/// <param name="body">The body to check for intersection</param>
|
||||
/// <param name="enlarge">How much to enlarge / shrink the bounds by given we often extend them slightly.</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(PhysicsComponent body, float enlarge = 0f)
|
||||
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(PhysicsComponent body)
|
||||
{
|
||||
// TODO: Should use the collisionmanager test for overlap instead (once I optimise and check it actually works).
|
||||
var mapId = EntityManager.GetComponent<TransformComponent>(body.Owner).MapID;
|
||||
var node = body.Contacts.First;
|
||||
|
||||
if (mapId == MapId.Nullspace ||
|
||||
!EntityManager.TryGetComponent(body.Owner, out FixturesComponent? manager) ||
|
||||
manager.FixtureCount == 0) return Array.Empty<PhysicsComponent>();
|
||||
|
||||
var bodies = new HashSet<PhysicsComponent>();
|
||||
|
||||
var (worldPos, worldRot) = Transform(body.Owner).GetWorldPositionRotation();
|
||||
var worldAABB = body.GetWorldAABB(worldPos, worldRot);
|
||||
|
||||
foreach (var broadphase in _broadphaseSystem.GetBroadphases(mapId, worldAABB))
|
||||
while (node != null)
|
||||
{
|
||||
var (_, broadRot, broadInvMatrix) = EntityManager.GetComponent<TransformComponent>(broadphase.Owner).GetWorldPositionRotationInvMatrix();
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
|
||||
var localTransform = new Transform(broadInvMatrix.Transform(worldPos), worldRot - broadRot);
|
||||
|
||||
foreach (var (_, fixture) in manager.Fixtures)
|
||||
{
|
||||
var collisionMask = fixture.CollisionMask;
|
||||
var collisionLayer = fixture.CollisionLayer;
|
||||
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
var aabb = fixture.Shape.ComputeAABB(localTransform, i).Enlarged(enlarge);
|
||||
|
||||
foreach (var proxy in broadphase.Tree.QueryAabb(aabb))
|
||||
{
|
||||
var proxyFixture = proxy.Fixture;
|
||||
var proxyBody = proxyFixture.Body;
|
||||
|
||||
if (proxyBody == body ||
|
||||
(proxyFixture.CollisionMask & collisionLayer) == 0x0 &&
|
||||
(collisionMask & proxyFixture.CollisionLayer) == 0x0) continue;
|
||||
|
||||
bodies.Add(proxyBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
var other = body == bodyA ? bodyB : bodyA;
|
||||
yield return other;
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
// TODO: This will get every body but we don't need to do that
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
{
|
||||
Vertex1 = start;
|
||||
Vertex2 = end;
|
||||
OneSided = true;
|
||||
OneSided = false;
|
||||
}
|
||||
|
||||
public bool Equals(IPhysShape? other)
|
||||
|
||||
@@ -31,11 +31,14 @@ using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -48,11 +51,61 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
internal MapId MapId { get; set; }
|
||||
|
||||
public readonly ContactHead ContactList;
|
||||
public int ContactCount { get; private set; }
|
||||
private const int ContactPoolInitialSize = 64;
|
||||
// TODO: Jesus we should really have a test for this
|
||||
/// <summary>
|
||||
/// Ordering is under <see cref="ShapeType"/>
|
||||
/// uses enum to work out which collision evaluation to use.
|
||||
/// </summary>
|
||||
private static Contact.ContactType[,] _registers = {
|
||||
{
|
||||
// Circle register
|
||||
Contact.ContactType.Circle,
|
||||
Contact.ContactType.EdgeAndCircle,
|
||||
Contact.ContactType.PolygonAndCircle,
|
||||
Contact.ContactType.ChainAndCircle,
|
||||
Contact.ContactType.AabbAndCircle,
|
||||
},
|
||||
{
|
||||
// Edge register
|
||||
Contact.ContactType.EdgeAndCircle,
|
||||
Contact.ContactType.NotSupported, // Edge
|
||||
Contact.ContactType.EdgeAndPolygon,
|
||||
Contact.ContactType.NotSupported, // Chain
|
||||
Contact.ContactType.NotSupported, // Aabb
|
||||
},
|
||||
{
|
||||
// Polygon register
|
||||
Contact.ContactType.PolygonAndCircle,
|
||||
Contact.ContactType.EdgeAndPolygon,
|
||||
Contact.ContactType.Polygon,
|
||||
Contact.ContactType.ChainAndPolygon,
|
||||
Contact.ContactType.AabbAndPolygon,
|
||||
},
|
||||
{
|
||||
// Chain register
|
||||
Contact.ContactType.ChainAndCircle,
|
||||
Contact.ContactType.NotSupported, // Edge
|
||||
Contact.ContactType.ChainAndPolygon,
|
||||
Contact.ContactType.NotSupported, // Chain
|
||||
Contact.ContactType.NotSupported, // Aabb - TODO Just cast to poly
|
||||
},
|
||||
{
|
||||
// Aabb register
|
||||
Contact.ContactType.AabbAndCircle,
|
||||
Contact.ContactType.NotSupported, // Edge - TODO Just cast to poly
|
||||
Contact.ContactType.AabbAndPolygon,
|
||||
Contact.ContactType.NotSupported, // Chain - TODO Just cast to poly
|
||||
Contact.ContactType.Aabb,
|
||||
}
|
||||
};
|
||||
|
||||
internal Stack<Contact> ContactPoolList = new(ContactPoolInitialSize);
|
||||
public int ContactCount => _activeContacts.Count;
|
||||
|
||||
private int ContactPoolInitialSize = 64;
|
||||
|
||||
private ObjectPool<Contact> _contactPool = new DefaultObjectPool<Contact>(new ContactPoolPolicy(), 1024);
|
||||
|
||||
internal LinkedList<Contact> _activeContacts = new();
|
||||
|
||||
// Didn't use the eventbus because muh allocs on something being run for every collision every frame.
|
||||
/// <summary>
|
||||
@@ -65,10 +118,54 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
// TODO: Also need to clean the station up to not have 160 contacts on roundstart
|
||||
|
||||
public ContactManager()
|
||||
private sealed class ContactPoolPolicy : IPooledObjectPolicy<Contact>
|
||||
{
|
||||
ContactList = new ContactHead();
|
||||
ContactCount = 0;
|
||||
public Contact Create()
|
||||
{
|
||||
var contact = new Contact();
|
||||
IoCManager.InjectDependencies(contact);
|
||||
#if DEBUG
|
||||
contact._debugPhysics = EntitySystem.Get<SharedDebugPhysicsSystem>();
|
||||
#endif
|
||||
contact.Manifold = new Manifold
|
||||
{
|
||||
Points = new ManifoldPoint[2]
|
||||
};
|
||||
|
||||
return contact;
|
||||
}
|
||||
|
||||
public bool Return(Contact obj)
|
||||
{
|
||||
SetContact(obj, null, 0, null, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetContact(Contact contact, Fixture? fixtureA, int indexA, Fixture? fixtureB, int indexB)
|
||||
{
|
||||
contact.Enabled = true;
|
||||
contact.IsTouching = false;
|
||||
contact.IslandFlag = false;
|
||||
contact.FilterFlag = false;
|
||||
// TOIFlag = false;
|
||||
|
||||
contact.FixtureA = fixtureA;
|
||||
contact.FixtureB = fixtureB;
|
||||
|
||||
contact.ChildIndexA = indexA;
|
||||
contact.ChildIndexB = indexB;
|
||||
|
||||
contact.Manifold.PointCount = 0;
|
||||
|
||||
//FPE: We only set the friction and restitution if we are not destroying the contact
|
||||
if (fixtureA != null && fixtureB != null)
|
||||
{
|
||||
contact.Friction = MathF.Sqrt(fixtureA.Friction * fixtureB.Friction);
|
||||
contact.Restitution = MathF.Max(fixtureA.Restitution, fixtureB.Restitution);
|
||||
}
|
||||
|
||||
contact.TangentSpeed = 0;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
@@ -100,10 +197,43 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
private void InitializePool()
|
||||
{
|
||||
var dummy = new Contact[ContactPoolInitialSize];
|
||||
|
||||
for (var i = 0; i < ContactPoolInitialSize; i++)
|
||||
{
|
||||
ContactPoolList.Push(new Contact(null, 0, null, 0));
|
||||
dummy[i] = _contactPool.Get();
|
||||
}
|
||||
|
||||
for (var i = 0; i < ContactPoolInitialSize; i++)
|
||||
{
|
||||
_contactPool.Return(dummy[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private Contact CreateContact(Fixture fixtureA, int indexA, Fixture fixtureB, int indexB)
|
||||
{
|
||||
var type1 = fixtureA.Shape.ShapeType;
|
||||
var type2 = fixtureB.Shape.ShapeType;
|
||||
|
||||
DebugTools.Assert(ShapeType.Unknown < type1 && type1 < ShapeType.TypeCount);
|
||||
DebugTools.Assert(ShapeType.Unknown < type2 && type2 < ShapeType.TypeCount);
|
||||
|
||||
// Pull out a spare contact object
|
||||
var contact = _contactPool.Get();
|
||||
|
||||
// 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, fixtureA, indexA, fixtureB, indexB);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetContact(contact, fixtureB, indexB, fixtureA, indexA);
|
||||
}
|
||||
|
||||
contact.Type = _registers[(int)type1, (int)type2];
|
||||
|
||||
return contact;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,95 +254,40 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// so no point duplicating
|
||||
|
||||
// Does a contact already exist?
|
||||
var edge = bodyB.ContactEdges;
|
||||
if (fixtureA.Contacts.ContainsKey(fixtureB))
|
||||
return;
|
||||
|
||||
while (edge != null)
|
||||
{
|
||||
if (edge.Other == bodyA)
|
||||
{
|
||||
var fA = edge.Contact!.FixtureA;
|
||||
var fB = edge.Contact!.FixtureB;
|
||||
var iA = edge.Contact.ChildIndexA;
|
||||
var iB = edge.Contact.ChildIndexB;
|
||||
|
||||
if (fA == fixtureA && fB == fixtureB && iA == indexA && iB == indexB)
|
||||
{
|
||||
// A contact already exists.
|
||||
return;
|
||||
}
|
||||
|
||||
if (fA == fixtureB && fB == fixtureA && iA == indexB && iB == indexA)
|
||||
{
|
||||
// A contact already exists.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
edge = edge.Next;
|
||||
}
|
||||
DebugTools.Assert(!fixtureB.Contacts.ContainsKey(fixtureA));
|
||||
|
||||
// Does a joint override collision? Is at least one body dynamic?
|
||||
if (!bodyB.ShouldCollide(bodyA))
|
||||
return;
|
||||
|
||||
//FPE feature: BeforeCollision delegate
|
||||
/*
|
||||
if (fixtureA.BeforeCollision != null && fixtureA.BeforeCollision(fixtureA, fixtureB) == false)
|
||||
return;
|
||||
|
||||
if (fixtureB.BeforeCollision != null && fixtureB.BeforeCollision(fixtureB, fixtureA) == false)
|
||||
return;
|
||||
*/
|
||||
|
||||
// Call the factory.
|
||||
Contact c = Contact.Create(this, fixtureA, indexA, fixtureB, indexB);
|
||||
|
||||
// Sloth: IDK why Farseer and Aether2D have this shit but fuck it.
|
||||
if (c == null) return;
|
||||
var contact = CreateContact(fixtureA, indexA, fixtureB, indexB);
|
||||
|
||||
// Contact creation may swap fixtures.
|
||||
fixtureA = c.FixtureA!;
|
||||
fixtureB = c.FixtureB!;
|
||||
fixtureA = contact.FixtureA!;
|
||||
fixtureB = contact.FixtureB!;
|
||||
bodyA = fixtureA.Body;
|
||||
bodyB = fixtureB.Body;
|
||||
|
||||
// Insert into world
|
||||
c.Prev = ContactList;
|
||||
c.Next = c.Prev.Next;
|
||||
c.Prev.Next = c;
|
||||
c.Next!.Prev = c;
|
||||
ContactCount++;
|
||||
contact.MapNode = _activeContacts.AddLast(contact);
|
||||
|
||||
// Connect to body A
|
||||
c.NodeA.Contact = c;
|
||||
c.NodeA.Other = bodyB;
|
||||
|
||||
c.NodeA.Previous = null;
|
||||
c.NodeA.Next = bodyA.ContactEdges;
|
||||
|
||||
if (bodyA.ContactEdges != null)
|
||||
{
|
||||
bodyA.ContactEdges.Previous = c.NodeA;
|
||||
}
|
||||
bodyA.ContactEdges = c.NodeA;
|
||||
DebugTools.Assert(!fixtureA.Contacts.ContainsKey(fixtureB));
|
||||
fixtureA.Contacts.Add(fixtureB, contact);
|
||||
contact.BodyANode = bodyA.Contacts.AddLast(contact);
|
||||
|
||||
// Connect to body B
|
||||
c.NodeB.Contact = c;
|
||||
c.NodeB.Other = bodyA;
|
||||
|
||||
c.NodeB.Previous = null;
|
||||
c.NodeB.Next = bodyB.ContactEdges;
|
||||
|
||||
if (bodyB.ContactEdges != null)
|
||||
{
|
||||
bodyB.ContactEdges.Previous = c.NodeB;
|
||||
}
|
||||
bodyB.ContactEdges = c.NodeB;
|
||||
DebugTools.Assert(!fixtureB.Contacts.ContainsKey(fixtureA));
|
||||
fixtureB.Contacts.Add(fixtureA, contact);
|
||||
contact.BodyBNode = bodyB.Contacts.AddLast(contact);
|
||||
}
|
||||
|
||||
internal static bool ShouldCollide(Fixture fixtureA, Fixture fixtureB)
|
||||
{
|
||||
// TODO: Should we only be checking one side's mask? I think maybe fixtureB? IDK
|
||||
return !((fixtureA.CollisionMask & fixtureB.CollisionLayer) == 0x0 &&
|
||||
(fixtureB.CollisionMask & fixtureA.CollisionLayer) == 0x0);
|
||||
}
|
||||
@@ -230,33 +305,35 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyB.Owner, new EndCollideEvent(fixtureB, fixtureA));
|
||||
}
|
||||
|
||||
if (contact.Manifold.PointCount > 0 && contact.FixtureA?.Hard == true && contact.FixtureB?.Hard == true)
|
||||
{
|
||||
if (bodyA.CanCollide)
|
||||
contact.FixtureA.Body.Awake = true;
|
||||
|
||||
if (bodyB.CanCollide)
|
||||
contact.FixtureB.Body.Awake = true;
|
||||
}
|
||||
|
||||
// Remove from the world
|
||||
contact.Prev!.Next = contact.Next;
|
||||
contact.Next!.Prev = contact.Prev;
|
||||
contact.Next = null;
|
||||
contact.Prev = null;
|
||||
ContactCount--;
|
||||
DebugTools.Assert(contact.MapNode != null);
|
||||
_activeContacts.Remove(contact.MapNode!);
|
||||
contact.MapNode = null;
|
||||
|
||||
// Remove from body 1
|
||||
if (contact.NodeA == bodyA.ContactEdges)
|
||||
bodyA.ContactEdges = contact.NodeA.Next;
|
||||
if (contact.NodeA.Previous != null)
|
||||
contact.NodeA.Previous.Next = contact.NodeA.Next;
|
||||
if (contact.NodeA.Next != null)
|
||||
contact.NodeA.Next.Previous = contact.NodeA.Previous;
|
||||
DebugTools.Assert(fixtureA.Contacts.ContainsKey(fixtureB));
|
||||
fixtureA.Contacts.Remove(fixtureB);
|
||||
DebugTools.Assert(bodyA.Contacts.Contains(contact.BodyANode!.Value));
|
||||
bodyA.Contacts.Remove(contact.BodyANode!);
|
||||
contact.BodyANode = null;
|
||||
|
||||
// Remove from body 2
|
||||
if (contact.NodeB == bodyB.ContactEdges)
|
||||
bodyB.ContactEdges = contact.NodeB.Next;
|
||||
if (contact.NodeB.Previous != null)
|
||||
contact.NodeB.Previous.Next = contact.NodeB.Next;
|
||||
if (contact.NodeB.Next != null)
|
||||
contact.NodeB.Next.Previous = contact.NodeB.Previous;
|
||||
|
||||
contact.Destroy();
|
||||
DebugTools.Assert(fixtureB.Contacts.ContainsKey(fixtureA));
|
||||
fixtureB.Contacts.Remove(fixtureA);
|
||||
bodyB.Contacts.Remove(contact.BodyBNode!);
|
||||
contact.BodyBNode = null;
|
||||
|
||||
// Insert into the pool.
|
||||
ContactPoolList.Push(contact);
|
||||
_contactPool.Return(contact);
|
||||
}
|
||||
|
||||
internal void Collide()
|
||||
@@ -269,9 +346,12 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Can be changed while enumerating
|
||||
// TODO: check for null instead?
|
||||
// Work out which contacts are still valid before we decide to update manifolds.
|
||||
for (var contact = ContactList.Next; contact != ContactList;)
|
||||
var node = _activeContacts.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
if (contact == null) break;
|
||||
var contact = node.Value;
|
||||
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
Fixture fixtureB = contact.FixtureB!;
|
||||
int indexA = contact.ChildIndexA;
|
||||
@@ -283,7 +363,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Do not try to collide disabled bodies
|
||||
if (!bodyA.CanCollide || !bodyB.CanCollide)
|
||||
{
|
||||
contact = contact.Next;
|
||||
node = node.Next;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -293,18 +373,16 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Should these bodies collide?
|
||||
if (bodyB.ShouldCollide(bodyA) == false)
|
||||
{
|
||||
Contact cNuke = contact;
|
||||
contact = contact.Next;
|
||||
Destroy(cNuke);
|
||||
node = node.Next;
|
||||
Destroy(contact);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check default filtering
|
||||
if (ShouldCollide(fixtureA, fixtureB) == false)
|
||||
{
|
||||
Contact cNuke = contact;
|
||||
contact = contact.Next;
|
||||
Destroy(cNuke);
|
||||
node = node.Next;
|
||||
Destroy(contact);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -329,7 +407,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// At least one body must be awake and it must be dynamic or kinematic.
|
||||
if (activeA == false && activeB == false)
|
||||
{
|
||||
contact = contact.Next;
|
||||
node = node.Next;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -362,14 +440,13 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Here we destroy contacts that cease to overlap in the broad-phase.
|
||||
if (!overlap)
|
||||
{
|
||||
Contact cNuke = contact;
|
||||
contact = contact.Next;
|
||||
Destroy(cNuke);
|
||||
node = node.Next;
|
||||
Destroy(contact);
|
||||
continue;
|
||||
}
|
||||
|
||||
contacts[index++] = contact;
|
||||
contact = contact.Next;
|
||||
node = node.Next;
|
||||
}
|
||||
|
||||
var status = ArrayPool<ContactStatus>.Shared.Rent(index);
|
||||
@@ -442,6 +519,8 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status)
|
||||
{
|
||||
var wake = ArrayPool<bool>.Shared.Rent(count);
|
||||
|
||||
if (count > _contactMultithreadThreshold * _contactMinimumThreads)
|
||||
{
|
||||
var (batches, batchSize) = SharedPhysicsSystem.GetBatch(count, _contactMultithreadThreshold);
|
||||
@@ -450,21 +529,37 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
var start = i * batchSize;
|
||||
var end = Math.Min(start + batchSize, count);
|
||||
UpdateContacts(contacts, start, end, status);
|
||||
UpdateContacts(contacts, start, end, status, wake);
|
||||
});
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateContacts(contacts, 0, count, status);
|
||||
UpdateContacts(contacts, 0, count, status, wake);
|
||||
}
|
||||
|
||||
// Can't do this during UpdateContacts due to IoC threading issues.
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var shouldWake = wake[i];
|
||||
if (!shouldWake) continue;
|
||||
|
||||
var contact = contacts[i];
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
|
||||
bodyA.Awake = true;
|
||||
bodyB.Awake = true;
|
||||
}
|
||||
|
||||
ArrayPool<bool>.Shared.Return(wake);
|
||||
}
|
||||
|
||||
private void UpdateContacts(Contact[] contacts, int start, int end, ContactStatus[] status)
|
||||
private void UpdateContacts(Contact[] contacts, int start, int end, ContactStatus[] status, bool[] wake)
|
||||
{
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
status[i] = contacts[i].Update(_physicsManager);
|
||||
status[i] = contacts[i].Update(_physicsManager, out wake[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,12 +568,14 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
|
||||
// We'll do pre and post-solve around all islands rather than each specific island as it seems cleaner with race conditions.
|
||||
for (var contact = ContactList.Next; contact != ContactList; contact = contact?.Next)
|
||||
var node = _activeContacts.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
if (contact is not {IsTouching: true, Enabled: true})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
if (contact is not {IsTouching: true, Enabled: true}) continue;
|
||||
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
|
||||
@@ -29,92 +29,45 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
[Virtual]
|
||||
public class Contact : IEquatable<Contact>
|
||||
public sealed class Contact : IEquatable<Contact>
|
||||
{
|
||||
[Dependency] private readonly IManifoldManager _manifoldManager = default!;
|
||||
#if DEBUG
|
||||
private SharedDebugPhysicsSystem _debugPhysics = default!;
|
||||
internal SharedDebugPhysicsSystem _debugPhysics = default!;
|
||||
#endif
|
||||
|
||||
public ContactEdge NodeA = new();
|
||||
public ContactEdge NodeB = new();
|
||||
// Store these nodes so we can do fast removals when required, rather than having to iterate every node
|
||||
// trying to find it.
|
||||
|
||||
/// <summary>
|
||||
/// Get the next world contact.
|
||||
/// The node of this contact on the map.
|
||||
/// </summary>
|
||||
public Contact? Next { get; internal set; }
|
||||
public LinkedListNode<Contact>? MapNode = null;
|
||||
|
||||
/// <summary>
|
||||
/// Get the previous world contact.
|
||||
/// The node of this contact on body A.
|
||||
/// </summary>
|
||||
public Contact? Prev { get; internal set; }
|
||||
public LinkedListNode<Contact>? BodyANode = null;
|
||||
|
||||
/// <summary>
|
||||
/// The node of this contact on body A.
|
||||
/// </summary>
|
||||
public LinkedListNode<Contact>? BodyBNode = null;
|
||||
|
||||
public Fixture? FixtureA;
|
||||
public Fixture? FixtureB;
|
||||
|
||||
public Manifold Manifold;
|
||||
|
||||
private ContactType _type;
|
||||
|
||||
// TODO: Jesus we should really have a test for this
|
||||
/// <summary>
|
||||
/// Ordering is under <see cref="ShapeType"/>
|
||||
/// uses enum to work out which collision evaluation to use.
|
||||
/// </summary>
|
||||
private static ContactType[,] _registers = {
|
||||
{
|
||||
// Circle register
|
||||
ContactType.Circle,
|
||||
ContactType.EdgeAndCircle,
|
||||
ContactType.PolygonAndCircle,
|
||||
ContactType.ChainAndCircle,
|
||||
ContactType.AabbAndCircle,
|
||||
},
|
||||
{
|
||||
// Edge register
|
||||
ContactType.EdgeAndCircle,
|
||||
ContactType.NotSupported, // Edge
|
||||
ContactType.EdgeAndPolygon,
|
||||
ContactType.NotSupported, // Chain
|
||||
ContactType.NotSupported, // Aabb
|
||||
},
|
||||
{
|
||||
// Polygon register
|
||||
ContactType.PolygonAndCircle,
|
||||
ContactType.EdgeAndPolygon,
|
||||
ContactType.Polygon,
|
||||
ContactType.ChainAndPolygon,
|
||||
ContactType.AabbAndPolygon,
|
||||
},
|
||||
{
|
||||
// Chain register
|
||||
ContactType.ChainAndCircle,
|
||||
ContactType.NotSupported, // Edge
|
||||
ContactType.ChainAndPolygon,
|
||||
ContactType.NotSupported, // Chain
|
||||
ContactType.NotSupported, // Aabb - TODO Just cast to poly
|
||||
},
|
||||
{
|
||||
// Aabb register
|
||||
ContactType.AabbAndCircle,
|
||||
ContactType.NotSupported, // Edge - TODO Just cast to poly
|
||||
ContactType.AabbAndPolygon,
|
||||
ContactType.NotSupported, // Chain - TODO Just cast to poly
|
||||
ContactType.Aabb,
|
||||
}
|
||||
};
|
||||
internal ContactType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Has this contact already been added to an island?
|
||||
@@ -158,54 +111,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// </summary>
|
||||
public float TangentSpeed { get; set; }
|
||||
|
||||
public Contact(Fixture? fixtureA, int indexA, Fixture? fixtureB, int indexB)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
#if DEBUG
|
||||
_debugPhysics = EntitySystem.Get<SharedDebugPhysicsSystem>();
|
||||
#endif
|
||||
Manifold = new Manifold
|
||||
{
|
||||
Points = new ManifoldPoint[2]
|
||||
};
|
||||
Reset(fixtureA, indexA, fixtureB, indexB);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new contact to use, using the contact pool if relevant.
|
||||
/// </summary>
|
||||
internal static Contact Create(ContactManager contactManager, Fixture fixtureA, int indexA, Fixture fixtureB, int indexB)
|
||||
{
|
||||
var type1 = fixtureA.Shape.ShapeType;
|
||||
var type2 = fixtureB.Shape.ShapeType;
|
||||
|
||||
DebugTools.Assert(ShapeType.Unknown < type1 && type1 < ShapeType.TypeCount);
|
||||
DebugTools.Assert(ShapeType.Unknown < type2 && type2 < ShapeType.TypeCount);
|
||||
|
||||
// Pull out a spare contact object
|
||||
contactManager.ContactPoolList.TryPop(out var contact);
|
||||
|
||||
// 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))
|
||||
{
|
||||
if (contact == null)
|
||||
contact = new Contact(fixtureA, indexA, fixtureB, indexB);
|
||||
else
|
||||
contact.Reset(fixtureA, indexA, fixtureB, indexB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (contact == null)
|
||||
contact = new Contact(fixtureB, indexB, fixtureA, indexA);
|
||||
else
|
||||
contact.Reset(fixtureB, indexB, fixtureA, indexA);
|
||||
}
|
||||
|
||||
contact._type = _registers[(int)type1, (int)type2];
|
||||
|
||||
return contact;
|
||||
}
|
||||
|
||||
public void ResetRestitution()
|
||||
{
|
||||
Restitution = MathF.Max(FixtureA?.Restitution ?? 0.0f, FixtureB?.Restitution ?? 0.0f);
|
||||
@@ -216,47 +121,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
Friction = MathF.Sqrt(FixtureA?.Friction ?? 0.0f * FixtureB?.Friction ?? 0.0f);
|
||||
}
|
||||
|
||||
private void Reset(Fixture? fixtureA, int indexA, Fixture? fixtureB, int indexB)
|
||||
{
|
||||
Enabled = true;
|
||||
IsTouching = false;
|
||||
IslandFlag = false;
|
||||
FilterFlag = false;
|
||||
// TOIFlag = false;
|
||||
|
||||
FixtureA = fixtureA;
|
||||
FixtureB = fixtureB;
|
||||
|
||||
ChildIndexA = indexA;
|
||||
ChildIndexB = indexB;
|
||||
|
||||
Manifold.PointCount = 0;
|
||||
|
||||
Next = null;
|
||||
Prev = null;
|
||||
|
||||
NodeA.Contact = null;
|
||||
NodeA.Previous = null;
|
||||
NodeA.Next = null;
|
||||
NodeA.Other = null;
|
||||
|
||||
NodeB.Contact = null;
|
||||
NodeB.Previous = null;
|
||||
NodeB.Next = null;
|
||||
NodeB.Other = null;
|
||||
|
||||
// _toiCount = 0;
|
||||
|
||||
//FPE: We only set the friction and restitution if we are not destroying the contact
|
||||
if (FixtureA != null && FixtureB != null)
|
||||
{
|
||||
Friction = MathF.Sqrt(FixtureA.Friction * FixtureB.Friction);
|
||||
Restitution = MathF.Max(FixtureA.Restitution, FixtureB.Restitution);
|
||||
}
|
||||
|
||||
TangentSpeed = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the world manifold.
|
||||
/// </summary>
|
||||
@@ -276,8 +140,9 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// Update the contact manifold and touching status.
|
||||
/// Note: do not assume the fixture AABBs are overlapping or are valid.
|
||||
/// </summary>
|
||||
/// <param name="wake">Whether we should wake the bodies due to touching changing.</param>
|
||||
/// <returns>What current status of the contact is (e.g. start touching, end touching, etc.)</returns>
|
||||
internal ContactStatus Update(IPhysicsManager physicsManager)
|
||||
internal ContactStatus Update(IPhysicsManager physicsManager, out bool wake)
|
||||
{
|
||||
PhysicsComponent bodyA = FixtureA!.Body;
|
||||
PhysicsComponent bodyB = FixtureB!.Body;
|
||||
@@ -290,6 +155,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
bool touching;
|
||||
var wasTouching = IsTouching;
|
||||
|
||||
wake = false;
|
||||
var sensor = !(FixtureA.Hard && FixtureB.Hard);
|
||||
|
||||
var bodyATransform = physicsManager.GetTransform(bodyA);
|
||||
@@ -336,8 +202,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
if (touching != wasTouching)
|
||||
{
|
||||
bodyA.Awake = true;
|
||||
bodyB.Awake = true;
|
||||
wake = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +243,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
private void Evaluate(ref Manifold manifold, in Transform transformA, in Transform transformB)
|
||||
{
|
||||
// This is expensive and shitcodey, see below.
|
||||
switch (_type)
|
||||
switch (Type)
|
||||
{
|
||||
// TODO: Need a unit test for these.
|
||||
case ContactType.Polygon:
|
||||
@@ -426,24 +291,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
}
|
||||
}
|
||||
|
||||
internal void Destroy()
|
||||
{
|
||||
if (Manifold.PointCount > 0 && FixtureA?.Hard == true && FixtureB?.Hard == true)
|
||||
{
|
||||
var bodyA = FixtureA.Body;
|
||||
var bodyB = FixtureB.Body;
|
||||
|
||||
if (bodyA.CanCollide)
|
||||
FixtureA.Body.Awake = true;
|
||||
|
||||
if (bodyB.CanCollide)
|
||||
FixtureB.Body.Awake = true;
|
||||
}
|
||||
|
||||
Reset(null, 0, null, 0);
|
||||
}
|
||||
|
||||
private enum ContactType : byte
|
||||
public enum ContactType : byte
|
||||
{
|
||||
NotSupported,
|
||||
Polygon,
|
||||
@@ -466,7 +314,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
return Equals(FixtureA, other.FixtureA) &&
|
||||
Equals(FixtureB, other.FixtureB) &&
|
||||
Manifold.Equals(other.Manifold) &&
|
||||
_type == other._type &&
|
||||
Type == other.Type &&
|
||||
Enabled == other.Enabled &&
|
||||
ChildIndexA == other.ChildIndexA &&
|
||||
ChildIndexB == other.ChildIndexB &&
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Farseer Physics Engine:
|
||||
* Copyright (c) 2012 Ian Qvist
|
||||
*
|
||||
* Original source Box2D:
|
||||
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
public sealed class ContactEdge
|
||||
{
|
||||
/// <summary>
|
||||
/// This contact in the chain.
|
||||
/// </summary>
|
||||
public Contact? Contact { get; set; }
|
||||
|
||||
public ContactEdge? Next { get; set; }
|
||||
|
||||
public ContactEdge? Previous { get; set; }
|
||||
|
||||
// Subject to change
|
||||
public PhysicsComponent? Other { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright (c) 2017 Kastellanos Nikolaos (Aether2D)
|
||||
|
||||
/*
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
// So Farseer uses List<T> but box2d and aether both use a linkedlist for world contacts presumably because you need to frequently
|
||||
// and remove in the middle of enumeration so this is probably more brrrrrt
|
||||
public sealed class ContactHead : Contact, IEnumerable<Contact>
|
||||
{
|
||||
internal ContactHead(): base(null, 0, null, 0)
|
||||
{
|
||||
Prev = this;
|
||||
Next = this;
|
||||
}
|
||||
|
||||
IEnumerator<Contact> IEnumerable<Contact>.GetEnumerator()
|
||||
{
|
||||
return new ContactEnumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return new ContactEnumerator(this);
|
||||
}
|
||||
|
||||
|
||||
#region Nested type: ContactEnumerator
|
||||
|
||||
private struct ContactEnumerator : IEnumerator<Contact>
|
||||
{
|
||||
private ContactHead _head;
|
||||
private Contact _current;
|
||||
|
||||
public Contact Current => _current;
|
||||
object IEnumerator.Current => _current;
|
||||
|
||||
public ContactEnumerator(ContactHead contact)
|
||||
{
|
||||
_head = contact;
|
||||
_current = _head;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_current = _head;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_current = _current.Next!;
|
||||
return _current != _head;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_head = null!;
|
||||
_current = null!;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
@@ -72,6 +73,13 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
[field:NonSerialized]
|
||||
public PhysicsComponent Body { get; internal set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// All of the other fixtures this fixture has a contact with.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[NonSerialized]
|
||||
public Dictionary<Fixture, Contact> Contacts = new();
|
||||
|
||||
/// <summary>
|
||||
/// Contact friction between 2 bodies. Not tile-friction for top-down.
|
||||
/// </summary>
|
||||
|
||||
@@ -277,9 +277,13 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
DebugTools.Assert(_islandSet.Count == 0);
|
||||
|
||||
for (Contact? c = ContactManager.ContactList.Next; c != ContactManager.ContactList; c = c.Next)
|
||||
var contactNode = ContactManager._activeContacts.First;
|
||||
|
||||
while (contactNode != null)
|
||||
{
|
||||
c!.IslandFlag = false;
|
||||
var contact = contactNode.Value;
|
||||
contactNode = contactNode.Next;
|
||||
contact.IslandFlag = false;
|
||||
}
|
||||
|
||||
// Build and simulated islands from awake bodies.
|
||||
@@ -337,9 +341,12 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// As static bodies can never be awake (unlike Farseer) we'll set this after the check.
|
||||
body.ForceAwake();
|
||||
|
||||
for (var contactEdge = body.ContactEdges; contactEdge != null; contactEdge = contactEdge.Next)
|
||||
var node = body.Contacts.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
var contact = contactEdge.Contact!;
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
// Has this contact already been added to an island?
|
||||
if (contact.IslandFlag) continue;
|
||||
@@ -352,8 +359,10 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
_islandContacts.Add(contact);
|
||||
contact.IslandFlag = true;
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
|
||||
var other = contactEdge.Other!;
|
||||
var other = bodyA == body ? bodyB : bodyA;
|
||||
|
||||
// Was the other body already added to this island?
|
||||
if (other.Island) continue;
|
||||
|
||||
@@ -218,20 +218,9 @@ namespace Robust.Shared.Physics
|
||||
return;
|
||||
}
|
||||
|
||||
var edge = body.ContactEdges;
|
||||
|
||||
while (edge != null)
|
||||
foreach (var (_, contact) in fixture.Contacts.ToArray())
|
||||
{
|
||||
var contact = edge.Contact!;
|
||||
edge = edge.Next;
|
||||
|
||||
var fixtureA = contact.FixtureA;
|
||||
var fixtureB = contact.FixtureB;
|
||||
|
||||
if (fixture == fixtureA || fixture == fixtureB)
|
||||
{
|
||||
body.PhysicsMap?.ContactManager.Destroy(contact);
|
||||
}
|
||||
body.PhysicsMap?.ContactManager.Destroy(contact);
|
||||
}
|
||||
|
||||
var broadphase = body.Broadphase;
|
||||
|
||||
@@ -375,29 +375,16 @@ namespace Robust.Shared.Physics
|
||||
|
||||
public void RegenerateContacts(PhysicsComponent body)
|
||||
{
|
||||
var edge = body.ContactEdges;
|
||||
|
||||
// TODO: PhysicsMap actually needs to be made nullable (or needs a re-design to not be on the body).
|
||||
// Eventually it'll be a component on the map so nullspace won't have one anyway and we need to handle that scenario.
|
||||
// Technically it is nullable coz of networking (previously it got away with being able to ignore it
|
||||
// but anchoring can touch BodyType in HandleComponentState so we need to handle this here).
|
||||
if (body.PhysicsMap != null)
|
||||
{
|
||||
var contactManager = body.PhysicsMap.ContactManager;
|
||||
|
||||
while (edge != null)
|
||||
{
|
||||
var ce0 = edge;
|
||||
edge = edge.Next;
|
||||
contactManager.Destroy(ce0.Contact!);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugTools.Assert(body.ContactEdges == null);
|
||||
body.DestroyContacts();
|
||||
}
|
||||
|
||||
body.ContactEdges = null;
|
||||
DebugTools.Assert(body.Contacts.Count == 0);
|
||||
|
||||
var broadphase = body.Broadphase;
|
||||
|
||||
@@ -419,11 +406,12 @@ namespace Robust.Shared.Physics
|
||||
|
||||
var body = fixture.Body;
|
||||
|
||||
var edge = body.ContactEdges;
|
||||
var node = body.Contacts.First;
|
||||
|
||||
while (edge != null)
|
||||
while (node != null)
|
||||
{
|
||||
var contact = edge.Contact!;
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
var fixtureA = contact.FixtureA;
|
||||
var fixtureB = contact.FixtureB;
|
||||
|
||||
@@ -431,8 +419,6 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
contact.FilterFlag = true;
|
||||
}
|
||||
|
||||
edge = edge.Next;
|
||||
}
|
||||
|
||||
var broadphase = body.Broadphase;
|
||||
|
||||
@@ -409,17 +409,20 @@ namespace Robust.Shared.Physics
|
||||
var bodyA = joint.BodyA;
|
||||
var bodyB = joint.BodyB;
|
||||
|
||||
var edge = bodyB.ContactEdges;
|
||||
while (edge != null)
|
||||
var node = bodyB.Contacts.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
if (edge.Other == bodyA)
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
if (contact.FixtureA?.Body == bodyA ||
|
||||
contact.FixtureB?.Body == bodyA)
|
||||
{
|
||||
// Flag the contact for filtering at the next time step (where either
|
||||
// body is awake).
|
||||
edge.Contact!.FilterFlag = true;
|
||||
contact.FilterFlag = true;
|
||||
}
|
||||
|
||||
edge = edge.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
160
Robust.Shared/Prototypes/IPrototypeManager.cs
Normal file
160
Robust.Shared/Prototypes/IPrototypeManager.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Handle storage and loading of YAML prototypes.
|
||||
/// </summary>
|
||||
public interface IPrototypeManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain variant.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the variant of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
T Index<T>(string id) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the ID does not exist or the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IPrototype Index(Type type, string id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype of type <typeparamref name="T"/> with the specified <param name="id"/> exists.
|
||||
/// </summary>
|
||||
bool HasIndex<T>(string id) where T : class, IPrototype;
|
||||
|
||||
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
bool TryIndex(Type type, string id, [NotNullWhen(true)] out IPrototype? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype variant <param name="variant"/> exists.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>Whether the prototype variant exists.</returns>
|
||||
bool HasVariant(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>The specified prototype Type.</returns>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown when the specified prototype variant isn't registered or doesn't exist.
|
||||
/// </exception>
|
||||
Type GetVariantType(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <param name="prototype">The specified prototype Type, or null.</param>
|
||||
/// <returns>Whether the prototype type was found and <see cref="prototype"/> isn't null.</returns>
|
||||
bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="variant"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="prototype">The prototype in question.</param>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <typeparam name="T">The prototype in question.</typeparam>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom<T>([NotNullWhen(true)] out string? variant) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
/// </summary>
|
||||
List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false);
|
||||
|
||||
Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path);
|
||||
|
||||
HashSet<IPrototype> LoadFromString(string str, bool overwrite = false, string actionMessage="");
|
||||
|
||||
void RemoveString(string prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// Clear out all prototypes and reset to a blank slate.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
|
||||
/// </summary>
|
||||
void Resync();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific prototype name to be ignored.
|
||||
/// </summary>
|
||||
void RegisterIgnore(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single prototype class type into the manager.
|
||||
/// </summary>
|
||||
/// <param name="protoClass">A prototype class type that implements IPrototype. This type also
|
||||
/// requires a <see cref="PrototypeAttribute"/> with a non-empty class string.</param>
|
||||
void RegisterType(Type protoClass);
|
||||
|
||||
event Action<YamlStream, string>? LoadedData;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when prototype are reloaded. The event args contain the modified prototypes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does NOT fire on initial prototype load.
|
||||
/// </remarks>
|
||||
event Action<PrototypesReloadedEventArgs> PrototypesReloaded;
|
||||
}
|
||||
}
|
||||
32
Robust.Shared/Prototypes/PrototypeAttribute.cs
Normal file
32
Robust.Shared/Prototypes/PrototypeAttribute.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Quick attribute to give the prototype its type string.
|
||||
/// To prevent needing to instantiate it because interfaces can't declare statics.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(IPrototype))]
|
||||
[MeansImplicitUse]
|
||||
[MeansDataDefinition]
|
||||
public sealed class PrototypeAttribute : Attribute
|
||||
{
|
||||
private readonly string type;
|
||||
public string Type => type;
|
||||
public readonly int LoadPriority;
|
||||
public readonly string LoadBefore;
|
||||
public readonly string LoadAfter;
|
||||
|
||||
public PrototypeAttribute(string type, int loadPriority = 1, string loadBefore="" ,string loadAfter= "")
|
||||
{
|
||||
this.type = type;
|
||||
LoadPriority = loadPriority;
|
||||
LoadBefore = loadBefore;
|
||||
LoadAfter = loadAfter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
@@ -25,175 +26,8 @@ using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle storage and loading of YAML prototypes.
|
||||
/// </summary>
|
||||
public interface IPrototypeManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain variant.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the variant of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
T Index<T>(string id) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the ID does not exist or the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IPrototype Index(Type type, string id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype of type <typeparamref name="T"/> with the specified <param name="id"/> exists.
|
||||
/// </summary>
|
||||
bool HasIndex<T>(string id) where T : class, IPrototype;
|
||||
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
bool TryIndex(Type type, string id, [NotNullWhen(true)] out IPrototype? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype variant <param name="variant"/> exists.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>Whether the prototype variant exists.</returns>
|
||||
bool HasVariant(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>The specified prototype Type.</returns>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown when the specified prototype variant isn't registered or doesn't exist.
|
||||
/// </exception>
|
||||
Type GetVariantType(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <param name="prototype">The specified prototype Type, or null.</param>
|
||||
/// <returns>Whether the prototype type was found and <see cref="prototype"/> isn't null.</returns>
|
||||
bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="variant"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="prototype">The prototype in question.</param>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <typeparam name="T">The prototype in question.</typeparam>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom<T>([NotNullWhen(true)] out string? variant) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
/// </summary>
|
||||
List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false);
|
||||
|
||||
Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path);
|
||||
|
||||
List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false);
|
||||
|
||||
List<IPrototype> LoadString(string str, bool overwrite = false);
|
||||
|
||||
void RemoveString(string prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// Clear out all prototypes and reset to a blank slate.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
|
||||
/// </summary>
|
||||
void Resync();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific prototype name to be ignored.
|
||||
/// </summary>
|
||||
void RegisterIgnore(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single prototype class type into the manager.
|
||||
/// </summary>
|
||||
/// <param name="protoClass">A prototype class type that implements IPrototype. This type also
|
||||
/// requires a <see cref="PrototypeAttribute"/> with a non-empty class string.</param>
|
||||
void RegisterType(Type protoClass);
|
||||
|
||||
event Action<YamlStream, string>? LoadedData;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when prototype are reloaded. The event args contain the modified prototypes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does NOT fire on initial prototype load.
|
||||
/// </remarks>
|
||||
event Action<PrototypesReloadedEventArgs> PrototypesReloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quick attribute to give the prototype its type string.
|
||||
/// To prevent needing to instantiate it because interfaces can't declare statics.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(IPrototype))]
|
||||
[MeansImplicitUse]
|
||||
[MeansDataDefinition]
|
||||
public sealed class PrototypeAttribute : Attribute
|
||||
{
|
||||
private readonly string type;
|
||||
public string Type => type;
|
||||
public readonly int LoadPriority = 1;
|
||||
|
||||
public PrototypeAttribute(string type, int loadPriority = 1)
|
||||
{
|
||||
this.type = type;
|
||||
LoadPriority = loadPriority;
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class PrototypeManager : IPrototypeManager
|
||||
@@ -204,11 +38,12 @@ namespace Robust.Shared.Prototypes
|
||||
[Dependency] protected readonly ITaskManager TaskManager = default!;
|
||||
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
||||
|
||||
private readonly Dictionary<string, Type> _prototypeTypes = new();
|
||||
private readonly Dictionary<string, Type> _types = new();
|
||||
private readonly Dictionary<Type, int> _prototypePriorities = new();
|
||||
|
||||
private bool _initialized;
|
||||
private bool _hasEverBeenReloaded;
|
||||
private int mappingErrors;
|
||||
|
||||
#region IPrototypeManager members
|
||||
|
||||
@@ -216,8 +51,13 @@ namespace Robust.Shared.Prototypes
|
||||
private readonly Dictionary<Type, Dictionary<string, DeserializationResult>> _prototypeResults = new();
|
||||
private readonly Dictionary<Type, PrototypeInheritanceTree> _inheritanceTrees = new();
|
||||
|
||||
private readonly HashSet<string> _ignoredPrototypeTypes = new();
|
||||
private readonly HashSet<Type> LoadBeforeList = new ();
|
||||
private readonly HashSet<Type> LoadNormalList = new ();
|
||||
private readonly HashSet<Type> LoadAfterList = new ();
|
||||
|
||||
private readonly HashSet<ErrorNode> ErrorNodes = new ();
|
||||
|
||||
private readonly HashSet<string> _ignoredPrototypeTypes = new();
|
||||
public virtual void Initialize()
|
||||
{
|
||||
if (_initialized)
|
||||
@@ -225,8 +65,9 @@ namespace Robust.Shared.Prototypes
|
||||
throw new InvalidOperationException($"{nameof(PrototypeManager)} has already been initialized.");
|
||||
}
|
||||
|
||||
mappingErrors = 0;
|
||||
ReloadTypes();
|
||||
_initialized = true;
|
||||
ReloadPrototypeTypes();
|
||||
}
|
||||
|
||||
public IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype
|
||||
@@ -283,7 +124,8 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_prototypeTypes.Clear();
|
||||
mappingErrors = 0;
|
||||
_types.Clear();
|
||||
_prototypes.Clear();
|
||||
_prototypeResults.Clear();
|
||||
_inheritanceTrees.Clear();
|
||||
@@ -297,7 +139,7 @@ namespace Robust.Shared.Prototypes
|
||||
protected void ReloadPrototypes(IEnumerable<ResourcePath> filePaths)
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
var changed = filePaths.SelectMany(f => LoadFile(f.ToRootedPath(), true)).ToList();
|
||||
var changed = filePaths.SelectMany(f => LoadFromFile(f.ToRootedPath(), true)).ToList();
|
||||
ReloadPrototypes(changed);
|
||||
#endif
|
||||
}
|
||||
@@ -374,6 +216,7 @@ namespace Robust.Shared.Prototypes
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Inheritance Tree
|
||||
public void Resync()
|
||||
{
|
||||
var trees = _inheritanceTrees.Keys.ToList();
|
||||
@@ -442,174 +285,6 @@ namespace Robust.Shared.Prototypes
|
||||
_prototypes[type][id] = (IPrototype) populatedRes.RawValue!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
|
||||
_hasEverBeenReloaded = true;
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
var filePrototypes = LoadFile(resourcePath, overwrite);
|
||||
changedPrototypes.AddRange(filePrototypes);
|
||||
}
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path)
|
||||
{
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
var dict = new Dictionary<string, HashSet<ErrorNode>>();
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
using var reader = ReadFile(resourcePath);
|
||||
|
||||
if (reader == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
for (var i = 0; i < yamlStream.Documents.Count; i++)
|
||||
{
|
||||
var rootNode = (YamlSequenceNode) yamlStream.Documents[i].RootNode;
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
{
|
||||
var type = node.GetNode("type").AsString();
|
||||
if (!_prototypeTypes.ContainsKey(type))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
|
||||
}
|
||||
|
||||
var mapping = node.ToDataNodeCast<MappingDataNode>();
|
||||
mapping.Remove("type");
|
||||
var errorNodes = _serializationManager.ValidateNode(_prototypeTypes[type], mapping).GetErrors()
|
||||
.ToHashSet();
|
||||
if (errorNodes.Count == 0) continue;
|
||||
if (!dict.TryGetValue(resourcePath.ToString(), out var hashSet))
|
||||
dict[resourcePath.ToString()] = new HashSet<ErrorNode>();
|
||||
dict[resourcePath.ToString()].UnionWith(errorNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private StreamReader? ReadFile(ResourcePath file, bool @throw = true)
|
||||
{
|
||||
var retries = 0;
|
||||
|
||||
// This might be shit-code, but its pjb-responded-idk-when-asked shit-code.
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = new StreamReader(Resources.ContentFileRead(file), EncodingHelpers.UTF8);
|
||||
return reader;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (retries > 10)
|
||||
{
|
||||
if (@throw)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.Error($"Error reloading prototypes in file {file}.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
retries++;
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<IPrototype> LoadFile(ResourcePath file, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new HashSet<IPrototype>();
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = ReadFile(file, !overwrite);
|
||||
|
||||
if (reader == null)
|
||||
{
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
LoadedData?.Invoke(yamlStream, file.ToString());
|
||||
|
||||
for (var i = 0; i < yamlStream.Documents.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentPrototypes = LoadFromDocument(yamlStream.Documents[i], overwrite);
|
||||
changedPrototypes.UnionWith(documentPrototypes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("eng", $"Exception whilst loading prototypes from {file}#{i}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
var sawmill = Logger.GetSawmill("eng");
|
||||
sawmill.Error("YamlException whilst loading prototypes from {0}: {1}", file, e.Message);
|
||||
}
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
_hasEverBeenReloaded = true;
|
||||
var yaml = new YamlStream();
|
||||
yaml.Load(stream);
|
||||
|
||||
for (var i = 0; i < yaml.Documents.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentPrototypes = LoadFromDocument(yaml.Documents[i], overwrite);
|
||||
changedPrototypes.AddRange(documentPrototypes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new PrototypeLoadException($"Failed to load prototypes from document#{i}", e);
|
||||
}
|
||||
}
|
||||
|
||||
LoadedData?.Invoke(yaml, "anonymous prototypes YAML stream");
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public List<IPrototype> LoadString(string str, bool overwrite = false)
|
||||
{
|
||||
return LoadFromStream(new StringReader(str), overwrite);
|
||||
}
|
||||
|
||||
public void RemoveString(string prototypes)
|
||||
{
|
||||
var reader = new StringReader(prototypes);
|
||||
@@ -623,7 +298,7 @@ namespace Robust.Shared.Prototypes
|
||||
foreach (var node in root.Cast<YamlMappingNode>())
|
||||
{
|
||||
var typeString = node.GetNode("type").AsString();
|
||||
var type = _prototypeTypes[typeString];
|
||||
var type = _types[typeString];
|
||||
|
||||
var id = node.GetNode("id").AsString();
|
||||
|
||||
@@ -636,63 +311,204 @@ namespace Robust.Shared.Prototypes
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion IPrototypeManager members
|
||||
|
||||
private void ReloadPrototypeTypes()
|
||||
public Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path)
|
||||
{
|
||||
Clear();
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IPrototype>())
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
var dict = new Dictionary<string, HashSet<ErrorNode>>();
|
||||
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
RegisterType(type);
|
||||
LoadFromFile(resourcePath, false, true);
|
||||
|
||||
if (!ErrorNodes.Any())
|
||||
continue;
|
||||
|
||||
if (!dict.TryGetValue(resourcePath.ToString(), out var hashSet))
|
||||
dict[resourcePath.ToString()] = new HashSet<ErrorNode>();
|
||||
|
||||
dict[resourcePath.ToString()].UnionWith(ErrorNodes);
|
||||
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private HashSet<IPrototype> LoadFromDocument(YamlDocument document, bool overwrite = false)
|
||||
#region Prototype Loading
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Loads Prototypes from a path.
|
||||
/// </summary>
|
||||
/// <param name="path">path to files to load as prototype.</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
public List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new HashSet<IPrototype>();
|
||||
var rootNode = (YamlSequenceNode) document.RootNode;
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
_hasEverBeenReloaded = true;
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
var type = node.GetNode("type").AsString();
|
||||
if (!_prototypeTypes.ContainsKey(type))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
|
||||
}
|
||||
|
||||
var prototypeType = _prototypeTypes[type];
|
||||
var res = _serializationManager.Read(prototypeType, node.ToDataNode(), skipHook: true);
|
||||
var prototype = (IPrototype) res.RawValue!;
|
||||
|
||||
if (!overwrite && _prototypes[prototypeType].ContainsKey(prototype.ID))
|
||||
{
|
||||
throw new PrototypeLoadException($"Duplicate ID: '{prototype.ID}'");
|
||||
}
|
||||
|
||||
_prototypeResults[prototypeType][prototype.ID] = res;
|
||||
if (prototype is IInheritingPrototype inheritingPrototype)
|
||||
{
|
||||
_inheritanceTrees[prototypeType].AddId(prototype.ID, inheritingPrototype.Parent, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//we call it here since it wont get called when pushing inheritance
|
||||
res.CallAfterDeserializationHook();
|
||||
}
|
||||
|
||||
_prototypes[prototypeType][prototype.ID] = prototype;
|
||||
changedPrototypes.Add(prototype);
|
||||
var filePrototypes = LoadFromFile(resourcePath, overwrite);
|
||||
changedPrototypes.AddRange(filePrototypes);
|
||||
}
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads Prototypes from a file.
|
||||
/// </summary>
|
||||
/// <param name="file">file to load as prototype.</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists</param>
|
||||
/// /// <param name="validateMapping">toggle true to receive a count of total mapping errors</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
public HashSet<IPrototype> LoadFromFile(ResourcePath file, bool overwrite = false, bool validateMapping = false)
|
||||
{
|
||||
HashSet<IPrototype> LoadedPrototypes = new();
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = new StreamReader(Resources.ContentFileRead(file), EncodingHelpers.UTF8);
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
LoadedPrototypes = LoadFromDocument(yamlStream, overwrite, validateMapping, actionMessage: file.ToString());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.Error($"Error loading prototypes in file {file}.", e);
|
||||
}
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
var sawmill = Logger.GetSawmill("eng");
|
||||
sawmill.Error("Caught YamlException whilst loading prototypes from a File {0}: {1}", file.Filename, e.Message);
|
||||
}
|
||||
|
||||
return LoadedPrototypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads Prototypes from a string.
|
||||
/// </summary>
|
||||
/// <param name="str">Input string to load as prototype.</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists</param>
|
||||
/// <param name="actionMessage">String that will be included in the LoadedData Event</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
public HashSet<IPrototype> LoadFromString(string str, bool overwrite = false, string actionMessage = "")
|
||||
{
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(str));
|
||||
|
||||
return LoadFromDocument(yamlStream, overwrite, actionMessage: actionMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads YAML Prototypes via a Yaml Stream
|
||||
/// </summary>
|
||||
/// <param name="yamlStream">YamlStream to process</param>
|
||||
/// <param name="mappingErrors">a count of mapping errors. is 0 until you toggle validateMapping on.</param>
|
||||
/// <param name="validateMapping">toggle true to receive a count of total mapping errors</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists.</param>
|
||||
/// <param name="actionMessage">String that will be included in the LoadedData Event</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
/// <exception cref="PrototypeLoadException">Thrown when Prototype failed to load</exception>
|
||||
private HashSet<IPrototype> LoadFromDocument(YamlStream yamlStream, bool overwrite = false,
|
||||
bool validateMapping = false, string actionMessage = "")
|
||||
{
|
||||
var loadedPrototypes = new HashSet<IPrototype>();
|
||||
|
||||
|
||||
for (var i = 0; i < yamlStream.Documents.Count(); i++)
|
||||
{
|
||||
var prototypeDocument = yamlStream.Documents[i];
|
||||
var rootNode = (YamlSequenceNode) prototypeDocument.RootNode;
|
||||
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
{
|
||||
var type = node.GetNode("type").AsString();
|
||||
if (!_types.ContainsKey(type))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
|
||||
}
|
||||
|
||||
var prototypeType = _types[type];
|
||||
|
||||
var res = _serializationManager.Read(prototypeType, node.ToDataNode(), skipHook: true);
|
||||
var prototype = (IPrototype) res.RawValue!;
|
||||
|
||||
if (!overwrite && _prototypes[prototypeType].ContainsKey(prototype.ID))
|
||||
{
|
||||
throw new PrototypeLoadException($"Duplicate ID: '{prototype.ID}'");
|
||||
}
|
||||
|
||||
_prototypeResults[prototypeType][prototype.ID] = res;
|
||||
if (prototype is IInheritingPrototype inheritingPrototype)
|
||||
{
|
||||
_inheritanceTrees[prototypeType].AddId(prototype.ID, inheritingPrototype.Parent, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//we call it here since it wont get called when pushing inheritance
|
||||
res.CallAfterDeserializationHook();
|
||||
}
|
||||
|
||||
_prototypes[prototypeType][prototype.ID] = prototype;
|
||||
loadedPrototypes.Add(prototype);
|
||||
|
||||
if (validateMapping)
|
||||
{
|
||||
var mapping = node.ToDataNodeCast<MappingDataNode>();
|
||||
mapping.Remove("type");
|
||||
var errorNodes = _serializationManager.ValidateNode(_types[type], mapping).GetErrors()
|
||||
.ToHashSet();
|
||||
mappingErrors = errorNodes.Count;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadedData?.Invoke(yamlStream, actionMessage);
|
||||
return loadedPrototypes;
|
||||
}
|
||||
#endregion
|
||||
#endregion IPrototypeManager members
|
||||
|
||||
private void ReloadTypes()
|
||||
{
|
||||
Clear();
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IPrototype>())
|
||||
{
|
||||
var prototypeAttributes = (PrototypeAttribute?) Attribute.GetCustomAttribute(type, typeof(PrototypeAttribute));
|
||||
|
||||
if ( prototypeAttributes!.LoadBefore != string.Empty)
|
||||
LoadBeforeList.Add(type);
|
||||
else if (prototypeAttributes!.LoadAfter != string.Empty)
|
||||
LoadAfterList.Add(type);
|
||||
else
|
||||
LoadNormalList.Add(type);
|
||||
|
||||
RegisterType(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public bool HasIndex<T>(string id) where T : class, IPrototype
|
||||
{
|
||||
if (!_prototypes.TryGetValue(typeof(T), out var index))
|
||||
@@ -723,19 +539,19 @@ namespace Robust.Shared.Prototypes
|
||||
/// <inheritdoc />
|
||||
public bool HasVariant(string variant)
|
||||
{
|
||||
return _prototypeTypes.ContainsKey(variant);
|
||||
return _types.ContainsKey(variant);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetVariantType(string variant)
|
||||
{
|
||||
return _prototypeTypes[variant];
|
||||
return _types[variant];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype)
|
||||
{
|
||||
return _prototypeTypes.TryGetValue(variant, out prototype);
|
||||
return _types.TryGetValue(variant, out prototype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -793,14 +609,14 @@ namespace Robust.Shared.Prototypes
|
||||
"No " + nameof(PrototypeAttribute) + " to give it a type string.");
|
||||
}
|
||||
|
||||
if (_prototypeTypes.ContainsKey(attribute.Type))
|
||||
if (_types.ContainsKey(attribute.Type))
|
||||
{
|
||||
throw new InvalidImplementationException(type,
|
||||
typeof(IPrototype),
|
||||
$"Duplicate prototype type ID: {attribute.Type}. Current: {_prototypeTypes[attribute.Type]}");
|
||||
$"Duplicate prototype type ID: {attribute.Type}. Current: {_types[attribute.Type]}");
|
||||
}
|
||||
|
||||
_prototypeTypes[attribute.Type] = type;
|
||||
_types[attribute.Type] = type;
|
||||
_prototypePriorities[type] = attribute.LoadPriority;
|
||||
|
||||
if (typeof(IPrototype).IsAssignableFrom(type))
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.4" />
|
||||
<PackageReference Include="Microsoft.ILVerification" Version="5.0.0" />
|
||||
<PackageReference Include="Nett" Version="0.15.0" />
|
||||
<PackageReference Include="nfluidsynth" Version="0.3.1" />
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace Robust.Shared.Timing
|
||||
});
|
||||
|
||||
private readonly IGameTiming _timing;
|
||||
private TimeSpan _lastTick; // last wall time tick
|
||||
private TimeSpan _lastKeepUp; // last wall time keep up announcement
|
||||
|
||||
public event EventHandler<FrameEventArgs>? Input;
|
||||
@@ -128,7 +127,7 @@ namespace Robust.Shared.Timing
|
||||
// maximum number of ticks to queue before the loop slows down.
|
||||
var maxTime = TimeSpan.FromTicks(_timing.TickPeriod.Ticks * MaxQueuedTicks);
|
||||
|
||||
var accumulator = _timing.RealTime - _lastTick;
|
||||
var accumulator = _timing.RealTime - _timing.LastTick;
|
||||
|
||||
// If the game can't keep up, limit time.
|
||||
if (accumulator > maxTime)
|
||||
@@ -136,10 +135,10 @@ namespace Robust.Shared.Timing
|
||||
// limit accumulator to max time.
|
||||
accumulator = maxTime;
|
||||
|
||||
// pull lastTick up to the current realTime
|
||||
// pull LastTick up to the current realTime
|
||||
// This will slow down the simulation, but if we are behind from a
|
||||
// lag spike hopefully it will be able to catch up.
|
||||
_lastTick = _timing.RealTime - maxTime;
|
||||
_timing.LastTick = _timing.RealTime - maxTime;
|
||||
|
||||
// announce we are falling behind
|
||||
if ((_timing.RealTime - _lastKeepUp).TotalSeconds >= 15.0)
|
||||
@@ -172,7 +171,7 @@ namespace Robust.Shared.Timing
|
||||
while (accumulator >= tickPeriod)
|
||||
{
|
||||
accumulator -= tickPeriod;
|
||||
_lastTick += tickPeriod;
|
||||
_timing.LastTick += tickPeriod;
|
||||
|
||||
// only run the simulation if unpaused, but still use up the accumulated time
|
||||
if (_timing.Paused)
|
||||
|
||||
@@ -117,6 +117,11 @@ namespace Robust.Shared.Timing
|
||||
/// </summary>
|
||||
public GameTick CurTick { get; set; } = new(1); // Time always starts on the first tick
|
||||
|
||||
/// <summary>
|
||||
/// Timespan for the last tick.
|
||||
/// </summary>
|
||||
public TimeSpan LastTick { get; set; }
|
||||
|
||||
private byte _tickRate;
|
||||
private TimeSpan _tickRemainder;
|
||||
|
||||
|
||||
@@ -77,6 +77,11 @@ namespace Robust.Shared.Timing
|
||||
/// </summary>
|
||||
GameTick CurTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timespan for the last tick.
|
||||
/// </summary>
|
||||
TimeSpan LastTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The target ticks/second of the simulation.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadString(Prototypes))
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadFromString(Prototypes))
|
||||
.InitializeInstance();
|
||||
|
||||
// Adds the map with id 1, and spawns entity 1 as the map entity.
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
var manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.RegisterType(typeof(EntityPrototype));
|
||||
manager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||
manager.LoadFromString(PROTOTYPES);
|
||||
manager.Resync();
|
||||
|
||||
// build the net dream
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Robust.UnitTesting.Server.GameObjects
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
var manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.RegisterType(typeof(EntityPrototype));
|
||||
manager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||
manager.LoadFromString(PROTOTYPES);
|
||||
manager.Resync();
|
||||
|
||||
//NOTE: The grids have not moved, so we can assert worldpos == localpos for the test
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadString(PROTOTYPE))
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadFromString(PROTOTYPE))
|
||||
.InitializeInstance();
|
||||
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(f=>
|
||||
{
|
||||
f.LoadString(Prototypes);
|
||||
f.LoadFromString(Prototypes);
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
prototypeManager.RegisterType(typeof(EntityPrototype));
|
||||
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||
prototypeManager.LoadFromString(PROTOTYPES);
|
||||
prototypeManager.Resync();
|
||||
|
||||
var factory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
@@ -48,12 +48,19 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
// No tiles set hence should be no collision
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var edge = physics1?.ContactEdges;
|
||||
var node = physics1?.Contacts.First;
|
||||
|
||||
while (edge != null)
|
||||
while (node != null)
|
||||
{
|
||||
Assert.That(edge.Other, Is.Not.EqualTo(physics2));
|
||||
edge = edge.Next;
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
|
||||
var other = physics1 == bodyA ? bodyB : bodyA;
|
||||
|
||||
Assert.That(other, Is.Not.EqualTo(physics2));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,17 +75,23 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var colliding = false;
|
||||
var edge = physics1?.ContactEdges;
|
||||
var node = physics1?.Contacts.First;
|
||||
|
||||
while (edge != null)
|
||||
while (node != null)
|
||||
{
|
||||
if (edge.Other == physics2)
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
|
||||
var other = physics1 == bodyA ? bodyB : bodyA;
|
||||
|
||||
if (other == physics2)
|
||||
{
|
||||
colliding = true;
|
||||
break;
|
||||
}
|
||||
|
||||
edge = edge.Next;
|
||||
}
|
||||
|
||||
Assert.That(colliding);
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
EntityUid tempQualifier = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entityManager.AddComponent<PhysicsComponent>(tempQualifier);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(20, 0), new Vector2(-20, 0));
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
var horizontalFixture = new Fixture(ground, horizontal)
|
||||
{
|
||||
CollisionLayer = 1,
|
||||
@@ -187,7 +187,7 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
EntityUid tempQualifier = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entityManager.AddComponent<PhysicsComponent>(tempQualifier);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(20, 0), new Vector2(-20, 0));
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
var horizontalFixture = new Fixture(ground, horizontal)
|
||||
{
|
||||
CollisionLayer = 1,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -47,7 +48,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
_prototypes = (PrototypeManager) IoCManager.Resolve<IPrototypeManager>();
|
||||
_prototypes.RegisterType(typeof(EntityPrototype));
|
||||
_prototypes.LoadString(InitialPrototypes);
|
||||
_prototypes.LoadFromString(InitialPrototypes);
|
||||
_prototypes.Resync();
|
||||
|
||||
_maps = IoCManager.Resolve<IMapManager>();
|
||||
@@ -75,8 +76,8 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
Assert.That(entityComponent.Value, Is.EqualTo(5));
|
||||
Assert.False(IoCManager.Resolve<IEntityManager>().HasComponent<HotReloadTestTwoComponent>(entity));
|
||||
|
||||
var changedPrototypes = _prototypes.LoadString(ReloadedPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes);
|
||||
var changedPrototypes = _prototypes.LoadFromString(ReloadedPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes.ToList());
|
||||
|
||||
Assert.True(reloaded);
|
||||
reloaded = false;
|
||||
@@ -87,8 +88,8 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
// New components are added
|
||||
Assert.True(IoCManager.Resolve<IEntityManager>().HasComponent<HotReloadTestTwoComponent>(entity));
|
||||
|
||||
changedPrototypes = _prototypes.LoadString(InitialPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes);
|
||||
changedPrototypes = _prototypes.LoadFromString(InitialPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes.ToList());
|
||||
|
||||
Assert.True(reloaded);
|
||||
reloaded = false;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.RegisterType(typeof(EntityPrototype));
|
||||
manager.LoadString(DOCUMENT);
|
||||
manager.LoadFromString(DOCUMENT);
|
||||
manager.Resync();
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
[Test]
|
||||
public void TestLoadString()
|
||||
{
|
||||
manager.LoadString(LoadStringDocument);
|
||||
manager.LoadFromString(LoadStringDocument);
|
||||
manager.Resync();
|
||||
|
||||
var prototype = manager.Index<EntityPrototype>(LoadStringTestDummyId);
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Robust.UnitTesting.Shared.Serialization
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
prototypeManager.RegisterType(typeof(EntityPrototype));
|
||||
prototypeManager.LoadString(Prototypes);
|
||||
prototypeManager.LoadFromString(Prototypes);
|
||||
prototypeManager.Resync();
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
@@ -46,7 +46,7 @@ entitiesImmutableList:
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
protoMan.RegisterType(typeof(EntityPrototype));
|
||||
protoMan.LoadString(Prototypes);
|
||||
protoMan.LoadFromString(Prototypes);
|
||||
protoMan.Resync();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user