mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d12a238ecc | ||
|
|
ac46a7845d | ||
|
|
60b526f653 | ||
|
|
d58e380dd9 | ||
|
|
96a098a0c2 | ||
|
|
c0d4e34089 | ||
|
|
e16e0f4bd0 | ||
|
|
d31ffd2794 | ||
|
|
04d94f87fc | ||
|
|
f809375389 | ||
|
|
530ea5f4e6 | ||
|
|
590a23d540 | ||
|
|
52351e8b11 | ||
|
|
f75a764ff3 | ||
|
|
d14f4f0ce3 | ||
|
|
5367570c7f | ||
|
|
e523a733c8 | ||
|
|
06af61106c | ||
|
|
2239c30924 | ||
|
|
f9f55b6862 | ||
|
|
83a5560850 | ||
|
|
9a8f139fb9 | ||
|
|
20dae60fd4 | ||
|
|
b4098668bb | ||
|
|
9397cc4a6b | ||
|
|
1601e75879 | ||
|
|
a7b9c87926 | ||
|
|
8fea42ff9a | ||
|
|
86d20a0ef1 | ||
|
|
09012ea4ff | ||
|
|
e68297eb93 | ||
|
|
c1a2e23ce2 | ||
|
|
f208f6bfa9 | ||
|
|
ae526e2e10 | ||
|
|
25549869b1 | ||
|
|
f71e81d204 | ||
|
|
e67812fdb4 | ||
|
|
aa44b1cb8a | ||
|
|
8ec75be244 | ||
|
|
48746b7bd3 | ||
|
|
a9791d2033 | ||
|
|
709f1f4284 | ||
|
|
907094a5c8 | ||
|
|
a35a5e1645 | ||
|
|
ad8a59a72f |
107
Robust.Analyzers/FriendAnalyzer.cs
Normal file
107
Robust.Analyzers/FriendAnalyzer.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class FriendAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
const string FriendAttribute = "Robust.Shared.Analyzers.FriendAttribute";
|
||||
|
||||
public const string DiagnosticId = "RA0002";
|
||||
|
||||
private const string Title = "Tried to access friend-only member";
|
||||
private const string MessageFormat = "Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes";
|
||||
private const string Description = "Make sure to specify the accessing class in the friends attribute.";
|
||||
private const string Category = "Usage";
|
||||
|
||||
[SuppressMessage("ReSharper", "RS2008")]
|
||||
private static readonly DiagnosticDescriptor Rule = new (DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true, Description);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSyntaxNodeAction(CheckFriendship, SyntaxKind.SimpleMemberAccessExpression);
|
||||
}
|
||||
|
||||
private void CheckFriendship(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not MemberAccessExpressionSyntax memberAccess)
|
||||
return;
|
||||
|
||||
// We only do something if our parent is one of a few types.
|
||||
switch (context.Node.Parent)
|
||||
{
|
||||
// If we're being assigned...
|
||||
case AssignmentExpressionSyntax assignParent:
|
||||
{
|
||||
if (assignParent.Left != memberAccess)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're being invoked...
|
||||
case InvocationExpressionSyntax:
|
||||
break;
|
||||
|
||||
// Otherwise, do nothing.
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the friend attribute
|
||||
var friendAttr = context.Compilation.GetTypeByMetadataName(FriendAttribute);
|
||||
|
||||
// Get the type that is containing this expression, or, the class where this is happening.
|
||||
if (context.ContainingSymbol?.ContainingType is not { } containingType)
|
||||
return;
|
||||
|
||||
// We check all of our children and get only the identifiers.
|
||||
foreach (var identifier in memberAccess.ChildNodes().Select(node => node as IdentifierNameSyntax))
|
||||
{
|
||||
if (identifier == null) continue;
|
||||
|
||||
// Get the type info of the identifier, so we can check the attributes...
|
||||
if (context.SemanticModel.GetTypeInfo(identifier).ConvertedType is not { } type)
|
||||
continue;
|
||||
|
||||
// Same-type access is always fine.
|
||||
if (SymbolEqualityComparer.Default.Equals(type, containingType))
|
||||
continue;
|
||||
|
||||
// Finally, get all attributes of the type, to check if we have any friend classes.
|
||||
foreach (var attribute in type.GetAttributes())
|
||||
{
|
||||
// If the attribute isn't the friend attribute, continue.
|
||||
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, friendAttr))
|
||||
continue;
|
||||
|
||||
// Check all types allowed in the friend attribute. (We assume there's only one constructor arg.)
|
||||
foreach (var constant in attribute.ConstructorArguments[0].Values)
|
||||
{
|
||||
// Check if the value is a type...
|
||||
if (constant.Value is not INamedTypeSymbol t)
|
||||
continue;
|
||||
|
||||
// If we find that the containing class is specified in the attribute, return! All is good.
|
||||
if (SymbolEqualityComparer.Default.Equals(containingType, t))
|
||||
return;
|
||||
}
|
||||
|
||||
// Not in a friend class! Report an error.
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(Rule, context.Node.GetLocation(),
|
||||
$"{context.Node.ToString().Split('.').LastOrDefault()}", $"{type.Name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
28
Robust.Benchmarks/NumericsHelpers/AddBenchmark.cs
Normal file
28
Robust.Benchmarks/NumericsHelpers/AddBenchmark.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers
|
||||
{
|
||||
public class AddBenchmark
|
||||
{
|
||||
[Params(32, 128)]
|
||||
public int N { get; set; }
|
||||
|
||||
private float[] _inputA = default!;
|
||||
private float[] _inputB = default!;
|
||||
private float[] _output = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_inputA = new float[N];
|
||||
_inputB = new float[N];
|
||||
_output = new float[N];
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Bench()
|
||||
{
|
||||
Shared.Maths.NumericsHelpers.Add(_inputA, _inputB, _output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Benchmarks
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,7 +871,7 @@ namespace Robust.Client.Console.Commands
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = internalGrid.GetChunk(chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
Robust.Client/Console/Commands/GridChunkBBCommand.cs
Normal file
17
Robust.Client/Console/Commands/GridChunkBBCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public class GridChunkBBCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showchunkbb";
|
||||
public string Description => "Displays chunk bounds for the purposes of rendering";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<GridChunkBoundsDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ClientContainerSystem : ContainerSystem
|
||||
{
|
||||
private readonly HashSet<IEntity> _updateQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
|
||||
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
}
|
||||
|
||||
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
|
||||
{
|
||||
_updateQueue.Add(ev.Entity);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (toUpdate.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private static void UpdateEntityRecursively(IEntity entity)
|
||||
{
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
237
Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs
Normal file
237
Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using static Robust.Shared.Containers.ContainerManagerComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
|
||||
private readonly HashSet<IEntity> _updateQueue = new();
|
||||
|
||||
public readonly Dictionary<EntityUid, IContainer> ExpectedEntities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
|
||||
SubscribeLocalEvent<EntityInitializedMessage>(HandleEntityInitialized);
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
|
||||
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
}
|
||||
|
||||
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
|
||||
{
|
||||
_updateQueue.Add(ev.Entity);
|
||||
}
|
||||
|
||||
private void HandleEntityInitialized(EntityInitializedMessage ev)
|
||||
{
|
||||
if (!ExpectedEntities.TryGetValue(ev.Entity.Uid, out var container))
|
||||
return;
|
||||
|
||||
RemoveExpectedEntity(ev.Entity.Uid);
|
||||
|
||||
if (container.Deleted)
|
||||
return;
|
||||
|
||||
container.Insert(ev.Entity);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ContainerManagerComponentState cast)
|
||||
return;
|
||||
|
||||
// Delete now-gone containers.
|
||||
List<string>? toDelete = null;
|
||||
foreach (var (id, container) in component.Containers)
|
||||
{
|
||||
if (!cast.ContainerSet.Any(data => data.Id == id))
|
||||
{
|
||||
container.Shutdown();
|
||||
toDelete ??= new List<string>();
|
||||
toDelete.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (toDelete != null)
|
||||
{
|
||||
foreach (var dead in toDelete)
|
||||
{
|
||||
component.Containers.Remove(dead);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new containers and update existing contents.
|
||||
|
||||
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet)
|
||||
{
|
||||
if (!component.Containers.TryGetValue(id, out var container))
|
||||
{
|
||||
container = ContainerFactory(component, containerType, id);
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
// sync show flag
|
||||
container.ShowContents = showEnts;
|
||||
container.OccludesLight = occludesLight;
|
||||
|
||||
// Remove gone entities.
|
||||
List<IEntity>? toRemove = null;
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entity.Uid))
|
||||
{
|
||||
toRemove ??= new List<IEntity>();
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var goner in toRemove)
|
||||
container.Remove(goner);
|
||||
}
|
||||
|
||||
// Remove entities that were expected, but have been removed from the container.
|
||||
List<EntityUid>? removedExpected = null;
|
||||
foreach (var entityUid in container.ExpectedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entityUid))
|
||||
{
|
||||
removedExpected ??= new List<EntityUid>();
|
||||
removedExpected.Add(entityUid);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedExpected != null)
|
||||
{
|
||||
foreach (var entityUid in removedExpected)
|
||||
RemoveExpectedEntity(entityUid);
|
||||
}
|
||||
|
||||
// Add new entities.
|
||||
foreach (var entityUid in entityUids)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(entityUid, out var entity))
|
||||
{
|
||||
AddExpectedEntity(entityUid, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!container.ContainedEntities.Contains(entity))
|
||||
container.Insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
|
||||
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
|
||||
|
||||
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
|
||||
newContainer.ID = id;
|
||||
newContainer.Manager = component;
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(EntityUid uid, IContainer container)
|
||||
{
|
||||
if (ExpectedEntities.ContainsKey(uid))
|
||||
return;
|
||||
|
||||
ExpectedEntities.Add(uid, container);
|
||||
container.ExpectedEntities.Add(uid);
|
||||
}
|
||||
|
||||
public void RemoveExpectedEntity(EntityUid uid)
|
||||
{
|
||||
if (!ExpectedEntities.TryGetValue(uid, out var container))
|
||||
return;
|
||||
|
||||
ExpectedEntities.Remove(uid);
|
||||
container.ExpectedEntities.Remove(uid);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (toUpdate.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private static void UpdateEntityRecursively(IEntity entity)
|
||||
{
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class GridChunkBoundsDebugSystem : EntitySystem
|
||||
{
|
||||
private GridChunkBoundsOverlay? _overlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.Assert(_overlay == null);
|
||||
_overlay = new GridChunkBoundsOverlay(
|
||||
IoCManager.Resolve<IEntityManager>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>());
|
||||
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(_overlay!);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
}
|
||||
|
||||
internal sealed class GridChunkBoundsOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager)
|
||||
{
|
||||
_entityManager = entManager;
|
||||
_eyeManager = eyeManager;
|
||||
_mapManager = mapManager;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(currentMap, viewport))
|
||||
{
|
||||
var mapGrid = (IMapGridInternal) grid;
|
||||
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
|
||||
|
||||
var worldPos = gridEnt.Transform.WorldPosition;
|
||||
var worldRot = gridEnt.Transform.WorldRotation;
|
||||
|
||||
foreach (var (_, chunk) in mapGrid.GetMapChunks())
|
||||
{
|
||||
var chunkBounds = chunk.CalcWorldBounds(worldPos, worldRot);
|
||||
var aabb = chunkBounds.CalcBoundingBox();
|
||||
|
||||
// Calc world bounds for chunk.
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
args.WorldHandle.DrawRect(chunkBounds, Color.Green.WithAlpha(0.2f), true);
|
||||
args.WorldHandle.DrawRect(aabb, Color.Red, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,10 +111,11 @@ namespace Robust.Client.Graphics
|
||||
public MapCoordinates ScreenToMap(ScreenCoordinates point)
|
||||
{
|
||||
var (pos, window) = point;
|
||||
if (window != MainViewport.Window?.Id)
|
||||
|
||||
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
|
||||
return default;
|
||||
|
||||
return MainViewport.ScreenToMap(pos);
|
||||
return viewport.ScreenToMap(pos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.Graphics
|
||||
ScreenCoordinates CoordinatesToScreen(EntityCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a point from UI screen space to world space using the current camera.
|
||||
/// Unprojects a point from UI screen space to world space using the viewport under the screen coordinates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane
|
||||
@@ -64,7 +64,7 @@ namespace Robust.Client.Graphics
|
||||
MapCoordinates ScreenToMap(ScreenCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a point from UI screen space to world space using the current camera.
|
||||
/// Unprojects a point from UI screen space to world space using the main viewport.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane
|
||||
|
||||
@@ -19,18 +19,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
private void _drawGrids(Box2 worldBounds)
|
||||
private void _drawGrids(Viewport viewport, Box2 worldBounds, IEye eye)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = eye.Position.MapId;
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
{
|
||||
// fall back to the default eye's map
|
||||
_eyeManager.ClearCurrentEye();
|
||||
mapId = _eyeManager.CurrentMap;
|
||||
// fall back to nullspace map
|
||||
mapId = MapId.Nullspace;
|
||||
}
|
||||
|
||||
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? _currentViewport!.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
|
||||
var (gridProgram, _) = ActivateShaderInstance(_defaultShader.Handle);
|
||||
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
|
||||
@@ -52,11 +51,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var transform = compMan.GetComponent<ITransformComponent>(grid.GridEntityId);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
var worldPos = transform.WorldPosition;
|
||||
var worldRot = transform.WorldRotation;
|
||||
|
||||
foreach (var (_, chunk) in grid.GetMapChunks())
|
||||
{
|
||||
// Calc world bounds for chunk.
|
||||
if (!chunk.CalcWorldBounds().Intersects(in worldBounds))
|
||||
if (!chunk.CalcWorldAABB(worldPos, worldRot).Intersects(in worldBounds))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -201,9 +201,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
|
||||
private void DrawEntities(Viewport viewport, Box2 worldBounds)
|
||||
private void DrawEntities(Viewport viewport, Box2 worldBounds, IEye eye)
|
||||
{
|
||||
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
|
||||
var mapId = eye.Position.MapId;
|
||||
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -217,7 +218,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// TODO: Make this check more accurate.
|
||||
var widerBounds = worldBounds.Enlarged(5);
|
||||
|
||||
ProcessSpriteEntities(_eyeManager.CurrentMap, widerBounds, _drawingSpriteList);
|
||||
ProcessSpriteEntities(mapId, widerBounds, _drawingSpriteList);
|
||||
|
||||
var worldOverlays = new List<Overlay>();
|
||||
|
||||
@@ -291,6 +292,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// scale can be passed with PostShader as variable in future
|
||||
var postShadeScale = 1.25f;
|
||||
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
|
||||
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
|
||||
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
|
||||
screenSpriteSize.Y = -screenSpriteSize.Y;
|
||||
|
||||
// I'm not 100% sure why it works, but without it post-shader
|
||||
@@ -467,13 +471,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
using (DebugGroup("Grids"))
|
||||
{
|
||||
_drawGrids(worldBounds);
|
||||
_drawGrids(viewport, worldBounds, eye);
|
||||
}
|
||||
|
||||
// We will also render worldspace overlays here so we can do them under / above entities as necessary
|
||||
using (DebugGroup("Entities"))
|
||||
{
|
||||
DrawEntities(viewport, worldBounds);
|
||||
DrawEntities(viewport, worldBounds, eye);
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldBounds);
|
||||
|
||||
@@ -324,11 +324,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return;
|
||||
}
|
||||
|
||||
var map = eye.Position.MapId;
|
||||
var mapId = eye.Position.MapId;
|
||||
|
||||
var (lights, count, expandedBounds) = GetLightsToRender(map, worldBounds);
|
||||
// If this map has lighting disabled, return
|
||||
if (!_mapManager.GetMapEntity(mapId).GetComponent<IMapComponent>().LightingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateOcclusionGeometry(map, expandedBounds, eye.Position.Position);
|
||||
var (lights, count, expandedBounds) = GetLightsToRender(mapId, worldBounds);
|
||||
|
||||
UpdateOcclusionGeometry(mapId, expandedBounds, eye.Position.Position);
|
||||
|
||||
DrawFov(viewport, eye);
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ namespace Robust.Client.Physics
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_lastRem = _gameTiming.CurTime;
|
||||
|
||||
SimulateWorld(frameTime, !_gameTiming.InSimulation || !_gameTiming.IsFirstTimePredicted);
|
||||
SimulateWorld(frameTime, _gameTiming.InPrediction);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
|
||||
@@ -763,29 +763,6 @@ namespace Robust.Client.UserInterface
|
||||
SetPositionInParent(Parent.ChildCount - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This searches recursively through all the children of "parent"
|
||||
/// and sets the Disabled value of any buttons found to "val"
|
||||
/// </summary>
|
||||
/// <param name="parent">The control which childrens get searched</param>
|
||||
/// <param name="val">The value to which disabled gets set</param>
|
||||
public void SetButtonDisabledRecursive(Control parent, bool val)
|
||||
{
|
||||
foreach (var child in parent.Children)
|
||||
{
|
||||
if (child is Button but)
|
||||
{
|
||||
but.Disabled = val;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.Children != null)
|
||||
{
|
||||
SetButtonDisabledRecursive(child, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when this control receives keyboard focus.
|
||||
/// </summary>
|
||||
|
||||
@@ -94,10 +94,6 @@ namespace Robust.Server
|
||||
private ILogHandler? _logHandler;
|
||||
private IGameLoop _mainLoop = default!;
|
||||
|
||||
private TimeSpan _lastTitleUpdate;
|
||||
private long _lastReceivedBytes;
|
||||
private long _lastSentBytes;
|
||||
|
||||
private string? _shutdownReason;
|
||||
|
||||
private readonly ManualResetEventSlim _shutdownEvent = new(false);
|
||||
@@ -515,28 +511,6 @@ namespace Robust.Server
|
||||
_mainLoop = gameLoop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the console window title with performance statistics.
|
||||
/// </summary>
|
||||
private void UpdateTitle()
|
||||
{
|
||||
if (!Environment.UserInteractive || System.Console.IsInputRedirected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// every 1 second update stats in the console window title
|
||||
if ((_time.RealTime - _lastTitleUpdate).TotalSeconds < 1.0)
|
||||
return;
|
||||
|
||||
var netStats = UpdateBps();
|
||||
System.Console.Title = string.Format("FPS: {0:N2} SD: {1:N2}ms | Net: ({2}) | Memory: {3:N0} KiB",
|
||||
Math.Round(_time.FramesPerSecondAvg, 2),
|
||||
_time.RealFrameTimeStdDev.TotalMilliseconds,
|
||||
netStats,
|
||||
Process.GetCurrentProcess().PrivateMemorySize64 >> 10);
|
||||
_lastTitleUpdate = _time.RealTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the server settings from the ConfigurationManager.
|
||||
@@ -593,22 +567,9 @@ namespace Robust.Server
|
||||
}
|
||||
}
|
||||
|
||||
private string UpdateBps()
|
||||
{
|
||||
var stats = IoCManager.Resolve<IServerNetManager>().Statistics;
|
||||
|
||||
var bps =
|
||||
$"Send: {(stats.SentBytes - _lastSentBytes) >> 10:N0} KiB/s, Recv: {(stats.ReceivedBytes - _lastReceivedBytes) >> 10:N0} KiB/s";
|
||||
|
||||
_lastSentBytes = stats.SentBytes;
|
||||
_lastReceivedBytes = stats.ReceivedBytes;
|
||||
|
||||
return bps;
|
||||
}
|
||||
|
||||
private void Input(FrameEventArgs args)
|
||||
{
|
||||
_systemConsole.Update();
|
||||
_systemConsole.UpdateInput();
|
||||
|
||||
_network.ProcessPackets();
|
||||
_taskManager.ProcessPendingTasks();
|
||||
@@ -622,7 +583,7 @@ namespace Robust.Server
|
||||
// These are always the same on the server, there is no prediction.
|
||||
_time.LastRealTick = _time.CurTick;
|
||||
|
||||
UpdateTitle();
|
||||
_systemConsole.UpdateTick();
|
||||
|
||||
using (TickUsage.WithLabels("PreEngine").NewTimer())
|
||||
{
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
/// <summary>
|
||||
/// process input/output of the console. This needs to be called often.
|
||||
/// </summary>
|
||||
void Update();
|
||||
void UpdateInput();
|
||||
|
||||
/// <summary>
|
||||
/// Prints <paramref name="text" /> to the system console.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to write to the system console.</param>
|
||||
void Print(string text);
|
||||
|
||||
void UpdateTick();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Con = System.Console;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
@@ -12,6 +16,12 @@ namespace Robust.Server.Console
|
||||
[Dependency] private readonly IServerConsoleHost _conShell = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly IBaseServer _baseServer = default!;
|
||||
[Dependency] private readonly IServerNetManager _netManager = default!;
|
||||
[Dependency] private readonly IGameTiming _time = default!;
|
||||
|
||||
//
|
||||
// Command entry stuff.
|
||||
//
|
||||
|
||||
private readonly Dictionary<int, string> commandHistory = new();
|
||||
private string currentBuffer = "";
|
||||
@@ -21,6 +31,20 @@ namespace Robust.Server.Console
|
||||
private int tabCompleteIndex;
|
||||
private ConsoleKey lastKeyPressed = ConsoleKey.NoName;
|
||||
|
||||
//
|
||||
// Title update stuff.
|
||||
//
|
||||
|
||||
// This is ridiculously expensive to fetch for some reason.
|
||||
// I'm gonna just assume that this can't change during the lifetime of the process. I hope.
|
||||
// I want this ridiculous 0.1% CPU usage off my profiler.
|
||||
private readonly bool _userInteractive = Environment.UserInteractive;
|
||||
|
||||
private TimeSpan _lastTitleUpdate;
|
||||
private long _lastReceivedBytes;
|
||||
private long _lastSentBytes;
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Environment.UserInteractive)
|
||||
@@ -37,7 +61,50 @@ namespace Robust.Server.Console
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
public void UpdateTick()
|
||||
{
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the console window title with performance statistics.
|
||||
/// </summary>
|
||||
private void UpdateTitle()
|
||||
{
|
||||
if (!_userInteractive || System.Console.IsInputRedirected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// every 1 second update stats in the console window title
|
||||
if ((_time.RealTime - _lastTitleUpdate).TotalSeconds < 1.0)
|
||||
return;
|
||||
|
||||
var netStats = UpdateBps();
|
||||
var privateSize = Process.GetCurrentProcess().GetPrivateMemorySize64NotSlowHolyFuckingShitMicrosoft();
|
||||
|
||||
System.Console.Title = string.Format("FPS: {0:N2} SD: {1:N2}ms | Net: ({2}) | Memory: {3:N0} KiB",
|
||||
Math.Round(_time.FramesPerSecondAvg, 2),
|
||||
_time.RealFrameTimeStdDev.TotalMilliseconds,
|
||||
netStats,
|
||||
privateSize >> 10);
|
||||
_lastTitleUpdate = _time.RealTime;
|
||||
}
|
||||
|
||||
private string UpdateBps()
|
||||
{
|
||||
var stats = _netManager.Statistics;
|
||||
|
||||
var bps =
|
||||
$"Send: {(stats.SentBytes - _lastSentBytes) >> 10:N0} KiB/s, Recv: {(stats.ReceivedBytes - _lastReceivedBytes) >> 10:N0} KiB/s";
|
||||
|
||||
_lastSentBytes = stats.SentBytes;
|
||||
_lastReceivedBytes = stats.ReceivedBytes;
|
||||
|
||||
return bps;
|
||||
}
|
||||
|
||||
public void UpdateInput()
|
||||
{
|
||||
if (Con.IsInputRedirected)
|
||||
{
|
||||
@@ -161,7 +228,7 @@ namespace Robust.Server.Console
|
||||
{
|
||||
var currentLineCursor = Con.CursorTop;
|
||||
Con.SetCursorPosition(0, Con.CursorTop);
|
||||
Con.Write(new string(' ', Con.WindowWidth-1));
|
||||
Con.Write(new string(' ', Con.WindowWidth - 1));
|
||||
Con.SetCursorPosition(0, currentLineCursor);
|
||||
}
|
||||
|
||||
@@ -174,7 +241,8 @@ namespace Robust.Server.Console
|
||||
|
||||
if (tabCompleteList.Count == 0)
|
||||
{
|
||||
tabCompleteList = _conShell.RegisteredCommands.Keys.Where(key => key.StartsWith(currentBuffer)).ToList();
|
||||
tabCompleteList = _conShell.RegisteredCommands.Keys.Where(key => key.StartsWith(currentBuffer))
|
||||
.ToList();
|
||||
if (tabCompleteList.Count == 0)
|
||||
{
|
||||
return String.Empty;
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Robust.Server.Console
|
||||
{
|
||||
internal sealed class SystemConsoleManagerDummy : ISystemConsoleManager
|
||||
{
|
||||
public void Update()
|
||||
public void UpdateInput()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
@@ -11,5 +11,10 @@ namespace Robust.Server.Console
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void UpdateTick()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
Robust.Server/Containers/ContainerSystem.cs
Normal file
10
Robust.Server/Containers/ContainerSystem.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Robust.Server.Containers
|
||||
{
|
||||
public class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
// Seems like shared EntitySystems aren't registered, so this is here to register it on the server.
|
||||
// Registering the SharedContainerSystem causes conflicts on client where two entity systems are registered.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
internal class ViewSubscriberComponent : Component
|
||||
{
|
||||
public override string Name => "ViewSubscriber";
|
||||
|
||||
internal readonly HashSet<IPlayerSession> SubscribedSessions = new();
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public uint _renderOrder;
|
||||
private uint _renderOrder;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint RenderOrder
|
||||
{
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity System that handles subscribing and unsubscribing to PVS views.
|
||||
/// </summary>
|
||||
public class ViewSubscriberSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ViewSubscriberComponent, ComponentShutdown>(OnViewSubscriberShutdown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes the session to get PVS updates from the point of view of the specified entity.
|
||||
/// </summary>
|
||||
public void AddViewSubscriber(EntityUid uid, IPlayerSession session)
|
||||
{
|
||||
// This will throw if you pass in an invalid uid.
|
||||
var entity = EntityManager.GetEntity(uid);
|
||||
|
||||
// If the entity doesn't have the component, it will be added.
|
||||
var viewSubscriber = entity.EnsureComponent<ViewSubscriberComponent>();
|
||||
|
||||
if (viewSubscriber.SubscribedSessions.Contains(session))
|
||||
return; // Already subscribed, do nothing else.
|
||||
|
||||
viewSubscriber.SubscribedSessions.Add(session);
|
||||
session.AddViewSubscription(uid);
|
||||
|
||||
RaiseLocalEvent(uid, new ViewSubscriberAddedEvent(entity, session));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes the session from getting PVS updates from the point of view of the specified entity.
|
||||
/// </summary>
|
||||
public void RemoveViewSubscriber(EntityUid uid, IPlayerSession session)
|
||||
{
|
||||
if(!ComponentManager.TryGetComponent(uid, out ViewSubscriberComponent? viewSubscriber))
|
||||
return; // Entity didn't have any subscriptions, do nothing.
|
||||
|
||||
if (!viewSubscriber.SubscribedSessions.Remove(session))
|
||||
return; // Session wasn't subscribed, do nothing.
|
||||
|
||||
session.RemoveViewSubscription(uid);
|
||||
RaiseLocalEvent(uid, new ViewSubscriberRemovedEvent(EntityManager.GetEntity(uid), session));
|
||||
}
|
||||
|
||||
private void OnViewSubscriberShutdown(EntityUid uid, ViewSubscriberComponent component, ComponentShutdown _)
|
||||
{
|
||||
foreach (var session in component.SubscribedSessions)
|
||||
{
|
||||
session.RemoveViewSubscription(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a session subscribes to an entity's PVS view.
|
||||
/// </summary>
|
||||
public class ViewSubscriberAddedEvent : EntityEventArgs
|
||||
{
|
||||
public IEntity View { get; }
|
||||
public IPlayerSession Subscriber { get; }
|
||||
|
||||
public ViewSubscriberAddedEvent(IEntity view, IPlayerSession subscriber)
|
||||
{
|
||||
View = view;
|
||||
Subscriber = subscriber;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a session is unsubscribed from an entity's PVS view.
|
||||
/// Not raised when sessions are unsubscribed due to the component being removed.
|
||||
/// </summary>
|
||||
public class ViewSubscriberRemovedEvent : EntityEventArgs
|
||||
{
|
||||
public IEntity View { get; }
|
||||
public IPlayerSession Subscriber { get; }
|
||||
|
||||
public ViewSubscriberRemovedEvent(IEntity view, IPlayerSession subscriber)
|
||||
{
|
||||
View = view;
|
||||
Subscriber = subscriber;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -139,12 +140,15 @@ namespace Robust.Server.GameStates
|
||||
if (session.Status != SessionStatus.InGame || session.AttachedEntityUid is null)
|
||||
return viewers;
|
||||
|
||||
var query = _compMan.EntityQuery<ActorComponent>();
|
||||
viewers.Add(session.AttachedEntityUid.Value);
|
||||
|
||||
foreach (var actorComp in query)
|
||||
// This is awful, but we're not gonna add the list of view subscriptions to common session.
|
||||
if (session is not IPlayerSession playerSession)
|
||||
return viewers;
|
||||
|
||||
foreach (var uid in playerSession.ViewSubscriptions)
|
||||
{
|
||||
if (actorComp.PlayerSession == session)
|
||||
viewers.Add(actorComp.Owner.Uid);
|
||||
viewers.Add(uid);
|
||||
}
|
||||
|
||||
return viewers;
|
||||
@@ -276,8 +280,9 @@ namespace Robust.Server.GameStates
|
||||
if (_compMan.TryGetComponent<EyeComponent>(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
|
||||
//Always include the map entity of the eye
|
||||
//TODO: Add Map entity here
|
||||
//Always include the map entity of the eye, if it exists.
|
||||
if(_mapManager.MapExists(mapId))
|
||||
visibleEnts.Add(_mapManager.GetMapEntityId(mapId));
|
||||
|
||||
//Always include viewable ent itself
|
||||
visibleEnts.Add(eyeEuid);
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Robust.Server.GameStates
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
_logger.Log(LogLevel.Error, e, string.Empty);
|
||||
_logger.Log(LogLevel.Error, e, "Caught exception while generating mail.");
|
||||
}
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgState>();
|
||||
|
||||
@@ -366,11 +366,6 @@ namespace Robust.Server.Maps
|
||||
// Run Initialize on all components.
|
||||
FinishEntitiesInitialization();
|
||||
|
||||
// Regardless of what the frequency is we'll process fixtures sometime on map load for anything
|
||||
// that needs it before MapInit.
|
||||
var gridFixtures = EntitySystem.Get<GridFixtureSystem>();
|
||||
gridFixtures.Process();
|
||||
|
||||
// Run Startup on all components.
|
||||
FinishEntitiesStartup();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
@@ -21,47 +17,16 @@ namespace Robust.Server.Physics
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
|
||||
// Is delaying fixture updates a good idea? IDEK. We definitely can't do them on every tile changed
|
||||
// because if someone changes 50 tiles that will kill perf. We could probably just run it every Update
|
||||
// (and at specific times due to race condition stuff).
|
||||
// At any rate, cooldown given here if someone wants it. CD of 0 just runs it every tick.
|
||||
private float _cooldown;
|
||||
private float _accumulator;
|
||||
|
||||
private HashSet<MapChunk> _queuedChunks = new();
|
||||
/*
|
||||
* Currently we won't defer grid updates because content may alter a bunch of tiles then decide
|
||||
* to start anchroing entities for example.
|
||||
*/
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(PhysicsSystem));
|
||||
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
|
||||
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.GridFixtureUpdateRate, value => _cooldown = value, true);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_accumulator += frameTime;
|
||||
if (_accumulator < _cooldown) return;
|
||||
|
||||
_accumulator -= _cooldown;
|
||||
Process();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go through every dirty chunk and re-generate their fixtures.
|
||||
/// </summary>
|
||||
public void Process()
|
||||
{
|
||||
foreach (var chunk in _queuedChunks)
|
||||
{
|
||||
RegenerateCollision(chunk);
|
||||
}
|
||||
|
||||
_queuedChunks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,13 +35,7 @@ namespace Robust.Server.Physics
|
||||
/// <param name="ev"></param>
|
||||
private void HandleCollisionRegenerate(RegenerateChunkCollisionEvent ev)
|
||||
{
|
||||
if (_cooldown <= 0f)
|
||||
{
|
||||
RegenerateCollision(ev.Chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
_queuedChunks.Add(ev.Chunk);
|
||||
RegenerateCollision(ev.Chunk);
|
||||
}
|
||||
|
||||
private void RegenerateCollision(MapChunk chunk)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
@@ -32,6 +34,8 @@ namespace Robust.Server.Player
|
||||
void OnConnect();
|
||||
void OnDisconnect();
|
||||
|
||||
IReadOnlySet<EntityUid> ViewSubscriptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Persistent data for this player.
|
||||
/// </summary>
|
||||
@@ -43,5 +47,17 @@ namespace Robust.Server.Player
|
||||
/// and <see cref="DetachFromEntity"/> instead.
|
||||
/// </summary>
|
||||
internal void SetAttachedEntity(IEntity? entity);
|
||||
|
||||
/// <summary>
|
||||
/// Internal method to add an entity Uid to <see cref="ViewSubscriptions"/>.
|
||||
/// Do NOT use this outside of <see cref="ViewSubscriberSystem"/>.
|
||||
/// </summary>
|
||||
internal void AddViewSubscription(EntityUid eye);
|
||||
|
||||
/// <summary>
|
||||
/// Internal method to remove an entity Uid from <see cref="ViewSubscriptions"/>.
|
||||
/// Do NOT use this outside of <see cref="ViewSubscriberSystem"/>.
|
||||
/// </summary>
|
||||
internal void RemoveViewSubscription(EntityUid eye);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -35,6 +36,8 @@ namespace Robust.Server.Player
|
||||
UpdatePlayerState();
|
||||
}
|
||||
|
||||
private readonly HashSet<EntityUid> _viewSubscriptions = new();
|
||||
|
||||
[ViewVariables] public INetChannel ConnectedClient { get; }
|
||||
|
||||
[ViewVariables] public IEntity? AttachedEntity { get; set; }
|
||||
@@ -104,6 +107,7 @@ namespace Robust.Server.Player
|
||||
public NetUserId UserId { get; }
|
||||
|
||||
private readonly PlayerData _data;
|
||||
|
||||
[ViewVariables] public IPlayerData Data => _data;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -160,10 +164,13 @@ namespace Robust.Server.Player
|
||||
{
|
||||
Status = SessionStatus.Disconnected;
|
||||
|
||||
UnsubscribeAllViews();
|
||||
DetachFromEntity();
|
||||
UpdatePlayerState();
|
||||
}
|
||||
|
||||
public IReadOnlySet<EntityUid> ViewSubscriptions => _viewSubscriptions;
|
||||
|
||||
private void SetAttachedEntityName()
|
||||
{
|
||||
if (Name != null && AttachedEntity != null)
|
||||
@@ -193,6 +200,26 @@ namespace Robust.Server.Player
|
||||
UpdatePlayerState();
|
||||
}
|
||||
|
||||
void IPlayerSession.AddViewSubscription(EntityUid eye)
|
||||
{
|
||||
_viewSubscriptions.Add(eye);
|
||||
}
|
||||
|
||||
void IPlayerSession.RemoveViewSubscription(EntityUid eye)
|
||||
{
|
||||
_viewSubscriptions.Remove(eye);
|
||||
}
|
||||
|
||||
private void UnsubscribeAllViews()
|
||||
{
|
||||
var viewSubscriberSystem = EntitySystem.Get<ViewSubscriberSystem>();
|
||||
|
||||
foreach (var eye in _viewSubscriptions)
|
||||
{
|
||||
viewSubscriberSystem.RemoveViewSubscriber(eye, this);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayerState()
|
||||
{
|
||||
PlayerState.Status = Status;
|
||||
|
||||
@@ -103,6 +103,9 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public Vector2 RotateVec(in Vector2 vec)
|
||||
{
|
||||
// No calculation necessery when theta is zero
|
||||
if (Theta == 0) return vec;
|
||||
|
||||
var (x, y) = vec;
|
||||
var cos = Math.Cos(Theta);
|
||||
var sin = Math.Sin(Theta);
|
||||
|
||||
15
Robust.Shared/Analyzers/FriendAttribute.cs
Normal file
15
Robust.Shared/Analyzers/FriendAttribute.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Analyzers
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct)]
|
||||
public class FriendAttribute : Attribute
|
||||
{
|
||||
public readonly Type[] Friends;
|
||||
|
||||
public FriendAttribute(params Type[] friends)
|
||||
{
|
||||
Friends = friends;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +224,7 @@ namespace Robust.Shared
|
||||
/// outside of our viewport.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> MaxLightRadius =
|
||||
CVarDef.Create("light.max_radius", 20.0f, CVar.CLIENTONLY);
|
||||
CVarDef.Create("light.max_radius", 32.1f, CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* Lookup
|
||||
@@ -351,6 +351,13 @@ namespace Robust.Shared
|
||||
* PHYSICS
|
||||
*/
|
||||
|
||||
// - Contacts
|
||||
public static readonly CVarDef<int> ContactMultithreadThreshold =
|
||||
CVarDef.Create("physics.contact_multithread_threshold", 32);
|
||||
|
||||
public static readonly CVarDef<int> ContactMinimumThreads =
|
||||
CVarDef.Create("physics.contact_minimum_threads", 2);
|
||||
|
||||
// - Sleep
|
||||
public static readonly CVarDef<float> AngularSleepTolerance =
|
||||
CVarDef.Create("physics.angsleeptol", 2.0f / 180.0f * MathF.PI);
|
||||
@@ -366,8 +373,21 @@ namespace Robust.Shared
|
||||
CVarDef.Create("physics.timetosleep", 0.2f);
|
||||
|
||||
// - Solver
|
||||
public static readonly CVarDef<int> PositionConstraintsPerThread =
|
||||
CVarDef.Create("physics.position_constraints_per_thread", 32);
|
||||
|
||||
public static readonly CVarDef<int> PositionConstraintsMinimumThread =
|
||||
CVarDef.Create("physics.position_constraints_minimum_threads", 2);
|
||||
|
||||
public static readonly CVarDef<int> VelocityConstraintsPerThread =
|
||||
CVarDef.Create("physics.velocity_constraints_per_thread", 32);
|
||||
|
||||
public static readonly CVarDef<int> VelocityConstraintMinimumThreads =
|
||||
CVarDef.Create("physics.velocity_constraints_minimum_threads", 2);
|
||||
|
||||
// These are the minimum recommended by Box2D with the standard being 8 velocity 3 position iterations.
|
||||
// Trade-off is obviously performance vs how long it takes to stabilise.
|
||||
// PhysX opts for fewer velocity iterations and more position but they also have a different solver.
|
||||
public static readonly CVarDef<int> PositionIterations =
|
||||
CVarDef.Create("physics.positer", 3);
|
||||
|
||||
@@ -462,13 +482,6 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> MaxAngVelocity =
|
||||
CVarDef.Create("physics.maxangvelocity", 15f);
|
||||
|
||||
/// <summary>
|
||||
/// How frequently grid fixtures are updated. Given grid updates can be expensive they aren't run immediately.
|
||||
/// Set to 0 to run them immediately.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> GridFixtureUpdateRate =
|
||||
CVarDef.Create("physics.grid_fixture_update_rate", 0.2f);
|
||||
|
||||
/*
|
||||
* DISCORD
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace Robust.Shared.Containers
|
||||
[ViewVariables]
|
||||
public abstract IReadOnlyList<IEntity> ContainedEntities { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public abstract List<EntityUid> ExpectedEntities { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string ContainerType { get; }
|
||||
|
||||
|
||||
@@ -24,9 +24,13 @@ namespace Robust.Shared.Containers
|
||||
[DataField("ents")]
|
||||
private readonly List<IEntity> _containerList = new();
|
||||
|
||||
private readonly List<EntityUid> _expectedEntities = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;
|
||||
|
||||
public override List<EntityUid> ExpectedEntities => _expectedEntities;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ContainerType => ClassName;
|
||||
|
||||
|
||||
@@ -21,12 +21,11 @@ namespace Robust.Shared.Containers
|
||||
[NetworkedComponent()]
|
||||
public class ContainerManagerComponent : Component, IContainerManager
|
||||
{
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("containers")]
|
||||
private Dictionary<string, IContainer> _containers = new();
|
||||
public Dictionary<string, IContainer> Containers = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string Name => "ContainerContainer";
|
||||
@@ -37,12 +36,12 @@ namespace Robust.Shared.Containers
|
||||
base.OnRemove();
|
||||
|
||||
// IContianer.Shutdown modifies the _containers collection
|
||||
foreach (var container in _containers.Values.ToArray())
|
||||
foreach (var container in Containers.Values.ToArray())
|
||||
{
|
||||
container.Shutdown();
|
||||
}
|
||||
|
||||
_containers.Clear();
|
||||
Containers.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,7 +49,7 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var container in _containers)
|
||||
foreach (var container in Containers)
|
||||
{
|
||||
var baseContainer = (BaseContainer)container.Value;
|
||||
baseContainer.Manager = this;
|
||||
@@ -58,93 +57,13 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
if (!(curState is ContainerManagerComponentState cast))
|
||||
return;
|
||||
|
||||
// Delete now-gone containers.
|
||||
List<string>? toDelete = null;
|
||||
foreach (var (id, container) in _containers)
|
||||
{
|
||||
if (!cast.ContainerSet.Any(data => data.Id == id))
|
||||
{
|
||||
container.Shutdown();
|
||||
toDelete ??= new List<string>();
|
||||
toDelete.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (toDelete != null)
|
||||
{
|
||||
foreach (var dead in toDelete)
|
||||
{
|
||||
_containers.Remove(dead);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new containers and update existing contents.
|
||||
|
||||
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet)
|
||||
{
|
||||
if (!_containers.TryGetValue(id, out var container))
|
||||
{
|
||||
container = ContainerFactory(containerType, id);
|
||||
_containers.Add(id, container);
|
||||
}
|
||||
|
||||
// sync show flag
|
||||
container.ShowContents = showEnts;
|
||||
container.OccludesLight = occludesLight;
|
||||
|
||||
// Remove gone entities.
|
||||
List<IEntity>? toRemove = null;
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entity.Uid))
|
||||
{
|
||||
toRemove ??= new List<IEntity>();
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var goner in toRemove)
|
||||
{
|
||||
container.Remove(goner);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entities.
|
||||
foreach (var uid in entityUids)
|
||||
{
|
||||
var entity = Owner.EntityManager.GetEntity(uid);
|
||||
|
||||
if (!container.ContainedEntities.Contains(entity)) container.Insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IContainer ContainerFactory(string containerType, string id)
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
|
||||
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
|
||||
|
||||
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
|
||||
newContainer.ID = id;
|
||||
newContainer.Manager = this;
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
// naive implementation that just sends the full state of the component
|
||||
List<ContainerManagerComponentState.ContainerData> containerSet = new();
|
||||
|
||||
foreach (var container in _containers.Values)
|
||||
foreach (var container in Containers.Values)
|
||||
{
|
||||
var uidArr = new EntityUid[container.ContainedEntities.Count];
|
||||
|
||||
@@ -171,19 +90,19 @@ namespace Robust.Shared.Containers
|
||||
/// <inheritdoc />
|
||||
public IContainer GetContainer(string id)
|
||||
{
|
||||
return _containers[id];
|
||||
return Containers[id];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasContainer(string id)
|
||||
{
|
||||
return _containers.ContainsKey(id);
|
||||
return Containers.ContainsKey(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
|
||||
{
|
||||
var ret = _containers.TryGetValue(id, out var cont);
|
||||
var ret = Containers.TryGetValue(id, out var cont);
|
||||
container = cont!;
|
||||
return ret;
|
||||
}
|
||||
@@ -191,7 +110,7 @@ namespace Robust.Shared.Containers
|
||||
/// <inheritdoc />
|
||||
public bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container)
|
||||
{
|
||||
foreach (var contain in _containers.Values)
|
||||
foreach (var contain in Containers.Values)
|
||||
{
|
||||
if (!contain.Deleted && contain.Contains(entity))
|
||||
{
|
||||
@@ -207,7 +126,7 @@ namespace Robust.Shared.Containers
|
||||
/// <inheritdoc />
|
||||
public bool ContainsEntity(IEntity entity)
|
||||
{
|
||||
foreach (var container in _containers.Values)
|
||||
foreach (var container in Containers.Values)
|
||||
{
|
||||
if (!container.Deleted && container.Contains(entity)) return true;
|
||||
}
|
||||
@@ -218,7 +137,7 @@ namespace Robust.Shared.Containers
|
||||
/// <inheritdoc />
|
||||
public void ForceRemove(IEntity entity)
|
||||
{
|
||||
foreach (var container in _containers.Values)
|
||||
foreach (var container in Containers.Values)
|
||||
{
|
||||
if (container.Contains(entity)) container.ForceRemove(entity);
|
||||
}
|
||||
@@ -227,13 +146,13 @@ namespace Robust.Shared.Containers
|
||||
/// <inheritdoc />
|
||||
public void InternalContainerShutdown(IContainer container)
|
||||
{
|
||||
_containers.Remove(container.ID);
|
||||
Containers.Remove(container.ID);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(IEntity entity)
|
||||
{
|
||||
foreach (var containers in _containers.Values)
|
||||
foreach (var containers in Containers.Values)
|
||||
{
|
||||
if (containers.Contains(entity)) return containers.Remove(entity);
|
||||
}
|
||||
@@ -247,7 +166,7 @@ namespace Robust.Shared.Containers
|
||||
base.Shutdown();
|
||||
|
||||
// On shutdown we won't get to process remove events in the containers so this has to be manually done.
|
||||
foreach (var container in _containers.Values)
|
||||
foreach (var container in Containers.Values)
|
||||
{
|
||||
foreach (var containerEntity in container.ContainedEntities)
|
||||
{
|
||||
@@ -265,7 +184,7 @@ namespace Robust.Shared.Containers
|
||||
container.ID = id;
|
||||
container.Manager = this;
|
||||
|
||||
_containers[id] = container;
|
||||
Containers[id] = container;
|
||||
Dirty();
|
||||
return container;
|
||||
}
|
||||
@@ -366,7 +285,7 @@ namespace Robust.Shared.Containers
|
||||
|
||||
public AllContainersEnumerator(ContainerManagerComponent manager)
|
||||
{
|
||||
_enumerator = manager._containers.Values.GetEnumerator();
|
||||
_enumerator = manager.Containers.Values.GetEnumerator();
|
||||
Current = default;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,10 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
}
|
||||
|
||||
public override List<EntityUid> ExpectedEntities => _expectedEntities;
|
||||
|
||||
private IEntity? _containedEntity;
|
||||
private readonly List<EntityUid> _expectedEntities = new();
|
||||
// Used by ContainedEntities to avoid allocating.
|
||||
private readonly IEntity?[] _containedEntityArray = new IEntity[1];
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Robust.Shared.Containers
|
||||
/// </summary>
|
||||
IReadOnlyList<IEntity> ContainedEntities { get; }
|
||||
|
||||
List<EntityUid> ExpectedEntities { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of this container.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Containers
|
||||
{
|
||||
public class ContainerSystem : EntitySystem
|
||||
public abstract class SharedContainerSystem : EntitySystem
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(HandleParentChanged);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public interface ILookupWorldBox2Component
|
||||
{
|
||||
Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null);
|
||||
}
|
||||
}
|
||||
@@ -45,9 +45,10 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(ILookupWorldBox2Component))]
|
||||
[ComponentReference(typeof(IPhysBody))]
|
||||
[NetworkedComponent()]
|
||||
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks
|
||||
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks, ILookupWorldBox2Component
|
||||
{
|
||||
[DataField("status", readOnly: true)]
|
||||
private BodyStatus _bodyStatus = BodyStatus.OnGround;
|
||||
@@ -552,16 +553,17 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
worldPos ??= Owner.Transform.WorldPosition;
|
||||
worldRot ??= Owner.Transform.WorldRotation;
|
||||
var transform = new Transform(worldPos.Value, (float) worldRot.Value.Theta);
|
||||
|
||||
var worldPosValue = worldPos.Value;
|
||||
var worldRotValue = worldRot.Value;
|
||||
|
||||
var bounds = new Box2(worldPosValue, worldPosValue);
|
||||
var bounds = new Box2(transform.Position, transform.Position);
|
||||
|
||||
foreach (var fixture in _fixtures)
|
||||
{
|
||||
var boundy = fixture.Shape.CalculateLocalBounds(worldRotValue);
|
||||
bounds = bounds.Union(boundy.Translated(worldPosValue));
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
var boundy = fixture.Shape.ComputeAABB(transform, i);
|
||||
bounds = bounds.Union(boundy);
|
||||
}
|
||||
}
|
||||
|
||||
return bounds;
|
||||
@@ -1039,11 +1041,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private bool _predict;
|
||||
|
||||
/// <summary>
|
||||
/// As we defer updates need to store the MapId we used for broadphase.
|
||||
/// </summary>
|
||||
public MapId BroadphaseMapId { get; set; }
|
||||
|
||||
public IEnumerable<PhysicsComponent> GetBodiesIntersecting()
|
||||
{
|
||||
foreach (var entity in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(Owner.Transform.MapID, GetWorldAABB()))
|
||||
@@ -1060,7 +1057,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>The corresponding local point relative to the body's origin.</returns>
|
||||
public Vector2 GetLocalPoint(in Vector2 worldPoint)
|
||||
{
|
||||
return Physics.Transform.MulT(GetTransform(), worldPoint);
|
||||
return Transform.MulT(GetTransform(), worldPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public interface IMapComponent : IComponent
|
||||
{
|
||||
bool LightingEnabled { get; set; }
|
||||
MapId WorldMap { get; }
|
||||
void ClearMapId();
|
||||
}
|
||||
@@ -29,6 +30,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Map";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField(("lightingEnabled"))]
|
||||
public bool LightingEnabled { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapId WorldMap
|
||||
{
|
||||
@@ -46,7 +51,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new MapComponentState(_mapIndex);
|
||||
return new MapComponentState(_mapIndex, LightingEnabled);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -54,10 +59,11 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (!(curState is MapComponentState state))
|
||||
if (curState is not MapComponentState state)
|
||||
return;
|
||||
|
||||
_mapIndex = state.MapId;
|
||||
LightingEnabled = state.LightingEnabled;
|
||||
|
||||
((TransformComponent) Owner.Transform).ChangeMapId(_mapIndex);
|
||||
}
|
||||
@@ -70,10 +76,12 @@ namespace Robust.Shared.GameObjects
|
||||
internal class MapComponentState : ComponentState
|
||||
{
|
||||
public MapId MapId { get; }
|
||||
public bool LightingEnabled { get; }
|
||||
|
||||
public MapComponentState(MapId mapId)
|
||||
public MapComponentState(MapId mapId, bool lightingEnabled)
|
||||
{
|
||||
MapId = mapId;
|
||||
LightingEnabled = lightingEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,9 +302,10 @@ namespace Robust.Shared.GameObjects
|
||||
_parent = newParentEnt.Uid;
|
||||
ChangeMapId(newConcrete.MapID);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntParentChangedMessage(Owner, oldParent?.Owner));
|
||||
|
||||
// Cache new GridID before raising the event.
|
||||
GridID = GetGridIndex();
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntParentChangedMessage(Owner, oldParent?.Owner));
|
||||
}
|
||||
|
||||
// These conditions roughly emulate the effects of the code before I changed things,
|
||||
@@ -383,6 +384,11 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
_anchored = Owner.EntityManager.GetEntity(grid.GridEntityId).GetComponent<IMapGridComponent>().AnchorEntity(this);
|
||||
}
|
||||
// If no grid found then unanchor it.
|
||||
else
|
||||
{
|
||||
_anchored = false;
|
||||
}
|
||||
}
|
||||
else if (value && !_anchored && _mapManager.TryFindGridAt(MapPosition, out var grid))
|
||||
{
|
||||
@@ -673,7 +679,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void MapIdChanged(MapId oldId)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntMapIdChangedMessage(Owner, oldId));
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntMapIdChangedMessage(Owner, oldId));
|
||||
}
|
||||
|
||||
public void AttachParent(IEntity parent)
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Robust.Shared.GameObjects
|
||||
void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = true)
|
||||
where TEvent:EntityEventArgs;
|
||||
|
||||
void RaiseLocalEvent(EntityUid uid, EntityEventArgs args, bool broadcast = true);
|
||||
|
||||
void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs;
|
||||
@@ -82,6 +84,24 @@ namespace Robust.Shared.GameObjects
|
||||
RaiseEvent(EventSource.Local, args);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RaiseLocalEvent(EntityUid uid, EntityEventArgs args, bool broadcast = true)
|
||||
{
|
||||
var type = args.GetType();
|
||||
|
||||
if (_orderedEvents.Contains(type))
|
||||
{
|
||||
RaiseLocalOrdered(uid, args, broadcast);
|
||||
return;
|
||||
}
|
||||
|
||||
_eventTables.Dispatch(uid, type, args);
|
||||
|
||||
// we also broadcast it so the call site does not have to.
|
||||
if(broadcast)
|
||||
RaiseEvent(EventSource.Local, args);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
|
||||
where TComp : IComponent
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -7,6 +8,11 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
private List<SubBase>? _subscriptions;
|
||||
|
||||
/// <summary>
|
||||
/// A handle to allow subscription on this entity system's behalf.
|
||||
/// </summary>
|
||||
protected Subscriptions Subs { get; }
|
||||
|
||||
// NOTE: EntityEventHandler<T> and EntitySessionEventHandler<T> CANNOT BE ORDERED BETWEEN EACH OTHER.
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(
|
||||
@@ -136,6 +142,51 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptions = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API class to allow registering on an EntitySystem's behalf.
|
||||
/// Intended to support creation of boilerplate-reduction-methods
|
||||
/// that need to subscribe stuff on an entity system.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class Subscriptions
|
||||
{
|
||||
public EntitySystem System { get; }
|
||||
|
||||
internal Subscriptions(EntitySystem system)
|
||||
{
|
||||
System = system;
|
||||
}
|
||||
|
||||
// Intended for helper methods, so minimal API.
|
||||
|
||||
public void SubEvent<T>(
|
||||
EventSource src,
|
||||
EntityEventHandler<T> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
System.SubEvent(src, handler, before, after);
|
||||
}
|
||||
|
||||
public void SubSessionEvent<T>(
|
||||
EventSource src,
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
System.SubSessionEvent(src, handler, before, after);
|
||||
}
|
||||
|
||||
public void SubscribeLocalEvent<TComp, TEvent>(
|
||||
ComponentEventHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class SubBase
|
||||
{
|
||||
public abstract void Unsubscribe(EntitySystem sys, IEventBus bus);
|
||||
|
||||
@@ -30,6 +30,11 @@ namespace Robust.Shared.GameObjects
|
||||
IEnumerable<Type> IEntitySystem.UpdatesAfter => UpdatesAfter;
|
||||
IEnumerable<Type> IEntitySystem.UpdatesBefore => UpdatesBefore;
|
||||
|
||||
protected EntitySystem()
|
||||
{
|
||||
Subs = new Subscriptions(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize() { }
|
||||
|
||||
@@ -93,6 +98,11 @@ namespace Robust.Shared.GameObjects
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, args, broadcast);
|
||||
}
|
||||
|
||||
protected void RaiseLocalEvent(EntityUid uid, EntityEventArgs args, bool broadcast = true)
|
||||
{
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, args, broadcast);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Helpers
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -586,12 +587,24 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private static Box2 GetWorldAABB(in IEntity ent)
|
||||
{
|
||||
var pos = ent.Transform.WorldPosition;
|
||||
Vector2 pos;
|
||||
|
||||
if (ent.Deleted || !ent.TryGetComponent(out PhysicsComponent? physics))
|
||||
if (ent.Deleted)
|
||||
{
|
||||
pos = ent.Transform.WorldPosition;
|
||||
return new Box2(pos, pos);
|
||||
}
|
||||
|
||||
return physics.GetWorldAABB(pos);
|
||||
if (ent.TryGetContainerMan(out var manager))
|
||||
{
|
||||
return GetWorldAABB(manager.Owner);
|
||||
}
|
||||
|
||||
pos = ent.Transform.WorldPosition;
|
||||
|
||||
return ent.TryGetComponent(out ILookupWorldBox2Component? lookup) ?
|
||||
lookup.GetWorldAABB(pos) :
|
||||
new Box2(pos, pos);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -15,6 +15,13 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
SubscribeLocalEvent<MapGridComponent, ComponentRemove>(RemoveHandler);
|
||||
SubscribeLocalEvent<MapGridComponent, ComponentInit>(HandleGridInitialize);
|
||||
SubscribeLocalEvent<MapGridComponent, ComponentStartup>(HandleGridStartup);
|
||||
}
|
||||
|
||||
private void HandleGridStartup(EntityUid uid, MapGridComponent component, ComponentStartup args)
|
||||
{
|
||||
var msg = new GridStartupEvent(uid, component.GridIndex);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, msg);
|
||||
}
|
||||
|
||||
private void RemoveHandler(EntityUid uid, MapGridComponent component, ComponentRemove args)
|
||||
@@ -29,6 +36,18 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GridStartupEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid EntityUid { get; }
|
||||
public GridId GridId { get; }
|
||||
|
||||
public GridStartupEvent(EntityUid uid, GridId gridId)
|
||||
{
|
||||
EntityUid = uid;
|
||||
GridId = gridId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised whenever a grid is being initialized.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
@@ -29,13 +26,11 @@ namespace Robust.Shared.GameObjects
|
||||
* Raycasts for non-box shapes.
|
||||
* SetTransformIgnoreContacts for teleports (and anything else left on the physics body in Farseer)
|
||||
* Actual center of mass for shapes (currently just assumes center coordinate)
|
||||
* Circle offsets to entity.
|
||||
* TOI Solver (continuous collision detection)
|
||||
* Poly cutting
|
||||
* Chain shape
|
||||
* (Content) grenade launcher grenades that explode after time rather than impact.
|
||||
* pulling prediction
|
||||
* PVS + Collide allocations / performance
|
||||
* When someone yeets out of disposals need to have no collision on that object until they stop colliding
|
||||
* A bunch of objects have collision on round start
|
||||
* Need a way to specify conditional non-hard collisions (i.e. so items collide with players for IThrowCollide but can still be moved through freely but walls can't collide with them)
|
||||
@@ -190,6 +185,11 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
foreach (var controller in _controllers)
|
||||
{
|
||||
controller.Shutdown();
|
||||
}
|
||||
|
||||
_mapManager.MapCreated -= HandleMapCreated;
|
||||
_mapManager.MapDestroyed -= HandleMapDestroyed;
|
||||
}
|
||||
@@ -211,6 +211,7 @@ namespace Robust.Shared.GameObjects
|
||||
var map = _maps[eventArgs.Map];
|
||||
map.ContactManager.KinematicControllerCollision -= KinematicControllerCollision;
|
||||
|
||||
map.Shutdown();
|
||||
_maps.Remove(eventArgs.Map);
|
||||
Logger.DebugS("physics", $"Destroyed physics map for {eventArgs.Map}");
|
||||
}
|
||||
@@ -359,5 +360,13 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
_physicsManager.ClearTransforms();
|
||||
}
|
||||
|
||||
internal static (int Batches, int BatchSize) GetBatch(int count, int minimumBatchSize)
|
||||
{
|
||||
var batches = Math.Min((int) MathF.Floor((float) count / minimumBatchSize), Math.Max(1, Environment.ProcessorCount));
|
||||
var batchSize = (int) MathF.Ceiling((float) count / batches);
|
||||
|
||||
return (batches, batchSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
foreach (var ent in anchoredEnts.ToList()) // changing anchored modifies this set
|
||||
{
|
||||
if (!EntityManager.EntityExists(ent)) continue;
|
||||
|
||||
ComponentManager.GetComponent<TransformComponent>(ent).Anchored = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,17 @@ namespace Robust.Shared.Map
|
||||
|
||||
Box2i CalcLocalBounds();
|
||||
|
||||
Box2 CalcWorldBounds();
|
||||
// TODO: We can rely on the fixture instead for the bounds in the future but we also need to
|
||||
// update the rendering to account for it. Better working on accurate grid bounds after rotation IMO.
|
||||
/// <summary>
|
||||
/// Calculate the bounds of this chunk.
|
||||
/// </summary>
|
||||
Box2Rotated CalcWorldBounds(Vector2? worldPos = null, Angle? worldRot = null);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the AABB for this chunk.
|
||||
/// </summary>
|
||||
Box2 CalcWorldAABB(Vector2? worldPos = null, Angle? worldRot = null);
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a point is on top of a non-empty tile.
|
||||
|
||||
@@ -47,6 +47,11 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
Vector2 WorldPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the grid in world terms.
|
||||
/// </summary>
|
||||
Angle WorldRotation { get; set; }
|
||||
|
||||
Matrix3 WorldMatrix { get; }
|
||||
|
||||
Matrix3 InvWorldMatrix { get; }
|
||||
@@ -87,6 +92,12 @@ namespace Robust.Shared.Map
|
||||
/// <param name="tile">The tile to insert at the coordinates.</param>
|
||||
void SetTile(Vector2i gridIndices, Tile tile);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies many tiles inside of a chunk. Avoids regenerating collision until the end.
|
||||
/// </summary>
|
||||
/// <param name="tiles"></param>
|
||||
void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tiles inside the area that match the predicate.
|
||||
/// </summary>
|
||||
|
||||
@@ -245,19 +245,28 @@ namespace Robust.Shared.Map
|
||||
return _cachedBounds;
|
||||
}
|
||||
|
||||
public Box2 CalcWorldBounds()
|
||||
public Box2Rotated CalcWorldBounds(Vector2? gridPos = null, Angle? gridRot = null)
|
||||
{
|
||||
var worldPos = _grid.WorldPosition + Indices * _grid.TileSize * ChunkSize;
|
||||
gridRot ??= _grid.WorldRotation;
|
||||
gridPos ??= _grid.WorldPosition;
|
||||
var worldPos = gridPos.Value + gridRot.Value.RotateVec(Indices * _grid.TileSize * ChunkSize);
|
||||
|
||||
var localBounds = CalcLocalBounds();
|
||||
var ts = _grid.TileSize;
|
||||
|
||||
var scaledLocalBounds = new Box2(
|
||||
var scaledLocalBounds = new Box2Rotated(new Box2(
|
||||
localBounds.Left * ts,
|
||||
localBounds.Bottom * ts,
|
||||
localBounds.Right * ts,
|
||||
localBounds.Top * ts);
|
||||
localBounds.Top * ts).Translated(worldPos), gridRot.Value, worldPos);
|
||||
|
||||
return scaledLocalBounds.Translated(worldPos);
|
||||
return scaledLocalBounds;
|
||||
}
|
||||
|
||||
public Box2 CalcWorldAABB(Vector2? gridPos = null, Angle? gridRot = null)
|
||||
{
|
||||
var bounds = CalcWorldBounds(gridPos, gridRot);
|
||||
return bounds.CalcBoundingBox();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -112,6 +112,24 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public Angle WorldRotation
|
||||
{
|
||||
get
|
||||
{
|
||||
//TODO: Make grids real parents of entities.
|
||||
if(GridEntityId.IsValid())
|
||||
return _mapManager.EntityManager.GetEntity(GridEntityId).Transform.WorldRotation;
|
||||
return Angle.Zero;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mapManager.EntityManager.GetEntity(GridEntityId).Transform.WorldRotation = value;
|
||||
LastModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public Matrix3 WorldMatrix
|
||||
@@ -182,6 +200,7 @@ namespace Robust.Shared.Map
|
||||
// For now we'll just attach a fixture to each chunk.
|
||||
|
||||
// Not raising directed because the grid's EntityUid isn't set yet.
|
||||
// Don't call GridFixtureSystem directly because it's server-only.
|
||||
IoCManager
|
||||
.Resolve<IEntityManager>()
|
||||
.EventBus
|
||||
@@ -240,6 +259,26 @@ namespace Robust.Shared.Map
|
||||
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles)
|
||||
{
|
||||
var chunks = new HashSet<IMapChunkInternal>();
|
||||
|
||||
foreach (var (gridIndices, tile) in tiles)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
|
||||
chunks.Add(chunk);
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
}
|
||||
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
chunk.RegenerateCollision();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true, Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
@@ -284,20 +323,13 @@ namespace Robust.Shared.Map
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Circle worldArea, bool ignoreEmpty = true, Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius, worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
|
||||
var circleGridPos = new EntityCoordinates(GridEntityId, WorldToLocal(worldArea.Position));
|
||||
|
||||
foreach (var tile in GetTilesIntersecting(aabb, ignoreEmpty))
|
||||
{
|
||||
var local = GridTileToLocal(tile.GridIndices);
|
||||
var gridId = tile.GridIndex;
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var to = new EntityCoordinates(grid.GridEntityId, worldArea.Position);
|
||||
|
||||
if (!local.TryDistance(_entityManager, to, out var distance))
|
||||
if (!local.TryDistance(_entityManager, circleGridPos, out var distance))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -420,7 +452,7 @@ namespace Robust.Shared.Map
|
||||
RemoveFromSnapGridCell(TileIndicesFor(coords), euid);
|
||||
}
|
||||
|
||||
private (IMapChunk, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
|
||||
private (IMapChunkInternal, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
|
||||
{
|
||||
var gridChunkIndices = GridTileToChunkIndices(pos);
|
||||
var chunk = GetChunk(gridChunkIndices);
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -523,23 +524,33 @@ namespace Robust.Shared.Map
|
||||
/// <inheritdoc />
|
||||
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, [NotNullWhen(true)] out IMapGrid? grid)
|
||||
{
|
||||
// TODO: this won't actually "work" but tests are fucking me hard
|
||||
// We probably need to move these methods over to SharedBroadphaseSystem as they need to go through
|
||||
// physics to find grids intersecting a point anyway but the level of refactoring required for that
|
||||
// will kill me.
|
||||
foreach (var mapGrid in _grids.Values)
|
||||
foreach (var (_, mapGrid) in _grids)
|
||||
{
|
||||
if (mapGrid.ParentMapId != mapId)
|
||||
continue;
|
||||
|
||||
if (!mapGrid.WorldBounds.Contains(worldPos))
|
||||
continue;
|
||||
var gridEnt = _entityManager.GetEntity(mapGrid.GridEntityId);
|
||||
|
||||
grid = mapGrid;
|
||||
return true;
|
||||
if (!gridEnt.TryGetComponent(out PhysicsComponent? body)) continue;
|
||||
|
||||
var transform = new Transform(gridEnt.Transform.WorldPosition, (float) gridEnt.Transform.WorldRotation);
|
||||
|
||||
// We can't rely on the broadphase proxies existing as they're deferred until physics requires the update.
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
// TODO: Need TestPoint from Box2D
|
||||
if (fixture.Shape.ComputeAABB(transform, i).Contains(worldPos))
|
||||
{
|
||||
grid = mapGrid;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid = default;
|
||||
grid = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -551,9 +562,44 @@ namespace Robust.Shared.Map
|
||||
|
||||
public IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldArea)
|
||||
{
|
||||
// TODO: Unfortunately can't use BroadphaseSystem here as it will explode. Need to suss it out with DI
|
||||
// TryFindGridAt works as is and helps with grid traversals.
|
||||
return _grids.Values.Where(g => g.ParentMapId == mapId && g.WorldBounds.Intersects(worldArea));
|
||||
foreach (var (_, grid) in _grids)
|
||||
{
|
||||
if (grid.ParentMapId != mapId) continue;
|
||||
|
||||
var found = false;
|
||||
|
||||
// TODO: Future optimisation; we can probably do a very fast check with no fixtures
|
||||
// by just knowing the chunk bounds of the grid and then checking that first
|
||||
// this will mainly be helpful once we get multiple grids.
|
||||
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
|
||||
var body = gridEnt.GetComponent<PhysicsComponent>();
|
||||
var transform = new Transform(gridEnt.Transform.WorldPosition, (float) gridEnt.Transform.WorldRotation);
|
||||
|
||||
if (body.FixtureCount > 0)
|
||||
{
|
||||
// We can't rely on the broadphase proxies existing as they're deferred until physics requires the update.
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
// TODO: Need to use CollisionManager to test detailed overlap
|
||||
if (fixture.Shape.ComputeAABB(transform, i).Intersects(worldArea))
|
||||
{
|
||||
yield return grid;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (worldArea.Contains(transform.Position))
|
||||
{
|
||||
yield return grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<GridId> FindGridIdsIntersecting(MapId mapId, Box2 worldArea, bool includeInvalid = false)
|
||||
|
||||
@@ -82,13 +82,8 @@ namespace Robust.Shared.Physics.Collision
|
||||
|
||||
/*
|
||||
* Farseer had this as a static class with a ThreadStatic DistanceInput
|
||||
*
|
||||
* I also had to add Point initializers everywhere for the manifold as I just used an array but
|
||||
* should also profile just using FixedArray2 / FixedArray3
|
||||
*/
|
||||
|
||||
private DistanceInput _input = new();
|
||||
|
||||
/// <summary>
|
||||
/// Test overlap between the two shapes.
|
||||
/// </summary>
|
||||
@@ -102,15 +97,16 @@ namespace Robust.Shared.Physics.Collision
|
||||
bool ICollisionManager.TestOverlap(IPhysShape shapeA, int indexA, IPhysShape shapeB, int indexB,
|
||||
in Transform xfA, in Transform xfB)
|
||||
{
|
||||
_input.ProxyA.Set(shapeA, indexA);
|
||||
_input.ProxyB.Set(shapeB, indexB);
|
||||
_input.TransformA = xfA;
|
||||
_input.TransformB = xfB;
|
||||
_input.UseRadii = true;
|
||||
// TODO: Make this a struct.
|
||||
var input = new DistanceInput();
|
||||
|
||||
SimplexCache cache;
|
||||
DistanceOutput output;
|
||||
DistanceManager.ComputeDistance(out output, out cache, _input);
|
||||
input.ProxyA.Set(shapeA, indexA);
|
||||
input.ProxyB.Set(shapeB, indexB);
|
||||
input.TransformA = xfA;
|
||||
input.TransformB = xfB;
|
||||
input.UseRadii = true;
|
||||
|
||||
DistanceManager.ComputeDistance(out var output, out _, input);
|
||||
|
||||
return output.Distance < 10.0f * float.Epsilon;
|
||||
}
|
||||
@@ -177,8 +173,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.PointCount = 0;
|
||||
|
||||
// Compute circle in frame of edge
|
||||
// TODO: Circle Position
|
||||
Vector2 Q = Transform.MulT(transformA, Transform.Mul(transformB, Vector2.Zero));
|
||||
Vector2 Q = Transform.MulT(transformA, Transform.Mul(transformB, circleB.Position));
|
||||
|
||||
Vector2 A = edgeA.Vertex1, B = edgeA.Vertex2;
|
||||
Vector2 e = B - A;
|
||||
@@ -227,13 +222,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.Type = ManifoldType.Circles;
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.LocalPoint = P;
|
||||
ManifoldPoint mp = new ManifoldPoint
|
||||
{
|
||||
Id = {Key = 0, Features = cf},
|
||||
//LocalPoint = circleB.Position
|
||||
LocalPoint = Vector2.Zero
|
||||
};
|
||||
manifold.Points[0] = mp;
|
||||
ref var mp = ref manifold.Points[0];
|
||||
mp.Id.Key = 0;
|
||||
mp.Id.Features = cf;
|
||||
mp.LocalPoint = circleB.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -269,13 +261,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.Type = ManifoldType.Circles;
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.LocalPoint = P;
|
||||
ManifoldPoint mp = new ManifoldPoint
|
||||
{
|
||||
Id = {Key = 0, Features = cf},
|
||||
//LocalPoint = circleB.Position
|
||||
LocalPoint = Vector2.Zero
|
||||
};
|
||||
manifold.Points[0] = mp;
|
||||
ref var mp = ref manifold.Points[0];
|
||||
mp.Id.Key = 0;
|
||||
mp.Id.Features = cf;
|
||||
mp.LocalPoint = circleB.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -304,14 +293,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.Type = ManifoldType.FaceA;
|
||||
manifold.LocalNormal = n;
|
||||
manifold.LocalPoint = A;
|
||||
ManifoldPoint mp2 = new ManifoldPoint
|
||||
{
|
||||
Id = {Key = 0, Features = cf},
|
||||
//LocalPoint = circleB.Position
|
||||
LocalPoint = Vector2.Zero
|
||||
};
|
||||
|
||||
manifold.Points[0] = mp2;
|
||||
ref var mp2 = ref manifold.Points[0];
|
||||
mp2.Id.Key = 0;
|
||||
mp2.Id.Features = cf;
|
||||
mp2.LocalPoint = circleB.Position;
|
||||
}
|
||||
|
||||
public void CollideCircles(ref Manifold manifold, PhysShapeCircle circleA, in Transform xfA,
|
||||
@@ -320,9 +305,8 @@ namespace Robust.Shared.Physics.Collision
|
||||
{
|
||||
manifold.PointCount = 0;
|
||||
|
||||
// TODO Circle / shape offsets
|
||||
Vector2 pA = Transform.Mul(xfA, Vector2.Zero);
|
||||
Vector2 pB = Transform.Mul(xfB, Vector2.Zero);
|
||||
Vector2 pA = Transform.Mul(xfA, circleA.Position);
|
||||
Vector2 pB = Transform.Mul(xfB, circleB.Position);
|
||||
|
||||
Vector2 d = pB - pA;
|
||||
float distSqr = Vector2.Dot(d, d);
|
||||
@@ -333,16 +317,14 @@ namespace Robust.Shared.Physics.Collision
|
||||
}
|
||||
|
||||
manifold.Type = ManifoldType.Circles;
|
||||
manifold.LocalPoint = Vector2.Zero; // Also here
|
||||
manifold.LocalPoint = circleA.Position;
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.PointCount = 1;
|
||||
|
||||
ManifoldPoint p0 = manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
|
||||
p0.LocalPoint = Vector2.Zero; // Also here
|
||||
p0.Id.Key = 0;
|
||||
|
||||
manifold.Points[0] = p0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -364,6 +346,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
private class EPCollider
|
||||
{
|
||||
private float _polygonRadius;
|
||||
private float _angularSlop;
|
||||
|
||||
private TempPolygon _polygonB;
|
||||
|
||||
@@ -379,6 +362,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
internal EPCollider(IConfigurationManager configManager)
|
||||
{
|
||||
_polygonRadius = configManager.GetCVar(CVars.PolygonRadius);
|
||||
_angularSlop = configManager.GetCVar(CVars.AngularSlop);
|
||||
_polygonB = new TempPolygon(configManager);
|
||||
}
|
||||
|
||||
@@ -397,8 +381,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
|
||||
_xf = Transform.MulT(xfA, xfB);
|
||||
|
||||
// TODO: Centroid
|
||||
_centroidB = Transform.Mul(_xf, Vector2.Zero);
|
||||
_centroidB = Transform.Mul(_xf, polygonB.Centroid);
|
||||
|
||||
_v0 = edgeA.Vertex0;
|
||||
_v1 = edgeA.Vertex1;
|
||||
@@ -639,7 +622,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
primaryAxis = edgeAxis;
|
||||
}
|
||||
|
||||
ClipVertex[] ie = new ClipVertex[2];
|
||||
Span<ClipVertex> ie = stackalloc ClipVertex[2];
|
||||
ReferenceFace rf;
|
||||
if (primaryAxis.Type == EPAxisType.EdgeA)
|
||||
{
|
||||
@@ -661,21 +644,19 @@ namespace Robust.Shared.Physics.Collision
|
||||
int i1 = bestIndex;
|
||||
int i2 = i1 + 1 < _polygonB.Count ? i1 + 1 : 0;
|
||||
|
||||
ClipVertex c0 = ie[0];
|
||||
ref var c0 = ref ie[0];
|
||||
c0.V = _polygonB.Vertices[i1];
|
||||
c0.ID.Features.IndexA = 0;
|
||||
c0.ID.Features.IndexB = (byte) i1;
|
||||
c0.ID.Features.TypeA = (byte) ContactFeatureType.Face;
|
||||
c0.ID.Features.TypeB = (byte) ContactFeatureType.Vertex;
|
||||
ie[0] = c0;
|
||||
|
||||
ClipVertex c1 = ie[1];
|
||||
ref var c1 = ref ie[1];
|
||||
c1.V = _polygonB.Vertices[i2];
|
||||
c1.ID.Features.IndexA = 0;
|
||||
c1.ID.Features.IndexB = (byte) i2;
|
||||
c1.ID.Features.TypeA = (byte) ContactFeatureType.Face;
|
||||
c1.ID.Features.TypeB = (byte) ContactFeatureType.Vertex;
|
||||
ie[1] = c1;
|
||||
|
||||
if (_front)
|
||||
{
|
||||
@@ -697,21 +678,19 @@ namespace Robust.Shared.Physics.Collision
|
||||
else
|
||||
{
|
||||
manifold.Type = ManifoldType.FaceB;
|
||||
ClipVertex c0 = ie[0];
|
||||
ref var c0 = ref ie[0];
|
||||
c0.V = _v1;
|
||||
c0.ID.Features.IndexA = 0;
|
||||
c0.ID.Features.IndexB = (byte) primaryAxis.Index;
|
||||
c0.ID.Features.TypeA = (byte) ContactFeatureType.Vertex;
|
||||
c0.ID.Features.TypeB = (byte) ContactFeatureType.Face;
|
||||
ie[0] = c0;
|
||||
|
||||
ClipVertex c1 = ie[1];
|
||||
ref var c1 = ref ie[1];
|
||||
c1.V = _v2;
|
||||
c1.ID.Features.IndexA = 0;
|
||||
c1.ID.Features.IndexB = (byte) primaryAxis.Index;
|
||||
c1.ID.Features.TypeA = (byte) ContactFeatureType.Vertex;
|
||||
c1.ID.Features.TypeB = (byte) ContactFeatureType.Face;
|
||||
ie[1] = c1;
|
||||
|
||||
rf.i1 = primaryAxis.Index;
|
||||
rf.i2 = rf.i1 + 1 < _polygonB.Count ? rf.i1 + 1 : 0;
|
||||
@@ -727,10 +706,9 @@ namespace Robust.Shared.Physics.Collision
|
||||
|
||||
// Clip incident edge against extruded edge1 side edges.
|
||||
Span<ClipVertex> clipPoints1 = stackalloc ClipVertex[2];
|
||||
int np;
|
||||
|
||||
// Clip to box side 1
|
||||
np = ClipSegmentToLine(clipPoints1, ie, rf.sideNormal1, rf.sideOffset1, rf.i1);
|
||||
var np = ClipSegmentToLine(clipPoints1, ie, rf.sideNormal1, rf.sideOffset1, rf.i1);
|
||||
|
||||
if (np < 2)
|
||||
{
|
||||
@@ -767,7 +745,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
|
||||
if (separation <= _radius)
|
||||
{
|
||||
ManifoldPoint cp = manifold.Points[pointCount];
|
||||
ref var cp = ref manifold.Points[pointCount];
|
||||
|
||||
if (primaryAxis.Type == EPAxisType.EdgeA)
|
||||
{
|
||||
@@ -783,7 +761,6 @@ namespace Robust.Shared.Physics.Collision
|
||||
cp.Id.Features.IndexB = clipPoints2[i].ID.Features.IndexA;
|
||||
}
|
||||
|
||||
manifold.Points[pointCount] = cp;
|
||||
++pointCount;
|
||||
}
|
||||
}
|
||||
@@ -837,19 +814,17 @@ namespace Robust.Shared.Physics.Collision
|
||||
return axis;
|
||||
}
|
||||
|
||||
var angularSlop = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.LinearSlop);
|
||||
|
||||
// Adjacency
|
||||
if (Vector2.Dot(n, perp) >= 0.0f)
|
||||
{
|
||||
if (Vector2.Dot(n - _upperLimit, _normal) < -angularSlop)
|
||||
if (Vector2.Dot(n - _upperLimit, _normal) < -_angularSlop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Vector2.Dot(n - _lowerLimit, _normal) < -angularSlop)
|
||||
if (Vector2.Dot(n - _lowerLimit, _normal) < -_angularSlop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -881,7 +856,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.PointCount = 0;
|
||||
|
||||
// Compute circle position in the frame of the polygon.
|
||||
Vector2 c = Transform.Mul(xfB, Vector2.Zero); // TODO pos
|
||||
Vector2 c = Transform.Mul(xfB, circleB.Position);
|
||||
Vector2 cLocal = Transform.MulT(xfA, c);
|
||||
|
||||
// Find the min separating edge.
|
||||
@@ -923,13 +898,11 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.LocalNormal = polygonA.Normals[normalIndex];
|
||||
manifold.LocalPoint = (v1 + v2) * 0.5f;
|
||||
|
||||
ManifoldPoint p0 = manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
|
||||
p0.LocalPoint = Vector2.Zero; // TODO pos
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
|
||||
manifold.Points[0] = p0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -955,12 +928,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.LocalNormal.Y = manifold.LocalNormal.Y * factor;
|
||||
manifold.LocalPoint = v1;
|
||||
|
||||
ManifoldPoint p0b = manifold.Points[0];
|
||||
ref var p0b = ref manifold.Points[0];
|
||||
|
||||
p0b.LocalPoint = Vector2.Zero; // TODO pos
|
||||
p0b.LocalPoint = circleB.Position;
|
||||
p0b.Id.Key = 0;
|
||||
|
||||
manifold.Points[0] = p0b;
|
||||
}
|
||||
else if (u2 <= 0.0f)
|
||||
{
|
||||
@@ -981,12 +952,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.LocalNormal.Y = manifold.LocalNormal.Y * factor;
|
||||
manifold.LocalPoint = v2;
|
||||
|
||||
ManifoldPoint p0c = manifold.Points[0];
|
||||
ref var p0c = ref manifold.Points[0];
|
||||
|
||||
p0c.LocalPoint = Vector2.Zero; // TODO pos
|
||||
p0c.LocalPoint = circleB.Position;
|
||||
p0c.Id.Key = 0;
|
||||
|
||||
manifold.Points[0] = p0c;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1004,12 +973,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
manifold.LocalNormal = polygonA.Normals[vertIndex1];
|
||||
manifold.LocalPoint = faceCenter;
|
||||
|
||||
ManifoldPoint p0d = manifold.Points[0];
|
||||
ref var p0d = ref manifold.Points[0];
|
||||
|
||||
p0d.LocalPoint = Vector2.Zero; // TODO pos
|
||||
p0d.LocalPoint = circleB.Position;
|
||||
p0d.Id.Key = 0;
|
||||
|
||||
manifold.Points[0] = p0d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1130,7 +1097,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
|
||||
if (separation <= totalRadius)
|
||||
{
|
||||
ManifoldPoint cp = manifold.Points[pointCount];
|
||||
ref var cp = ref manifold.Points[pointCount];
|
||||
cp.LocalPoint = Transform.MulT(xf2, clipPoints2[i].V);
|
||||
cp.Id = clipPoints2[i].ID;
|
||||
|
||||
@@ -1144,8 +1111,6 @@ namespace Robust.Shared.Physics.Collision
|
||||
cp.Id.Features.TypeB = cf.TypeA;
|
||||
}
|
||||
|
||||
manifold.Points[pointCount] = cp;
|
||||
|
||||
pointCount++;
|
||||
}
|
||||
}
|
||||
@@ -1233,9 +1198,9 @@ namespace Robust.Shared.Physics.Collision
|
||||
if (distance0 * distance1 < 0.0f)
|
||||
{
|
||||
// Find intersection point of edge and plane
|
||||
float interp = distance0 / (distance0 - distance1);
|
||||
var interp = distance0 / (distance0 - distance1);
|
||||
|
||||
ClipVertex cv = vOut[numOut];
|
||||
ref var cv = ref vOut[numOut];
|
||||
|
||||
cv.V.X = v0.V.X + interp * (v1.V.X - v0.V.X);
|
||||
cv.V.Y = v0.V.Y + interp * (v1.V.Y - v0.V.Y);
|
||||
@@ -1246,8 +1211,6 @@ namespace Robust.Shared.Physics.Collision
|
||||
cv.ID.Features.TypeA = (byte) ContactFeatureType.Vertex;
|
||||
cv.ID.Features.TypeB = (byte) ContactFeatureType.Face;
|
||||
|
||||
vOut[numOut] = cv;
|
||||
|
||||
++numOut;
|
||||
}
|
||||
|
||||
@@ -1305,10 +1268,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
var v1 = Transform.Mul(xf, v1s[i]);
|
||||
|
||||
// Find deepest point for normal i.
|
||||
float si = float.MaxValue;
|
||||
var si = float.MaxValue;
|
||||
for (var j = 0; j < count2; ++j)
|
||||
{
|
||||
float sij = Vector2.Dot(n, v2s[j] - v1);
|
||||
var sij = Vector2.Dot(n, v2s[j] - v1);
|
||||
if (sij < si)
|
||||
{
|
||||
si = sij;
|
||||
@@ -1326,26 +1289,27 @@ namespace Robust.Shared.Physics.Collision
|
||||
return maxSeparation;
|
||||
}
|
||||
|
||||
private static void FindIncidentEdge(Span<ClipVertex> c, PolygonShape poly1, in Transform xf1, int edge1,
|
||||
PolygonShape poly2, in Transform xf2)
|
||||
private static void FindIncidentEdge(Span<ClipVertex> c, PolygonShape poly1, in Transform xf1, int edge1, PolygonShape poly2, in Transform xf2)
|
||||
{
|
||||
List<Vector2> normals1 = poly1.Normals;
|
||||
|
||||
int count2 = poly2.Vertices.Count;
|
||||
var count2 = poly2.Vertices.Count;
|
||||
List<Vector2> vertices2 = poly2.Vertices;
|
||||
List<Vector2> normals2 = poly2.Normals;
|
||||
|
||||
Debug.Assert(0 <= edge1 && edge1 < poly1.Vertices.Count);
|
||||
|
||||
// Get the normal of the reference edge in poly2's frame.
|
||||
Vector2 normal1 = Transform.MulT(xf2.Quaternion2D, Transform.Mul(xf1.Quaternion2D, normals1[edge1]));
|
||||
var normal1 = Transform.MulT(xf2.Quaternion2D, Transform.Mul(xf1.Quaternion2D, normals1[edge1]));
|
||||
|
||||
// Find the incident edge on poly2.
|
||||
int index = 0;
|
||||
float minDot = float.MaxValue;
|
||||
var index = 0;
|
||||
var minDot = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < count2; ++i)
|
||||
{
|
||||
float dot = Vector2.Dot(normal1, normals2[i]);
|
||||
var dot = Vector2.Dot(normal1, normals2[i]);
|
||||
|
||||
if (dot < minDot)
|
||||
{
|
||||
minDot = dot;
|
||||
@@ -1354,10 +1318,10 @@ namespace Robust.Shared.Physics.Collision
|
||||
}
|
||||
|
||||
// Build the clip vertices for the incident edge.
|
||||
int i1 = index;
|
||||
int i2 = i1 + 1 < count2 ? i1 + 1 : 0;
|
||||
var i1 = index;
|
||||
var i2 = i1 + 1 < count2 ? i1 + 1 : 0;
|
||||
|
||||
ClipVertex cv0 = c[0];
|
||||
ref var cv0 = ref c[0];
|
||||
|
||||
cv0.V = Transform.Mul(xf2, vertices2[i1]);
|
||||
cv0.ID.Features.IndexA = (byte) edge1;
|
||||
@@ -1365,16 +1329,12 @@ namespace Robust.Shared.Physics.Collision
|
||||
cv0.ID.Features.TypeA = (byte) ContactFeatureType.Face;
|
||||
cv0.ID.Features.TypeB = (byte) ContactFeatureType.Vertex;
|
||||
|
||||
c[0] = cv0;
|
||||
|
||||
ClipVertex cv1 = c[1];
|
||||
ref var cv1 = ref c[1];
|
||||
cv1.V = Transform.Mul(xf2, vertices2[i2]);
|
||||
cv1.ID.Features.IndexA = (byte) edge1;
|
||||
cv1.ID.Features.IndexB = (byte) i2;
|
||||
cv1.ID.Features.TypeA = (byte) ContactFeatureType.Face;
|
||||
cv1.ID.Features.TypeB = (byte) ContactFeatureType.Vertex;
|
||||
|
||||
c[1] = cv1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Physics.Collision
|
||||
@@ -130,7 +131,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
/// All contact scenarios must be expressed in one of these types.
|
||||
/// This structure is stored across time steps, so we keep it small.
|
||||
/// </summary>
|
||||
public struct Manifold
|
||||
public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
{
|
||||
public Vector2 LocalNormal;
|
||||
|
||||
@@ -144,48 +145,57 @@ namespace Robust.Shared.Physics.Collision
|
||||
/// <summary>
|
||||
/// Points of contact, can only be 0 -> 2.
|
||||
/// </summary>
|
||||
internal FixedArray2<ManifoldPoint> Points;
|
||||
internal ManifoldPoint[] Points;
|
||||
|
||||
public ManifoldType Type;
|
||||
}
|
||||
|
||||
internal struct FixedArray2<T>
|
||||
{
|
||||
private T _value0;
|
||||
private T _value1;
|
||||
|
||||
public T this[int index]
|
||||
public bool Equals(Manifold other)
|
||||
{
|
||||
get
|
||||
if (!(PointCount == other.PointCount &&
|
||||
Type == other.Type &&
|
||||
LocalNormal.Equals(other.LocalNormal) &&
|
||||
LocalPoint.Equals(other.LocalPoint))) return false;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return _value0;
|
||||
case 1:
|
||||
return _value1;
|
||||
default:
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
if (!Points[i].Equals(other.Points[i])) return false;
|
||||
}
|
||||
set
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EqualsApprox(Manifold other)
|
||||
{
|
||||
if (!(PointCount == other.PointCount &&
|
||||
Type == other.Type &&
|
||||
LocalNormal.EqualsApprox(other.LocalNormal) &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint))) return false;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
_value0 = value;
|
||||
break;
|
||||
case 1:
|
||||
_value1 = value;
|
||||
break;
|
||||
default:
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
if (!Points[i].EqualsApprox(other.Points[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EqualsApprox(Manifold other, double tolerance)
|
||||
{
|
||||
if (!(PointCount == other.PointCount &&
|
||||
Type == other.Type &&
|
||||
LocalNormal.EqualsApprox(other.LocalNormal, tolerance) &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint, tolerance))) return false;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
if (!Points[i].EqualsApprox(other.Points[i], tolerance)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ManifoldPoint
|
||||
public struct ManifoldPoint : IEquatable<ManifoldPoint>, IApproxEquatable<ManifoldPoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for the contact point between 2 shapes.
|
||||
@@ -239,5 +249,21 @@ namespace Robust.Shared.Physics.Collision
|
||||
hashcode = (hashcode * 397) ^ TangentImpulse.GetHashCode();
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
public bool EqualsApprox(ManifoldPoint other)
|
||||
{
|
||||
return Id == other.Id &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint) &&
|
||||
MathHelper.CloseTo(NormalImpulse, other.NormalImpulse) &&
|
||||
MathHelper.CloseTo(TangentImpulse, other.TangentImpulse);
|
||||
}
|
||||
|
||||
public bool EqualsApprox(ManifoldPoint other, double tolerance)
|
||||
{
|
||||
return Id == other.Id &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint, tolerance) &&
|
||||
MathHelper.CloseTo(NormalImpulse, other.NormalImpulse, tolerance) &&
|
||||
MathHelper.CloseTo(TangentImpulse, other.TangentImpulse, tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Collision.Shapes
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public sealed class EdgeShape : IPhysShape
|
||||
{
|
||||
internal Vector2 Centroid { get; set; } = Vector2.Zero;
|
||||
@@ -141,6 +141,20 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
return aabb;
|
||||
}
|
||||
|
||||
public Box2 ComputeAABB(Transform transform, int childIndex)
|
||||
{
|
||||
DebugTools.Assert(childIndex == 0);
|
||||
|
||||
var v1 = Transform.Mul(transform, Vertex1);
|
||||
var v2 = Transform.Mul(transform, Vertex2);
|
||||
|
||||
var lower = Vector2.ComponentMin(v1, v2);
|
||||
var upper = Vector2.ComponentMax(v1, v2);
|
||||
|
||||
var radius = new Vector2(_radius, _radius);
|
||||
return new Box2(lower - radius, upper + radius);
|
||||
}
|
||||
|
||||
public float CalculateArea()
|
||||
{
|
||||
// It's a line
|
||||
|
||||
@@ -36,15 +36,10 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
|
||||
ShapeType ShapeType { get; }
|
||||
|
||||
// TODO: Like raycasts these aren't using exact shapes so need to do that.
|
||||
bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the AABB of the shape.
|
||||
/// Calculate the AABB of the shape.
|
||||
/// </summary>
|
||||
/// <param name="rotation"></param>
|
||||
/// <returns></returns>
|
||||
Box2 CalculateLocalBounds(Angle rotation);
|
||||
Box2 ComputeAABB(Transform transform, int childIndex);
|
||||
|
||||
void ApplyState();
|
||||
|
||||
|
||||
@@ -72,6 +72,11 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
_radius = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.PolygonRadius);
|
||||
}
|
||||
|
||||
public Box2 ComputeAABB(Transform transform, int childIndex)
|
||||
{
|
||||
return new Box2Rotated(_localBounds.Translated(transform.Position), transform.Quaternion2D.Angle, transform.Position).CalcBoundingBox().Enlarged(_radius);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyState() { }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Physics.Collision.Shapes
|
||||
@@ -58,23 +59,19 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
}
|
||||
}
|
||||
|
||||
public bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot)
|
||||
{
|
||||
var bounds = CalculateLocalBounds(worldRot).Translated(worldPos);
|
||||
return bounds.Intersects(worldAABB);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2 CalculateLocalBounds(Angle rotation)
|
||||
{
|
||||
return new(-_radius, -_radius, _radius, _radius);
|
||||
}
|
||||
|
||||
public float CalculateArea()
|
||||
{
|
||||
return MathF.PI * _radius * _radius;
|
||||
}
|
||||
|
||||
public Box2 ComputeAABB(Transform transform, int childIndex)
|
||||
{
|
||||
DebugTools.Assert(childIndex == 0);
|
||||
|
||||
var p = transform.Position + Transform.Mul(transform.Quaternion2D, Position);
|
||||
return new Box2(p.X - _radius, p.Y - _radius, p.X + _radius, p.Y + _radius);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyState() { }
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
[ViewVariables]
|
||||
private Box2 _cachedBounds;
|
||||
|
||||
public Box2 ComputeAABB(Transform transform, int childIndex)
|
||||
{
|
||||
return new Box2Rotated(_cachedBounds.Translated(transform.Position), transform.Quaternion2D.Angle,
|
||||
transform.Position).CalcBoundingBox().Enlarged(_radius);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyState() { }
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
}
|
||||
|
||||
Centroid = ComputeCentroid(_vertices);
|
||||
_cachedAABB = CalculateAABB();
|
||||
|
||||
// Compute the polygon mass data
|
||||
// TODO: Update fixture. Maybe use events for it? Who tf knows.
|
||||
@@ -86,8 +85,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
|
||||
private List<Vector2> _vertices = new();
|
||||
|
||||
private Box2 _cachedAABB;
|
||||
|
||||
internal Vector2 Centroid { get; set; } = Vector2.Zero;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
@@ -164,31 +161,18 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
_radius = radius;
|
||||
}
|
||||
|
||||
public void SetAsBox(float width, float height)
|
||||
public void SetAsBox(float halfWidth, float halfHeight)
|
||||
{
|
||||
// TODO: Just have this set normals directly; look at Box2D to see how it does
|
||||
Vertices = new List<Vector2>()
|
||||
{
|
||||
new(-width, -height),
|
||||
new(width, -height),
|
||||
new(width, height),
|
||||
new(-width, height),
|
||||
new(-halfWidth, -halfHeight),
|
||||
new(halfWidth, -halfHeight),
|
||||
new(halfWidth, halfHeight),
|
||||
new(-halfWidth, halfHeight),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A temporary optimisation that bypasses GiftWrapping until we get proper AABB and Rect collisions
|
||||
/// </summary>
|
||||
internal void SetVertices(List<Vector2> vertices)
|
||||
{
|
||||
DebugTools.Assert(vertices.Count == 4, "Vertices optimisation only usable on rectangles");
|
||||
|
||||
// TODO: Get what the normals should be
|
||||
Vertices = vertices;
|
||||
// _normals = new List<Vector2>()
|
||||
// Verify on debug that the vertices skip was actually USEFUL
|
||||
DebugTools.Assert(Vertices == vertices);
|
||||
}
|
||||
|
||||
public bool Equals(IPhysShape? other)
|
||||
{
|
||||
if (other is not PolygonShape poly) return false;
|
||||
@@ -202,44 +186,21 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot)
|
||||
public Box2 ComputeAABB(Transform transform, int childIndex)
|
||||
{
|
||||
var aabb = CalculateLocalBounds(worldRot).Translated(worldPos);
|
||||
if (!worldAABB.Intersects(aabb)) return false;
|
||||
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculating the AABB for a polyshape isn't cheap so we'll just cache it whenever its vertices change.
|
||||
/// </summary>
|
||||
private Box2 CalculateAABB()
|
||||
{
|
||||
if (_vertices.Count == 0) return new Box2();
|
||||
|
||||
var lower = Vertices[0];
|
||||
DebugTools.Assert(childIndex == 0);
|
||||
var lower = Transform.Mul(transform, _vertices[0]);
|
||||
var upper = lower;
|
||||
|
||||
for (var i = 1; i < Vertices.Count; ++i)
|
||||
for (var i = 1; i < _vertices.Count; ++i)
|
||||
{
|
||||
var v = Vertices[i];
|
||||
var v = Transform.Mul(transform, _vertices[i]);
|
||||
lower = Vector2.ComponentMin(lower, v);
|
||||
upper = Vector2.ComponentMax(upper, v);
|
||||
}
|
||||
|
||||
var r = new Vector2(Radius, Radius);
|
||||
|
||||
return new Box2
|
||||
{
|
||||
BottomLeft = lower - r,
|
||||
TopRight = upper + r
|
||||
};
|
||||
}
|
||||
|
||||
public Box2 CalculateLocalBounds(Angle rotation)
|
||||
{
|
||||
return rotation == Angle.Zero ? _cachedAABB : new Box2Rotated(_cachedAABB, rotation).CalcBoundingBox();
|
||||
var r = new Vector2(_radius, _radius);
|
||||
return new Box2(lower - r, upper + r);
|
||||
}
|
||||
|
||||
public void ApplyState()
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Robust.Shared.Physics.Controllers
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public virtual void Shutdown() {}
|
||||
|
||||
/// <summary>
|
||||
/// Run before any map processing starts.
|
||||
/// </summary>
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -60,10 +60,10 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
/// </summary>
|
||||
internal event Action<Fixture, Fixture, float, Vector2>? KinematicControllerCollision;
|
||||
|
||||
private int _contactMultithreadThreshold;
|
||||
private int _contactMinimumThreads;
|
||||
|
||||
// TODO: Also need to clean the station up to not have 160 contacts on roundstart
|
||||
// TODO: CollideMultiCore
|
||||
private List<Contact> _startCollisions = new();
|
||||
private List<Contact> _endCollisions = new();
|
||||
|
||||
public ContactManager()
|
||||
{
|
||||
@@ -74,9 +74,30 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
public void Initialize()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.ContactMultithreadThreshold, OnContactMultithreadThreshold, true);
|
||||
configManager.OnValueChanged(CVars.ContactMinimumThreads, OnContactMinimumThreads, true);
|
||||
|
||||
InitializePool();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.UnsubValueChanged(CVars.ContactMultithreadThreshold, OnContactMultithreadThreshold);
|
||||
configManager.UnsubValueChanged(CVars.ContactMinimumThreads, OnContactMinimumThreads);
|
||||
}
|
||||
|
||||
private void OnContactMultithreadThreshold(int value)
|
||||
{
|
||||
_contactMultithreadThreshold = value;
|
||||
}
|
||||
|
||||
private void OnContactMinimumThreads(int value)
|
||||
{
|
||||
_contactMinimumThreads = value;
|
||||
}
|
||||
|
||||
private void InitializePool()
|
||||
{
|
||||
for (var i = 0; i < ContactPoolInitialSize; i++)
|
||||
@@ -247,8 +268,14 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
internal void Collide()
|
||||
{
|
||||
// Due to the fact some contacts may be removed (and we need to update this array as we iterate).
|
||||
// the length may not match the actual contact count, hence we track the index.
|
||||
var contacts = ArrayPool<Contact>.Shared.Rent(ContactCount);
|
||||
var index = 0;
|
||||
|
||||
// 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;)
|
||||
{
|
||||
if (contact == null) break;
|
||||
@@ -348,43 +375,103 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
continue;
|
||||
}
|
||||
|
||||
// The contact persists.
|
||||
contact.Update(_physicsManager, _startCollisions, _endCollisions);
|
||||
|
||||
contacts[index++] = contact;
|
||||
contact = contact.Next;
|
||||
}
|
||||
|
||||
foreach (var contact in _startCollisions)
|
||||
var status = ArrayPool<ContactStatus>.Shared.Rent(index);
|
||||
|
||||
// To avoid race conditions with the dictionary we'll cache all of the transforms up front.
|
||||
// Caching should provide better perf than multi-threading the GetTransform() as we can also re-use
|
||||
// these in PhysicsIsland as well.
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
if (!contact.IsTouching) continue;
|
||||
var contact = contacts[i];
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
|
||||
var fixtureA = contact.FixtureA!;
|
||||
var fixtureB = contact.FixtureB!;
|
||||
var bodyA = fixtureA.Body;
|
||||
var bodyB = fixtureB.Body;
|
||||
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyA.Owner.Uid, new StartCollideEvent(fixtureA, fixtureB));
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyB.Owner.Uid, new StartCollideEvent(fixtureB, fixtureA));
|
||||
_physicsManager.CreateTransform(bodyA);
|
||||
_physicsManager.CreateTransform(bodyB);
|
||||
}
|
||||
|
||||
foreach (var contact in _endCollisions)
|
||||
// Update contacts all at once.
|
||||
BuildManifolds(contacts, index, status);
|
||||
|
||||
// Single-threaded so content doesn't need to worry about race conditions.
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
var fixtureA = contact.FixtureA;
|
||||
var fixtureB = contact.FixtureB;
|
||||
var contact = contacts[i];
|
||||
|
||||
// If something under StartCollideEvent potentially nukes other contacts (e.g. if the entity is deleted)
|
||||
// then we'll just skip the EndCollide.
|
||||
if (fixtureA == null || fixtureB == null) continue;
|
||||
switch (status[i])
|
||||
{
|
||||
case ContactStatus.StartTouching:
|
||||
{
|
||||
if (!contact.IsTouching) continue;
|
||||
|
||||
var bodyA = fixtureA.Body;
|
||||
var bodyB = fixtureB.Body;
|
||||
var fixtureA = contact.FixtureA!;
|
||||
var fixtureB = contact.FixtureB!;
|
||||
var bodyA = fixtureA.Body;
|
||||
var bodyB = fixtureB.Body;
|
||||
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyA.Owner.Uid, new EndCollideEvent(fixtureA, fixtureB));
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyB.Owner.Uid, new EndCollideEvent(fixtureB, fixtureA));
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyA.Owner.Uid, new StartCollideEvent(fixtureA, fixtureB));
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyB.Owner.Uid, new StartCollideEvent(fixtureB, fixtureA));
|
||||
break;
|
||||
}
|
||||
case ContactStatus.Touching:
|
||||
break;
|
||||
case ContactStatus.EndTouching:
|
||||
{
|
||||
var fixtureA = contact.FixtureA;
|
||||
var fixtureB = contact.FixtureB;
|
||||
|
||||
// If something under StartCollideEvent potentially nukes other contacts (e.g. if the entity is deleted)
|
||||
// then we'll just skip the EndCollide.
|
||||
if (fixtureA == null || fixtureB == null) continue;
|
||||
|
||||
var bodyA = fixtureA.Body;
|
||||
var bodyB = fixtureB.Body;
|
||||
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyA.Owner.Uid, new EndCollideEvent(fixtureA, fixtureB));
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyB.Owner.Uid, new EndCollideEvent(fixtureB, fixtureA));
|
||||
break;
|
||||
}
|
||||
case ContactStatus.NoContact:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
_startCollisions.Clear();
|
||||
_endCollisions.Clear();
|
||||
ArrayPool<Contact>.Shared.Return(contacts);
|
||||
ArrayPool<ContactStatus>.Shared.Return(status);
|
||||
}
|
||||
|
||||
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status)
|
||||
{
|
||||
if (count > _contactMultithreadThreshold * _contactMinimumThreads)
|
||||
{
|
||||
var (batches, batchSize) = SharedPhysicsSystem.GetBatch(count, _contactMultithreadThreshold);
|
||||
|
||||
Parallel.For(0, batches, i =>
|
||||
{
|
||||
var start = i * batchSize;
|
||||
var end = Math.Min(start + batchSize, count);
|
||||
UpdateContacts(contacts, start, end, status);
|
||||
});
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateContacts(contacts, 0, count, status);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateContacts(Contact[] contacts, int start, int end, ContactStatus[] status)
|
||||
{
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
status[i] = contacts[i].Update(_physicsManager);
|
||||
}
|
||||
}
|
||||
|
||||
public void PreSolve(float frameTime)
|
||||
@@ -465,4 +552,12 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal enum ContactStatus : byte
|
||||
{
|
||||
NoContact = 0,
|
||||
StartTouching = 1,
|
||||
Touching = 2,
|
||||
EndTouching = 3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,10 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
#if DEBUG
|
||||
_debugPhysics = EntitySystem.Get<SharedDebugPhysicsSystem>();
|
||||
#endif
|
||||
Manifold = new Manifold
|
||||
{
|
||||
Points = new ManifoldPoint[2]
|
||||
};
|
||||
Reset(fixtureA, indexA, fixtureB, indexB);
|
||||
}
|
||||
|
||||
@@ -275,8 +279,8 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
PhysicsComponent bodyB = FixtureB?.Body!;
|
||||
IPhysShape shapeA = FixtureA?.Shape!;
|
||||
IPhysShape shapeB = FixtureB?.Shape!;
|
||||
var bodyATransform = physicsManager.GetTransform(bodyA);
|
||||
var bodyBTransform = physicsManager.GetTransform(bodyB);
|
||||
var bodyATransform = physicsManager.EnsureTransform(bodyA);
|
||||
var bodyBTransform = physicsManager.EnsureTransform(bodyB);
|
||||
|
||||
ContactSolver.InitializeManifold(ref Manifold, bodyATransform, bodyBTransform, shapeA.Radius, shapeB.Radius, out normal, points);
|
||||
}
|
||||
@@ -285,7 +289,8 @@ 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>
|
||||
internal void Update(IPhysicsManager physicsManager, List<Contact> startCollisions, List<Contact> endCollisions)
|
||||
/// <returns>What current status of the contact is (e.g. start touching, end touching, etc.)</returns>
|
||||
internal ContactStatus Update(IPhysicsManager physicsManager)
|
||||
{
|
||||
PhysicsComponent bodyA = FixtureA!.Body;
|
||||
PhysicsComponent bodyB = FixtureB!.Body;
|
||||
@@ -320,7 +325,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
// Match old contact ids to new contact ids and copy the
|
||||
// stored impulses to warm start the solver.
|
||||
for (int i = 0; i < Manifold.PointCount; ++i)
|
||||
for (var i = 0; i < Manifold.PointCount; ++i)
|
||||
{
|
||||
var mp2 = Manifold.Points[i];
|
||||
mp2.NormalImpulse = 0.0f;
|
||||
@@ -350,28 +355,31 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
}
|
||||
|
||||
IsTouching = touching;
|
||||
var status = ContactStatus.NoContact;
|
||||
|
||||
if (!wasTouching)
|
||||
{
|
||||
if (touching)
|
||||
{
|
||||
startCollisions.Add(this);
|
||||
status = ContactStatus.StartTouching;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!touching)
|
||||
{
|
||||
endCollisions.Add(this);
|
||||
status = ContactStatus.EndTouching;
|
||||
}
|
||||
}
|
||||
|
||||
if (sensor)
|
||||
return;
|
||||
|
||||
#if DEBUG
|
||||
_debugPhysics.HandlePreSolve(this, oldManifold);
|
||||
if (!sensor)
|
||||
{
|
||||
_debugPhysics.HandlePreSolve(this, oldManifold);
|
||||
}
|
||||
#endif
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -25,7 +25,7 @@ using Robust.Shared.Physics.Collision;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
internal sealed class ContactPositionConstraint
|
||||
internal struct ContactPositionConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of BodyA in the island.
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// </summary>
|
||||
public int IndexB { get; set; }
|
||||
|
||||
public Vector2[] LocalPoints = new Vector2[2];
|
||||
public Vector2[] LocalPoints;
|
||||
|
||||
public Vector2 LocalNormal;
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -47,6 +50,11 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
private ContactVelocityConstraint[] _velocityConstraints = Array.Empty<ContactVelocityConstraint>();
|
||||
private ContactPositionConstraint[] _positionConstraints = Array.Empty<ContactPositionConstraint>();
|
||||
|
||||
private int _velocityConstraintsPerThread;
|
||||
private int _velocityConstraintsMinimumThreads;
|
||||
private int _positionConstraintsPerThread;
|
||||
private int _positionConstraintsMinimumThreads;
|
||||
|
||||
public void LoadConfig(in IslandCfg cfg)
|
||||
{
|
||||
_warmStarting = cfg.WarmStarting;
|
||||
@@ -54,6 +62,10 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
_baumgarte = cfg.Baumgarte;
|
||||
_linearSlop = cfg.LinearSlop;
|
||||
_maxLinearCorrection = cfg.MaxLinearCorrection;
|
||||
_positionConstraintsPerThread = cfg.PositionConstraintsPerThread;
|
||||
_positionConstraintsMinimumThreads = cfg.PositionConstraintsMinimumThreads;
|
||||
_velocityConstraintsPerThread = cfg.VelocityConstraintsPerThread;
|
||||
_velocityConstraintsMinimumThreads = cfg.VelocityConstraintsMinimumThreads;
|
||||
}
|
||||
|
||||
public void Reset(SolverData data, int contactCount, Contact[] contacts)
|
||||
@@ -78,8 +90,31 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
for (var i = oldLength; i < _velocityConstraints.Length; i++)
|
||||
{
|
||||
_velocityConstraints[i] = new ContactVelocityConstraint();
|
||||
_positionConstraints[i] = new ContactPositionConstraint();
|
||||
var velocity = new ContactVelocityConstraint
|
||||
{
|
||||
K = new Vector2[2],
|
||||
Points = new VelocityConstraintPoint[2],
|
||||
NormalMass = new Vector2[2],
|
||||
};
|
||||
|
||||
for (var j = 0; j < 2; j++)
|
||||
{
|
||||
velocity.Points[j] = new VelocityConstraintPoint();
|
||||
}
|
||||
|
||||
_velocityConstraints[i] = velocity;
|
||||
|
||||
var position = new ContactPositionConstraint()
|
||||
{
|
||||
LocalPoints = new Vector2[2],
|
||||
};
|
||||
|
||||
for (var j = 0; j < 2; j++)
|
||||
{
|
||||
position.LocalPoints[j] = Vector2.Zero;
|
||||
}
|
||||
|
||||
_positionConstraints[i] = position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,14 +136,16 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
int pointCount = manifold.PointCount;
|
||||
DebugTools.Assert(pointCount > 0);
|
||||
|
||||
var velocityConstraint = _velocityConstraints[i];
|
||||
ref var velocityConstraint = ref _velocityConstraints[i];
|
||||
velocityConstraint.Friction = contact.Friction;
|
||||
velocityConstraint.Restitution = contact.Restitution;
|
||||
velocityConstraint.TangentSpeed = contact.TangentSpeed;
|
||||
velocityConstraint.IndexA = bodyA.IslandIndex[data.IslandIndex];
|
||||
velocityConstraint.IndexB = bodyB.IslandIndex[data.IslandIndex];
|
||||
|
||||
(velocityConstraint.InvMassA, velocityConstraint.InvMassB) = GetInvMass(bodyA, bodyB);
|
||||
var (invMassA, invMassB) = GetInvMass(bodyA, bodyB);
|
||||
|
||||
(velocityConstraint.InvMassA, velocityConstraint.InvMassB) = (invMassA, invMassB);
|
||||
velocityConstraint.InvIA = bodyA.InvI;
|
||||
velocityConstraint.InvIB = bodyB.InvI;
|
||||
velocityConstraint.ContactIndex = i;
|
||||
@@ -120,10 +157,10 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
velocityConstraint.NormalMass[x] = Vector2.Zero;
|
||||
}
|
||||
|
||||
var positionConstraint = _positionConstraints[i];
|
||||
ref var positionConstraint = ref _positionConstraints[i];
|
||||
positionConstraint.IndexA = bodyA.IslandIndex[data.IslandIndex];
|
||||
positionConstraint.IndexB = bodyB.IslandIndex[data.IslandIndex];
|
||||
(positionConstraint.InvMassA, positionConstraint.InvMassB) = GetInvMass(bodyA, bodyB);
|
||||
(positionConstraint.InvMassA, positionConstraint.InvMassB) = (invMassA, invMassB);
|
||||
// TODO: Dis
|
||||
// positionConstraint.LocalCenterA = bodyA._sweep.LocalCenter;
|
||||
// positionConstraint.LocalCenterB = bodyB._sweep.LocalCenter;
|
||||
@@ -139,10 +176,10 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
positionConstraint.RadiusB = radiusB;
|
||||
positionConstraint.Type = manifold.Type;
|
||||
|
||||
for (int j = 0; j < pointCount; ++j)
|
||||
for (var j = 0; j < pointCount; ++j)
|
||||
{
|
||||
var contactPoint = manifold.Points[j];
|
||||
var constraintPoint = velocityConstraint.Points[j];
|
||||
ref var constraintPoint = ref velocityConstraint.Points[j];
|
||||
|
||||
if (_warmStarting)
|
||||
{
|
||||
@@ -211,7 +248,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
for (var i = 0; i < _contactCount; ++i)
|
||||
{
|
||||
var velocityConstraint = _velocityConstraints[i];
|
||||
ref var velocityConstraint = ref _velocityConstraints[i];
|
||||
var positionConstraint = _positionConstraints[i];
|
||||
|
||||
var radiusA = positionConstraint.RadiusA;
|
||||
@@ -240,13 +277,12 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
DebugTools.Assert(manifold.PointCount > 0);
|
||||
|
||||
Transform xfA = new Transform(angleA);
|
||||
Transform xfB = new Transform(angleB);
|
||||
var xfA = new Transform(angleA);
|
||||
var xfB = new Transform(angleB);
|
||||
xfA.Position = centerA - Transform.Mul(xfA.Quaternion2D, localCenterA);
|
||||
xfB.Position = centerB - Transform.Mul(xfB.Quaternion2D, localCenterB);
|
||||
|
||||
Vector2 normal;
|
||||
InitializeManifold(ref manifold, xfA, xfB, radiusA, radiusB, out normal, points);
|
||||
InitializeManifold(ref manifold, xfA, xfB, radiusA, radiusB, out var normal, points);
|
||||
|
||||
velocityConstraint.Normal = normal;
|
||||
|
||||
@@ -254,7 +290,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
for (int j = 0; j < pointCount; ++j)
|
||||
{
|
||||
VelocityConstraintPoint vcp = velocityConstraint.Points[j];
|
||||
ref var vcp = ref velocityConstraint.Points[j];
|
||||
|
||||
vcp.RelativeVelocityA = points[j] - centerA;
|
||||
vcp.RelativeVelocityB = points[j] - centerB;
|
||||
@@ -359,10 +395,28 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
public void SolveVelocityConstraints()
|
||||
{
|
||||
// Here be dragons
|
||||
for (var i = 0; i < _contactCount; ++i)
|
||||
if (_contactCount > _velocityConstraintsPerThread * _velocityConstraintsMinimumThreads)
|
||||
{
|
||||
var velocityConstraint = _velocityConstraints[i];
|
||||
var (batches, batchSize) = SharedPhysicsSystem.GetBatch(_contactCount, _velocityConstraintsPerThread);
|
||||
Parallel.For(0, batches, i =>
|
||||
{
|
||||
var start = i * batchSize;
|
||||
var end = Math.Min(start + batchSize, _contactCount);
|
||||
SolveVelocityConstraints(start, end);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
SolveVelocityConstraints(0, _contactCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void SolveVelocityConstraints(int start, int end)
|
||||
{
|
||||
// Here be dragons
|
||||
for (var i = start; i < end; ++i)
|
||||
{
|
||||
ref var velocityConstraint = ref _velocityConstraints[i];
|
||||
|
||||
var indexA = velocityConstraint.IndexA;
|
||||
var indexB = velocityConstraint.IndexB;
|
||||
@@ -387,7 +441,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
// than friction.
|
||||
for (var j = 0; j < pointCount; ++j)
|
||||
{
|
||||
VelocityConstraintPoint velConstraintPoint = velocityConstraint.Points[j];
|
||||
ref var velConstraintPoint = ref velocityConstraint.Points[j];
|
||||
|
||||
// Relative velocity at contact
|
||||
var dv = vB + Vector2.Cross(wB, velConstraintPoint.RelativeVelocityB) - vA - Vector2.Cross(wA, velConstraintPoint.RelativeVelocityA);
|
||||
@@ -415,7 +469,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
// Solve normal constraints
|
||||
if (velocityConstraint.PointCount == 1)
|
||||
{
|
||||
VelocityConstraintPoint vcp = velocityConstraint.Points[0];
|
||||
ref var vcp = ref velocityConstraint.Points[0];
|
||||
|
||||
// Relative velocity at contact
|
||||
Vector2 dv = vB + Vector2.Cross(wB, vcp.RelativeVelocityB) - vA - Vector2.Cross(wA, vcp.RelativeVelocityA);
|
||||
@@ -472,8 +526,8 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
// = A * x + b'
|
||||
// b' = b - A * a;
|
||||
|
||||
VelocityConstraintPoint cp1 = velocityConstraint.Points[0];
|
||||
VelocityConstraintPoint cp2 = velocityConstraint.Points[1];
|
||||
ref var cp1 = ref velocityConstraint.Points[0];
|
||||
ref var cp2 = ref velocityConstraint.Points[1];
|
||||
|
||||
Vector2 a = new Vector2(cp1.NormalImpulse, cp2.NormalImpulse);
|
||||
DebugTools.Assert(a.X >= 0.0f && a.Y >= 0.0f);
|
||||
@@ -642,34 +696,52 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
public void StoreImpulses()
|
||||
{
|
||||
for (int i = 0; i < _contactCount; ++i)
|
||||
for (var i = 0; i < _contactCount; ++i)
|
||||
{
|
||||
ContactVelocityConstraint velocityConstraint = _velocityConstraints[i];
|
||||
var manifold = _contacts[velocityConstraint.ContactIndex].Manifold;
|
||||
ref var manifold = ref _contacts[velocityConstraint.ContactIndex].Manifold;
|
||||
|
||||
for (int j = 0; j < velocityConstraint.PointCount; ++j)
|
||||
for (var j = 0; j < velocityConstraint.PointCount; ++j)
|
||||
{
|
||||
ManifoldPoint point = manifold.Points[j];
|
||||
ref var point = ref manifold.Points[j];
|
||||
point.NormalImpulse = velocityConstraint.Points[j].NormalImpulse;
|
||||
point.TangentImpulse = velocityConstraint.Points[j].TangentImpulse;
|
||||
manifold.Points[j] = point;
|
||||
}
|
||||
|
||||
_contacts[velocityConstraint.ContactIndex].Manifold = manifold;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SolvePositionConstraints()
|
||||
{
|
||||
if (_contactCount > _positionConstraintsPerThread * _positionConstraintsMinimumThreads)
|
||||
{
|
||||
var unsolved = 0;
|
||||
|
||||
var (batches, batchSize) = SharedPhysicsSystem.GetBatch(_contactCount, _positionConstraintsPerThread);
|
||||
Parallel.For(0, batches, i =>
|
||||
{
|
||||
var start = i * batchSize;
|
||||
var end = Math.Min(start + batchSize, _contactCount);
|
||||
if (!SolvePositionConstraints(start, end))
|
||||
Interlocked.Increment(ref unsolved);
|
||||
});
|
||||
|
||||
return unsolved == 0;
|
||||
}
|
||||
|
||||
return SolvePositionConstraints(0, _contactCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to solve positions for all contacts specified.
|
||||
/// </summary>
|
||||
/// <returns>true if all positions solved</returns>
|
||||
public bool SolvePositionConstraints()
|
||||
public bool SolvePositionConstraints(int start, int end)
|
||||
{
|
||||
float minSeparation = 0.0f;
|
||||
|
||||
for (int i = 0; i < _contactCount; ++i)
|
||||
for (int i = start; i < end; ++i)
|
||||
{
|
||||
ContactPositionConstraint pc = _positionConstraints[i];
|
||||
var pc = _positionConstraints[i];
|
||||
|
||||
int indexA = pc.IndexA;
|
||||
int indexB = pc.IndexB;
|
||||
|
||||
@@ -24,7 +24,7 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
internal sealed class ContactVelocityConstraint
|
||||
internal struct ContactVelocityConstraint
|
||||
{
|
||||
public int ContactIndex { get; set; }
|
||||
|
||||
@@ -39,13 +39,13 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
public int IndexB { get; set; }
|
||||
|
||||
// Use 2 as its the max number of manifold points.
|
||||
public VelocityConstraintPoint[] Points = new VelocityConstraintPoint[2];
|
||||
public VelocityConstraintPoint[] Points;
|
||||
|
||||
public Vector2 Normal;
|
||||
|
||||
public Vector2[] NormalMass = new Vector2[2];
|
||||
public Vector2[] NormalMass;
|
||||
|
||||
public Vector2[] K = new Vector2[2];
|
||||
public Vector2[] K;
|
||||
|
||||
public float InvMassA;
|
||||
|
||||
@@ -62,17 +62,9 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
public float TangentSpeed;
|
||||
|
||||
public int PointCount;
|
||||
|
||||
public ContactVelocityConstraint()
|
||||
{
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
Points[i] = new VelocityConstraintPoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class VelocityConstraintPoint
|
||||
internal struct VelocityConstraintPoint
|
||||
{
|
||||
public Vector2 RelativeVelocityA;
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// this as a massless, rigid rod.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public sealed class DistanceJoint : Joint, IEquatable<DistanceJoint>
|
||||
{
|
||||
// Sloth note:
|
||||
|
||||
@@ -48,7 +48,6 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// It provides 2D translational friction and angular friction.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public sealed class FrictionJoint : Joint, IEquatable<FrictionJoint>
|
||||
{
|
||||
// Solver shared
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public abstract class Joint : IEquatable<Joint>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -326,7 +326,7 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
var body = Bodies[i];
|
||||
|
||||
// Didn't use the old variable names because they're hard to read
|
||||
var transform = _physicsManager.GetTransform(body);
|
||||
var transform = _physicsManager.EnsureTransform(body);
|
||||
var position = transform.Position;
|
||||
// DebugTools.Assert(!float.IsNaN(position.X) && !float.IsNaN(position.Y));
|
||||
var angle = transform.Quaternion2D.Angle;
|
||||
@@ -626,5 +626,9 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
public float Baumgarte;
|
||||
public float LinearSlop;
|
||||
public float MaxLinearCorrection;
|
||||
public int VelocityConstraintsPerThread;
|
||||
public int VelocityConstraintsMinimumThreads;
|
||||
public int PositionConstraintsPerThread;
|
||||
public int PositionConstraintsMinimumThreads;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
public sealed class PhysicsMap
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IIslandManager _islandManager = default!;
|
||||
|
||||
private SharedBroadphaseSystem _broadphaseSystem = default!;
|
||||
private SharedPhysicsSystem _physicsSystem = default!;
|
||||
|
||||
internal ContactManager ContactManager = new();
|
||||
@@ -124,6 +124,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
public PhysicsMap(MapId mapId)
|
||||
{
|
||||
MapId = mapId;
|
||||
_broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
_physicsSystem = EntitySystem.Get<SharedPhysicsSystem>();
|
||||
}
|
||||
|
||||
@@ -133,8 +134,21 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
ContactManager.Initialize();
|
||||
ContactManager.MapId = MapId;
|
||||
|
||||
_autoClearForces = _configManager.GetCVar(CVars.AutoClearForces);
|
||||
_configManager.OnValueChanged(CVars.AutoClearForces, value => _autoClearForces = value);
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange, true);
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
ContactManager.Shutdown();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.UnsubValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
}
|
||||
|
||||
private void OnAutoClearChange(bool value)
|
||||
{
|
||||
_autoClearForces = value;
|
||||
}
|
||||
|
||||
#region AddRemove
|
||||
@@ -385,7 +399,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Box2D does this at the end of a step and also here when there's a fixture update.
|
||||
// Given external stuff can move bodies we'll just do this here.
|
||||
// Unfortunately this NEEDS to be predicted to make pushing remotely fucking good.
|
||||
EntitySystem.Get<SharedBroadphaseSystem>().FindNewContacts(MapId);
|
||||
_broadphaseSystem.FindNewContacts(MapId);
|
||||
|
||||
var invDt = frameTime > 0.0f ? 1.0f / frameTime : 0.0f;
|
||||
var dtRatio = _invDt0 * frameTime;
|
||||
|
||||
@@ -101,6 +101,10 @@ namespace Robust.Shared.Physics
|
||||
CfgVar(CVars.Baumgarte, value => _islandCfg.Baumgarte = value);
|
||||
CfgVar(CVars.LinearSlop, value => _islandCfg.LinearSlop = value);
|
||||
CfgVar(CVars.MaxLinearCorrection, value => _islandCfg.MaxLinearCorrection = value);
|
||||
CfgVar(CVars.VelocityConstraintsPerThread, value => _islandCfg.VelocityConstraintsPerThread = value);
|
||||
CfgVar(CVars.VelocityConstraintMinimumThreads, value => _islandCfg.VelocityConstraintsMinimumThreads = value);
|
||||
CfgVar(CVars.PositionConstraintsPerThread, value => _islandCfg.PositionConstraintsPerThread = value);
|
||||
CfgVar(CVars.PositionConstraintsMinimumThread, value => _islandCfg.PositionConstraintsMinimumThreads = value);
|
||||
CfgVar(CVars.MaxLinVelocity, value =>
|
||||
{
|
||||
_maxLinearVelocityRaw = value;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
@@ -10,8 +10,12 @@ namespace Robust.Shared.Physics
|
||||
/// </summary>
|
||||
void ClearTransforms();
|
||||
|
||||
public bool CreateTransform(PhysicsComponent body);
|
||||
|
||||
public Transform EnsureTransform(PhysicsComponent body);
|
||||
|
||||
/// <summary>
|
||||
/// Get / create a cached transform for physics use.
|
||||
/// Get a cached transform for physics use.
|
||||
/// </summary>
|
||||
public Transform GetTransform(PhysicsComponent body);
|
||||
}
|
||||
@@ -26,18 +30,24 @@ namespace Robust.Shared.Physics
|
||||
_transforms.Clear();
|
||||
}
|
||||
|
||||
public bool CreateTransform(PhysicsComponent body)
|
||||
{
|
||||
if (_transforms.ContainsKey(body)) return false;
|
||||
|
||||
_transforms[body] = body.GetTransform();
|
||||
return true;
|
||||
}
|
||||
|
||||
public Transform EnsureTransform(PhysicsComponent body)
|
||||
{
|
||||
CreateTransform(body);
|
||||
return _transforms[body];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Transform GetTransform(PhysicsComponent body)
|
||||
{
|
||||
if (_transforms.TryGetValue(body, out var transform))
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
|
||||
transform = body.GetTransform();
|
||||
_transforms[body] = transform;
|
||||
|
||||
return transform;
|
||||
return _transforms[body];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Robust.Shared.Physics
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
//ProcessUpdates();
|
||||
ProcessUpdates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -480,6 +480,7 @@ namespace Robust.Shared.Physics
|
||||
|
||||
// Supposed to be wrapped in density but eh
|
||||
body.ResetMassData();
|
||||
body.Dirty();
|
||||
// TODO: Set newcontacts to true.
|
||||
}
|
||||
|
||||
@@ -521,6 +522,7 @@ namespace Robust.Shared.Physics
|
||||
|
||||
body.FixtureCount -= 1;
|
||||
body.ResetMassData();
|
||||
body.Dirty();
|
||||
}
|
||||
|
||||
private void SynchronizeFixtures(PhysicsComponent body, Vector2 worldPos)
|
||||
@@ -585,8 +587,8 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
var proxy = fixture.Proxies[i];
|
||||
|
||||
var aabb1 = fixture.Shape.CalculateLocalBounds(angle1).Translated(relativePos1.Position);
|
||||
var aabb2 = fixture.Shape.CalculateLocalBounds(angle2).Translated(relativePos2.Position);
|
||||
var aabb1 = fixture.Shape.ComputeAABB(relativePos1, i);
|
||||
var aabb2 = fixture.Shape.ComputeAABB(relativePos2, i);
|
||||
|
||||
var aabb = aabb1.Union(aabb2);
|
||||
proxy.AABB = aabb;
|
||||
@@ -614,15 +616,14 @@ namespace Robust.Shared.Physics
|
||||
_broadphasePositions[broadphase] = broadphaseOffset;
|
||||
}
|
||||
|
||||
// TODO: This needs a matrix transform ya dingus
|
||||
var relativePos1 = new Transform(transform1.Position - broadphaseOffset.Position,
|
||||
transform1.Quaternion2D.Angle - broadphaseOffset.Rotation);
|
||||
|
||||
var angle1 = new Angle(relativePos1.Quaternion2D.Angle);
|
||||
|
||||
for (var i = 0; i < proxyCount; i++)
|
||||
{
|
||||
var proxy = fixture.Proxies[i];
|
||||
var aabb = fixture.Shape.CalculateLocalBounds(angle1).Translated(relativePos1.Position);
|
||||
var aabb = fixture.Shape.ComputeAABB(relativePos1, i);
|
||||
proxy.AABB = aabb;
|
||||
var displacement = Vector2.Zero;
|
||||
broadphase.Tree.MoveProxy(proxy.ProxyId, aabb, displacement);
|
||||
@@ -704,11 +705,12 @@ namespace Robust.Shared.Physics
|
||||
|
||||
var posDiff = worldPos - broadphasePos;
|
||||
var rotDiff = worldRot - broadphaseRot;
|
||||
var transform = new Transform(posDiff, (float) rotDiff.Theta);
|
||||
var mapId = broadphase.Owner.Transform.MapID;
|
||||
|
||||
for (var i = 0; i < proxyCount; i++)
|
||||
{
|
||||
var aabb = fixture.Shape.CalculateLocalBounds(rotDiff).Translated(posDiff);
|
||||
var aabb = fixture.Shape.ComputeAABB(transform, i);
|
||||
var proxy = new FixtureProxy(aabb, fixture, i);
|
||||
proxy.ProxyId = broadphase.Tree.AddProxy(ref proxy);
|
||||
fixture.Proxies[i] = proxy;
|
||||
@@ -848,16 +850,32 @@ namespace Robust.Shared.Physics
|
||||
foreach (var broadphase in ComponentManager.EntityQuery<BroadphaseComponent>(true))
|
||||
{
|
||||
if (broadphase.Owner.Transform.MapID != mapId) continue;
|
||||
|
||||
// Always return map... for now
|
||||
if (broadphase.Owner.HasComponent<MapComponent>())
|
||||
{
|
||||
yield return broadphase;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (broadphase.Owner.TryGetComponent(out PhysicsComponent? physicsComponent) &&
|
||||
Intersects(physicsComponent, aabb))
|
||||
if (!broadphase.Owner.TryGetComponent(out PhysicsComponent? physicsComponent)) continue;
|
||||
|
||||
var transform = physicsComponent.GetTransform();
|
||||
var found = false;
|
||||
|
||||
// TODO: Need CollisionManager for accurate checks
|
||||
foreach (var fixture in physicsComponent.Fixtures)
|
||||
{
|
||||
yield return broadphase;
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
if (!fixture.Shape.ComputeAABB(transform, i).Intersects(aabb)) continue;
|
||||
yield return broadphase;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,22 +885,6 @@ namespace Robust.Shared.Physics
|
||||
return GetBroadphases(mapId, new Box2(worldPos, worldPos));
|
||||
}
|
||||
|
||||
private bool Intersects(PhysicsComponent physicsComponent, Box2 aabb)
|
||||
{
|
||||
var worldPos = physicsComponent.Owner.Transform.WorldPosition;
|
||||
var worldRot = physicsComponent.Owner.Transform.WorldRotation;
|
||||
var bodyAABB = physicsComponent.GetWorldAABB(worldPos, worldRot);
|
||||
|
||||
if (!aabb.Intersects(bodyAABB)) return false;
|
||||
|
||||
foreach (var fixture in physicsComponent.Fixtures)
|
||||
{
|
||||
if (fixture.Shape.Intersects(aabb, worldPos, worldRot)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the specified collision rectangle collides with any of the physBodies under management.
|
||||
/// Also fires the OnCollide event of the first managed physBody to intersect with the collider.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Timing
|
||||
@@ -16,7 +13,8 @@ namespace Robust.Shared.Timing
|
||||
private const int NumFrames = 60;
|
||||
|
||||
private readonly IStopwatch _realTimer = new Stopwatch();
|
||||
private readonly List<long> _realFrameTimes = new(NumFrames);
|
||||
private readonly long[] _realFrameTimes = new long[NumFrames];
|
||||
private int _frameIdx;
|
||||
private TimeSpan _lastRealTime;
|
||||
|
||||
/// <summary>
|
||||
@@ -182,9 +180,8 @@ namespace Robust.Shared.Timing
|
||||
_lastRealTime = curRealTime;
|
||||
|
||||
// update profiling
|
||||
if (_realFrameTimes.Count >= NumFrames)
|
||||
_realFrameTimes.RemoveAt(0);
|
||||
_realFrameTimes.Add(RealFrameTime.Ticks);
|
||||
_frameIdx = (1 + _frameIdx) % _realFrameTimes.Length;
|
||||
_realFrameTimes[_frameIdx] = RealFrameTime.Ticks;
|
||||
}
|
||||
|
||||
private TimeSpan CalcFrameTime()
|
||||
@@ -257,9 +254,6 @@ namespace Robust.Shared.Timing
|
||||
/// <returns></returns>
|
||||
private double CalcFpsAvg()
|
||||
{
|
||||
if (_realFrameTimes.Count == 0)
|
||||
return 0;
|
||||
|
||||
return 1 / (_realFrameTimes.Average() / TimeSpan.TicksPerSecond);
|
||||
}
|
||||
|
||||
@@ -270,7 +264,7 @@ namespace Robust.Shared.Timing
|
||||
private TimeSpan CalcRftStdDev()
|
||||
{
|
||||
var sum = _realFrameTimes.Sum();
|
||||
var count = _realFrameTimes.Count;
|
||||
var count = _realFrameTimes.Length;
|
||||
var avg = sum / (double)count;
|
||||
double devSquared = 0.0f;
|
||||
for (var i = 0; i < count; ++i)
|
||||
|
||||
376
Robust.Shared/Utility/FixedArray.cs
Normal file
376
Robust.Shared/Utility/FixedArray.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
#pragma warning disable 169
|
||||
|
||||
namespace Robust.Shared.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed size array stack allocation helpers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This helper class can be used to work around the limitation that <c>stackalloc</c>
|
||||
/// cannot be used with ref types.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To use, call it like so:
|
||||
/// <code>
|
||||
/// var span = FixedArray.Alloc32<object>(out _);
|
||||
/// </code>
|
||||
/// There is an <c>out</c> parameter that you should probably always discard (as shown in the example).
|
||||
/// This is so that stack space is properly allocated in your stack frame.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Do NOT under ANY CIRCUMSTANCE return the span given up the stack in any way.
|
||||
/// You will break the GC and stack and (worst of all) I will shed you.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This class cannot be used with variable size allocations.
|
||||
/// Just allocate a we'll-never-need-more bound like 128 and slim it down.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
|
||||
internal static class FixedArray
|
||||
{
|
||||
public static Span<T> Alloc2<T>(out FixedArray2<T> discard)
|
||||
{
|
||||
discard = new();
|
||||
return discard.AsSpan;
|
||||
}
|
||||
|
||||
public static Span<T> Alloc4<T>(out FixedArray4<T> discard)
|
||||
{
|
||||
discard = new();
|
||||
return discard.AsSpan;
|
||||
}
|
||||
|
||||
public static Span<T> Alloc8<T>(out FixedArray8<T> discard)
|
||||
{
|
||||
discard = new();
|
||||
return discard.AsSpan;
|
||||
}
|
||||
|
||||
public static Span<T> Alloc16<T>(out FixedArray16<T> discard)
|
||||
{
|
||||
discard = new();
|
||||
return discard.AsSpan;
|
||||
}
|
||||
|
||||
public static Span<T> Alloc32<T>(out FixedArray32<T> discard)
|
||||
{
|
||||
discard = new();
|
||||
return discard.AsSpan;
|
||||
}
|
||||
|
||||
public static Span<T> Alloc64<T>(out FixedArray64<T> discard)
|
||||
{
|
||||
discard = new();
|
||||
return discard.AsSpan;
|
||||
}
|
||||
|
||||
public static Span<T> Alloc128<T>(out FixedArray128<T> discard)
|
||||
{
|
||||
discard = new();
|
||||
return discard.AsSpan;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct FixedArray2<T>
|
||||
{
|
||||
private T _00;
|
||||
private T _01;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 2);
|
||||
}
|
||||
|
||||
internal struct FixedArray4<T>
|
||||
{
|
||||
private T _00;
|
||||
private T _01;
|
||||
private T _02;
|
||||
private T _03;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 4);
|
||||
}
|
||||
|
||||
internal struct FixedArray8<T>
|
||||
{
|
||||
private T _00;
|
||||
private T _01;
|
||||
private T _02;
|
||||
private T _03;
|
||||
private T _04;
|
||||
private T _05;
|
||||
private T _06;
|
||||
private T _07;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 8);
|
||||
}
|
||||
|
||||
internal struct FixedArray16<T>
|
||||
{
|
||||
private T _00;
|
||||
private T _01;
|
||||
private T _02;
|
||||
private T _03;
|
||||
private T _04;
|
||||
private T _05;
|
||||
private T _06;
|
||||
private T _07;
|
||||
private T _08;
|
||||
private T _09;
|
||||
private T _10;
|
||||
private T _11;
|
||||
private T _12;
|
||||
private T _13;
|
||||
private T _14;
|
||||
private T _15;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 16);
|
||||
}
|
||||
|
||||
internal struct FixedArray32<T>
|
||||
{
|
||||
private T _00;
|
||||
private T _01;
|
||||
private T _02;
|
||||
private T _03;
|
||||
private T _04;
|
||||
private T _05;
|
||||
private T _06;
|
||||
private T _07;
|
||||
private T _08;
|
||||
private T _09;
|
||||
private T _10;
|
||||
private T _11;
|
||||
private T _12;
|
||||
private T _13;
|
||||
private T _14;
|
||||
private T _15;
|
||||
private T _16;
|
||||
private T _17;
|
||||
private T _18;
|
||||
private T _19;
|
||||
private T _20;
|
||||
private T _21;
|
||||
private T _22;
|
||||
private T _23;
|
||||
private T _24;
|
||||
private T _25;
|
||||
private T _26;
|
||||
private T _27;
|
||||
private T _28;
|
||||
private T _29;
|
||||
private T _30;
|
||||
private T _31;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 32);
|
||||
}
|
||||
|
||||
internal struct FixedArray64<T>
|
||||
{
|
||||
private T _00;
|
||||
private T _01;
|
||||
private T _02;
|
||||
private T _03;
|
||||
private T _04;
|
||||
private T _05;
|
||||
private T _06;
|
||||
private T _07;
|
||||
private T _08;
|
||||
private T _09;
|
||||
private T _10;
|
||||
private T _11;
|
||||
private T _12;
|
||||
private T _13;
|
||||
private T _14;
|
||||
private T _15;
|
||||
private T _16;
|
||||
private T _17;
|
||||
private T _18;
|
||||
private T _19;
|
||||
private T _20;
|
||||
private T _21;
|
||||
private T _22;
|
||||
private T _23;
|
||||
private T _24;
|
||||
private T _25;
|
||||
private T _26;
|
||||
private T _27;
|
||||
private T _28;
|
||||
private T _29;
|
||||
private T _30;
|
||||
private T _31;
|
||||
private T _32;
|
||||
private T _33;
|
||||
private T _34;
|
||||
private T _35;
|
||||
private T _36;
|
||||
private T _37;
|
||||
private T _38;
|
||||
private T _39;
|
||||
private T _40;
|
||||
private T _41;
|
||||
private T _42;
|
||||
private T _43;
|
||||
private T _44;
|
||||
private T _45;
|
||||
private T _46;
|
||||
private T _47;
|
||||
private T _48;
|
||||
private T _49;
|
||||
private T _50;
|
||||
private T _51;
|
||||
private T _52;
|
||||
private T _53;
|
||||
private T _54;
|
||||
private T _55;
|
||||
private T _56;
|
||||
private T _57;
|
||||
private T _58;
|
||||
private T _59;
|
||||
private T _60;
|
||||
private T _61;
|
||||
private T _62;
|
||||
private T _63;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 64);
|
||||
}
|
||||
|
||||
internal struct FixedArray128<T>
|
||||
{
|
||||
private T _00;
|
||||
private T _01;
|
||||
private T _02;
|
||||
private T _03;
|
||||
private T _04;
|
||||
private T _05;
|
||||
private T _06;
|
||||
private T _07;
|
||||
private T _08;
|
||||
private T _09;
|
||||
private T _10;
|
||||
private T _11;
|
||||
private T _12;
|
||||
private T _13;
|
||||
private T _14;
|
||||
private T _15;
|
||||
private T _16;
|
||||
private T _17;
|
||||
private T _18;
|
||||
private T _19;
|
||||
private T _20;
|
||||
private T _21;
|
||||
private T _22;
|
||||
private T _23;
|
||||
private T _24;
|
||||
private T _25;
|
||||
private T _26;
|
||||
private T _27;
|
||||
private T _28;
|
||||
private T _29;
|
||||
private T _30;
|
||||
private T _31;
|
||||
private T _32;
|
||||
private T _33;
|
||||
private T _34;
|
||||
private T _35;
|
||||
private T _36;
|
||||
private T _37;
|
||||
private T _38;
|
||||
private T _39;
|
||||
private T _40;
|
||||
private T _41;
|
||||
private T _42;
|
||||
private T _43;
|
||||
private T _44;
|
||||
private T _45;
|
||||
private T _46;
|
||||
private T _47;
|
||||
private T _48;
|
||||
private T _49;
|
||||
private T _50;
|
||||
private T _51;
|
||||
private T _52;
|
||||
private T _53;
|
||||
private T _54;
|
||||
private T _55;
|
||||
private T _56;
|
||||
private T _57;
|
||||
private T _58;
|
||||
private T _59;
|
||||
private T _60;
|
||||
private T _61;
|
||||
private T _62;
|
||||
private T _63;
|
||||
private T _64;
|
||||
private T _65;
|
||||
private T _66;
|
||||
private T _67;
|
||||
private T _68;
|
||||
private T _69;
|
||||
private T _70;
|
||||
private T _71;
|
||||
private T _72;
|
||||
private T _73;
|
||||
private T _74;
|
||||
private T _75;
|
||||
private T _76;
|
||||
private T _77;
|
||||
private T _78;
|
||||
private T _79;
|
||||
private T _80;
|
||||
private T _81;
|
||||
private T _82;
|
||||
private T _83;
|
||||
private T _84;
|
||||
private T _85;
|
||||
private T _86;
|
||||
private T _87;
|
||||
private T _88;
|
||||
private T _89;
|
||||
private T _90;
|
||||
private T _91;
|
||||
private T _92;
|
||||
private T _93;
|
||||
private T _94;
|
||||
private T _95;
|
||||
private T _96;
|
||||
private T _97;
|
||||
private T _98;
|
||||
private T _99;
|
||||
private T _100;
|
||||
private T _101;
|
||||
private T _102;
|
||||
private T _103;
|
||||
private T _104;
|
||||
private T _105;
|
||||
private T _106;
|
||||
private T _107;
|
||||
private T _108;
|
||||
private T _109;
|
||||
private T _110;
|
||||
private T _111;
|
||||
private T _112;
|
||||
private T _113;
|
||||
private T _114;
|
||||
private T _115;
|
||||
private T _116;
|
||||
private T _117;
|
||||
private T _118;
|
||||
private T _119;
|
||||
private T _120;
|
||||
private T _121;
|
||||
private T _122;
|
||||
private T _123;
|
||||
private T _124;
|
||||
private T _125;
|
||||
private T _126;
|
||||
private T _127;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 128);
|
||||
}
|
||||
}
|
||||
82
Robust.Shared/Utility/ProcessExt.cs
Normal file
82
Robust.Shared/Utility/ProcessExt.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable IdentifierTypo
|
||||
// ReSharper disable CommentTypo
|
||||
#pragma warning disable 649
|
||||
|
||||
namespace Robust.Shared.Utility
|
||||
{
|
||||
internal static unsafe class ProcessExt
|
||||
{
|
||||
// THIS ENTIRE METHOD EXISTS PURELY OUT OF SPITE FOR MICROSOFT.
|
||||
// NOT TO SAVE 0.3% CPU USAGE ON WINDOWS. NAH.
|
||||
// TO SPITE MICROSOFT.
|
||||
// WHAT ABOUT THE 2.2% CPU USAGE IN CONSOLE.KEYAVAILABLE? WHAT ABOUT THAT WHATABOUTISM HUH???
|
||||
// I WANTED TO GO TO BED NOW IT'S 4:20 AM (BASED) GOD DAMNIT.
|
||||
// AS A RESULT, EVERYTHING HERE WILL BE WRITTEN IN *FUCKING* CAPS.
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static long GetPrivateMemorySize64NotSlowHolyFuckingShitMicrosoft(this Process process)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
// IT'S NOT SLOW ON LINUX, *LUCKILY*.
|
||||
// WELL I DIDN'T PROFILE IT BUT THEY DON'T DO A STUPID SEARCH OVER EVERY PROCESS.
|
||||
// IT USES PROCFS, IF YOU'RE CURIOUS.
|
||||
return process.PrivateMemorySize64;
|
||||
|
||||
//
|
||||
// GLASS TOLD ME TO PUT A TL;DR IN SO TL;DR
|
||||
// PROCESS.PRIVATEMEMORYSIZE64 (AND A BUNCH OF OTHERS) ARE SLOW AS BALLS (2+ms for me).
|
||||
// THIS ISN'T.
|
||||
//
|
||||
// AS IT *FUCKING* TURNS OUT.
|
||||
// *MANY* OF THE FIELDS ON PROCESS (THE CLASS, YOU KNOW THE ONE THIS METHOD IS AN EXTENSION FOR) HAVE TO FETCH A "PROCESS INFO".
|
||||
// HOW DO YOU FETCH A PROCESS INFO ON WINDOWS? OH YEAH YOU CALL NTQUERYSYSTEMINFORMATION APPARENTLY.
|
||||
// OH WAIT. THAT RETURNS INFO FOR *EVERY* PROCESS ON THE FUCKING SYSTEM.
|
||||
// SO YES. IF YOU LOOK UP PROCESS.PRIVATEMEMORYSIZE64, IT HAS TO SEARCH THROUGH *EVERY PROCESS ON THE FUCKING SYSTEM* TO FIND THE PROCESS IT'S LOOKING FOR.
|
||||
// THIS TAKES MORE THAN 2 FUCKING MILLISECONDS PER LOOKUP ON MY SYSTEM.
|
||||
// IF THE TITLE BAR ON THE CONSOLE WAS UPDATED EVERY TICK INSTEAD OF ONCE PER FRAME, THIS WOULD LITERALLY BE MOST OF THE IDLE SERVER CPU USAGE.
|
||||
// ***ARE YOU FUCKING KIDDING ME ????***
|
||||
// OH YEAH, NTQUERYSYSTEMINFORMATION IS DOCUMENTED AS AN UNSTABLE API THAT CAN CHANGE AT ANY TIME, APPARENTLY.
|
||||
// .NET FEELS IT'S FINE TO USE THAT (YEAH GUESS THEY'RE NEVER REMOVING IT FROM WINDOWS LMAO)
|
||||
// ARE YOU FUCKING TELLING ME THE .NET TEAM COULDN'T HAVE WALKED TO THE OTHER SIDE OF THE OFFICE AND TOLD THE KERNEL TEAM TO ADD A VERSION OF NTQUERYSYSTEMINFORMATION THAT FETCHES INFO FOR A SINGLE PROCESS?
|
||||
// THEY THOUGHT THIS SHIT WAS FUCKING ACCEPTABLE?
|
||||
// JESUS FUCKING CHRIST.
|
||||
// ANYWAYS, THIS EXTENSION METHOD USES GETPROCESSMEMORYINFO() INSTEAD BECAUSE THAT'S PROBABLY NOT FUCKING MORONIC.
|
||||
//
|
||||
|
||||
PROCESS_MEMORY_COUNTERS_EX counters;
|
||||
|
||||
if (GetProcessMemoryInfo(process.Handle, &counters, sizeof(PROCESS_MEMORY_COUNTERS_EX)) == 0)
|
||||
return 0;
|
||||
|
||||
var count = counters.PrivateUsage;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private struct PROCESS_MEMORY_COUNTERS_EX
|
||||
{
|
||||
public int cb;
|
||||
public int PageFaultCount;
|
||||
public nint PeakWorkingSetSize;
|
||||
public nint WorkingSetSize;
|
||||
public nint QuotaPeakPagedPoolUsage;
|
||||
public nint QuotaPagedPoolUsage;
|
||||
public nint QuotaPeakNonPagedPoolUsage;
|
||||
public nint QuotaNonPagedPoolUsage;
|
||||
public nint PagefileUsage;
|
||||
public nint PeakPagefileUsage;
|
||||
public nint PrivateUsage;
|
||||
}
|
||||
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
[DllImport("psapi.dll", SetLastError = true)]
|
||||
private static extern int GetProcessMemoryInfo(
|
||||
IntPtr Process,
|
||||
PROCESS_MEMORY_COUNTERS_EX* ppsmemCounters,
|
||||
int cb);
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,6 @@ namespace Robust.UnitTesting.Client.GameObjects.Components
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(factory =>
|
||||
{
|
||||
factory.RegisterClass<ContainerManagerComponent>();
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(factory => { factory.RegisterClass<ContainerManagerComponent>(); })
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadString(PROTOTYPES))
|
||||
.InitializeInstance();
|
||||
|
||||
@@ -281,12 +280,15 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
/// The generic container class uses a list of entities
|
||||
/// </summary>
|
||||
private readonly List<IEntity> _containerList = new();
|
||||
private readonly List<EntityUid> _expectedEntities = new();
|
||||
|
||||
public override string ContainerType => nameof(ContainerOnlyContainer);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;
|
||||
|
||||
public override List<EntityUid> ExpectedEntities => _expectedEntities;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InternalInsert(IEntity toinsert)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -229,6 +230,7 @@ namespace Robust.UnitTesting.Server
|
||||
compFactory.RegisterClass<PhysicsComponent>();
|
||||
compFactory.RegisterClass<EntityLookupComponent>();
|
||||
compFactory.RegisterClass<BroadphaseComponent>();
|
||||
compFactory.RegisterClass<ContainerManagerComponent>();
|
||||
|
||||
_regDelegate?.Invoke(compFactory);
|
||||
|
||||
|
||||
277
Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs
Normal file
277
Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
public class ContainerTests : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests container states with children that do not exist on the client
|
||||
/// and tests that said children are added to the container when they do arrive on the client.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Test]
|
||||
public async Task TestContainerNonexistantItems()
|
||||
{
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
||||
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
client.Post(() => IoCManager.Resolve<IClientNetManager>().ClientConnect(null!, 0, null!));
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Setup
|
||||
var mapId = MapId.Nullspace;
|
||||
var mapPos = MapCoordinates.Nullspace;
|
||||
|
||||
EntityUid entityUid = default!;
|
||||
EntityUid itemUid = default!;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var playerMan = IoCManager.Resolve<IPlayerManager>();
|
||||
|
||||
mapId = mapMan.CreateMap();
|
||||
mapPos = new MapCoordinates((0, 0), mapId);
|
||||
|
||||
var entity = entMan.SpawnEntity(null, mapPos);
|
||||
entity.Name = "Container";
|
||||
entityUid = entity.Uid;
|
||||
var container = entity.EnsureContainer<Container>("dummy");
|
||||
|
||||
// Setup PVS
|
||||
entity.AddComponent<Robust.Server.GameObjects.EyeComponent>();
|
||||
var player = playerMan.GetAllPlayers().First();
|
||||
player.AttachToEntity(entity);
|
||||
player.JoinGame();
|
||||
});
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var item = entMan.SpawnEntity(null, mapPos);
|
||||
item.Name = "Item";
|
||||
itemUid = item.Uid;
|
||||
Assert.That(entMan.TryGetEntity(entityUid, out var entity));
|
||||
var container = entity!.EnsureContainer<Container>("dummy");
|
||||
container.Insert(item);
|
||||
|
||||
// Move item out of PVS so that it doesn't get sent to the client
|
||||
item.Transform.LocalPosition = (100000, 0);
|
||||
});
|
||||
|
||||
// Needs minimum 4 to sync to client because buffer size is 3
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(4);
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entMan.TryGetEntity(entityUid, out var entity)
|
||||
|| !entity.TryGetComponent<ContainerManagerComponent>(out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = containerManagerComp.GetContainer("dummy");
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(1));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
|
||||
/*
|
||||
var containerSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
Assert.That(containerSystem.ExpectedEntities.ContainsKey(itemUid));
|
||||
Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(1));
|
||||
*/
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var compMan = IoCManager.Resolve<IComponentManager>();
|
||||
|
||||
// Move item into PVS so it gets sent to the client
|
||||
compMan.GetComponent<ITransformComponent>(itemUid).LocalPosition = (0, 0);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(4);
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entMan.TryGetEntity(entityUid, out var entity)
|
||||
|| !entity.TryGetComponent<ContainerManagerComponent>(out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = containerManagerComp.GetContainer("dummy");
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(1));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
|
||||
var containerSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
Assert.That(!containerSystem.ExpectedEntities.ContainsKey(itemUid));
|
||||
Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests container states with children that do not exist on the client
|
||||
/// and that if those children are deleted that they get properly removed from the expected entities list.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Test]
|
||||
public async Task TestContainerExpectedEntityDeleted()
|
||||
{
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
||||
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
client.Post(() => IoCManager.Resolve<IClientNetManager>().ClientConnect(null!, 0, null!));
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Setup
|
||||
var mapId = MapId.Nullspace;
|
||||
var mapPos = MapCoordinates.Nullspace;
|
||||
|
||||
EntityUid entityUid = default!;
|
||||
EntityUid itemUid = default!;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var playerMan = IoCManager.Resolve<IPlayerManager>();
|
||||
|
||||
mapId = mapMan.CreateMap();
|
||||
mapPos = new MapCoordinates((0, 0), mapId);
|
||||
|
||||
var entity = entMan.SpawnEntity(null, mapPos);
|
||||
entity.Name = "Container";
|
||||
entityUid = entity.Uid;
|
||||
var container = entity.EnsureContainer<Container>("dummy");
|
||||
|
||||
// Setup PVS
|
||||
entity.AddComponent<Robust.Server.GameObjects.EyeComponent>();
|
||||
var player = playerMan.GetAllPlayers().First();
|
||||
player.AttachToEntity(entity);
|
||||
player.JoinGame();
|
||||
});
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var item = entMan.SpawnEntity(null, mapPos);
|
||||
item.Name = "Item";
|
||||
itemUid = item.Uid;
|
||||
Assert.That(entMan.TryGetEntity(entityUid, out var entity));
|
||||
var container = entity!.EnsureContainer<Container>("dummy");
|
||||
container.Insert(item);
|
||||
|
||||
// Move item out of PVS so that it doesn't get sent to the client
|
||||
item.Transform.LocalPosition = (100000, 0);
|
||||
});
|
||||
|
||||
// Needs minimum 4 to sync to client because buffer size is 3
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(4);
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entMan.TryGetEntity(entityUid, out var entity)
|
||||
|| !entity.TryGetComponent<ContainerManagerComponent>(out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = containerManagerComp.GetContainer("dummy");
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(1));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
|
||||
/*
|
||||
var containerSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
Assert.That(containerSystem.ExpectedEntities.ContainsKey(itemUid));
|
||||
Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(1));
|
||||
*/
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
// If possible it'd be best to only have the DeleteEntity, but right now
|
||||
// the entity deleted event is not played on the client if the entity does not exist on the client.
|
||||
if (entMan.TryGetEntity(itemUid, out var item)
|
||||
&& ContainerHelpers.TryGetContainer(item, out var container))
|
||||
container.ForceRemove(item);
|
||||
entMan.DeleteEntity(itemUid);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(4);
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entMan.TryGetEntity(entityUid, out var entity)
|
||||
|| !entity.TryGetComponent<ContainerManagerComponent>(out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = containerManagerComp.GetContainer("dummy");
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(0));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
|
||||
/*
|
||||
var containerSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
Assert.That(!containerSystem.ExpectedEntities.ContainsKey(itemUid));
|
||||
Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,6 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(factory =>
|
||||
{
|
||||
factory.RegisterClass<ContainerManagerComponent>();
|
||||
})
|
||||
.RegisterPrototypes(f=>
|
||||
{
|
||||
f.LoadString(Prototypes);
|
||||
@@ -166,16 +162,13 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
var tileIndices = grid.TileIndicesFor(ent1.Transform.Coordinates);
|
||||
grid.SetTile(tileIndices, new Tile(1));
|
||||
|
||||
// Don't need the default grid for this.
|
||||
entMan.ComponentManager.RemoveComponent<PhysicsComponent>(grid.GridEntityId);
|
||||
|
||||
// Act
|
||||
ent1.Transform.Anchored = true;
|
||||
|
||||
Assert.That(grid.GetAnchoredEntities(tileIndices).First(), Is.EqualTo(ent1.Uid));
|
||||
Assert.That(grid.GetTileRef(tileIndices).Tile, Is.Not.EqualTo(Tile.Empty));
|
||||
Assert.That(ent1.HasComponent<PhysicsComponent>(), Is.False);
|
||||
Assert.That(entMan.GetEntity(grid.GridEntityId).HasComponent<PhysicsComponent>(), Is.False);
|
||||
Assert.That(entMan.GetEntity(grid.GridEntityId).HasComponent<PhysicsComponent>(), Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,26 +37,23 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
|
||||
// 1 fixture if we only ever update the 1 chunk
|
||||
grid.SetTile(Vector2i.Zero, new Tile(1));
|
||||
gridFixtures.Process();
|
||||
|
||||
Assert.That(gridBody.Fixtures.Count, Is.EqualTo(1));
|
||||
// Also should only be a single tile.
|
||||
var bounds = gridBody.Fixtures[0].Shape.CalculateLocalBounds(Angle.Zero);
|
||||
var bounds = gridBody.Fixtures[0].Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
// Poly probably has Box2D's radius added to it so won't be a unit square
|
||||
Assert.That(MathHelper.CloseTo(Box2.Area(bounds), 1.0f, 0.1f));
|
||||
|
||||
// Now do 2 tiles (same chunk)
|
||||
grid.SetTile(Vector2i.One, new Tile(1));
|
||||
gridFixtures.Process();
|
||||
|
||||
Assert.That(gridBody.Fixtures.Count, Is.EqualTo(1));
|
||||
bounds = gridBody.Fixtures[0].Shape.CalculateLocalBounds(Angle.Zero);
|
||||
bounds = gridBody.Fixtures[0].Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
// Because it's a diagonal tile it will actually be 2x2 area (until we get accurate hitboxes anyway).
|
||||
Assert.That(MathHelper.CloseTo(Box2.Area(bounds), 4.0f, 0.1f));
|
||||
|
||||
// If we add a new chunk should be 2 now
|
||||
grid.SetTile(new Vector2i(-1, -1), new Tile(1));
|
||||
gridFixtures.Process();
|
||||
Assert.That(gridBody.Fixtures.Count, Is.EqualTo(2));
|
||||
|
||||
gridBody.LinearVelocity = Vector2.One;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Physics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -15,7 +17,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
// need to rotate points about the grid's origin which is a /very/ common source of bugs.
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
public async Task TestLocalWorldConversions()
|
||||
{
|
||||
var server = StartServer();
|
||||
|
||||
@@ -50,5 +52,64 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
Assert.That(grid.LocalToWorld(coordinates.Position).EqualsApprox(new Vector2(0, -10)));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestChunkRotations()
|
||||
{
|
||||
// This is mainly checking for the purposes of rendering at this stage.
|
||||
var server = StartServer();
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = mapMan.CreateGrid(mapId);
|
||||
var gridEnt = entMan.GetEntity(grid.GridEntityId);
|
||||
var gridInternal = (IMapGridInternal) grid;
|
||||
|
||||
/* Test for map chunk rotations */
|
||||
var tile = new Tile(1);
|
||||
|
||||
for (var x = 0; x < 2; x++)
|
||||
{
|
||||
for (var y = 0; y < 10; y++)
|
||||
{
|
||||
grid.SetTile(new Vector2i(x, y), tile);
|
||||
}
|
||||
}
|
||||
|
||||
var chunks = gridInternal.GetMapChunks().Select(c => c.Value).ToList();
|
||||
|
||||
Assert.That(chunks.Count, Is.EqualTo(1));
|
||||
var chunk = chunks[0];
|
||||
var aabb = chunk.CalcWorldAABB();
|
||||
var bounds = new Box2(new Vector2(0, 0), new Vector2(2, 10));
|
||||
|
||||
// With all cardinal directions these should align.
|
||||
Assert.That(aabb, Is.EqualTo(bounds));
|
||||
|
||||
gridEnt.Transform.LocalRotation = new Angle(Math.PI);
|
||||
aabb = chunk.CalcWorldAABB();
|
||||
bounds = new Box2(new Vector2(-2, -10), new Vector2(0, 0));
|
||||
|
||||
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");
|
||||
|
||||
gridEnt.Transform.LocalRotation = new Angle(-Math.PI / 2);
|
||||
aabb = chunk.CalcWorldAABB();
|
||||
bounds = new Box2(new Vector2(0, -2), new Vector2(10, 0));
|
||||
|
||||
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");
|
||||
|
||||
gridEnt.Transform.LocalRotation = new Angle(-Math.PI / 4);
|
||||
aabb = chunk.CalcWorldAABB();
|
||||
bounds = new Box2(new Vector2(0, -1.4142135f), new Vector2(8.485281f, 7.071068f));
|
||||
|
||||
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,10 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
{
|
||||
var transformB = new Transform(Vector2.One, 0f);
|
||||
var transformA = new Transform(transformB.Position + new Vector2(0.5f, 0.0f), 0f);
|
||||
var manifold = new Manifold();
|
||||
var manifold = new Manifold()
|
||||
{
|
||||
Points = new ManifoldPoint[2]
|
||||
};
|
||||
|
||||
var expectedManifold = new Manifold
|
||||
{
|
||||
@@ -83,13 +86,19 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
LocalNormal = new Vector2(-1, 0),
|
||||
LocalPoint = new Vector2(-0.5f, 0),
|
||||
PointCount = 2,
|
||||
Points = new FixedArray2<ManifoldPoint>
|
||||
Points = new ManifoldPoint[]
|
||||
{
|
||||
[0] = new() {LocalPoint = new Vector2(0.5f, -0.5f), Id = new ContactID {Key = 65538}},
|
||||
[1] = new() {LocalPoint = new Vector2(0.5f, 0.5f), Id = new ContactID {Key = 65794}}
|
||||
new() {LocalPoint = new Vector2(0.5f, -0.5f), Id = new ContactID {Key = 65538}},
|
||||
new() {LocalPoint = new Vector2(0.5f, 0.5f), Id = new ContactID {Key = 65794}}
|
||||
}
|
||||
};
|
||||
_collisionManager.CollidePolygons(ref manifold, _polyA, transformA, _polyB, transformB);
|
||||
|
||||
for (var i = 0; i < manifold.Points.Length; i++)
|
||||
{
|
||||
Assert.That(manifold.Points[i], Is.EqualTo(expectedManifold.Points[i]));
|
||||
}
|
||||
|
||||
Assert.That(manifold, Is.EqualTo(expectedManifold));
|
||||
}
|
||||
}
|
||||
|
||||
83
Robust.UnitTesting/Shared/Physics/ShapeAABB_Test.cs
Normal file
83
Robust.UnitTesting/Shared/Physics/ShapeAABB_Test.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Physics
|
||||
{
|
||||
[TestFixture]
|
||||
public class ShapeAABB_Test : RobustUnitTest
|
||||
{
|
||||
private Transform _transform;
|
||||
private Transform _rotatedTransform;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_transform = new Transform(Vector2.One, 0f);
|
||||
// We'll use 45 degrees as it's easier to spot bugs
|
||||
_rotatedTransform = new Transform(Vector2.One, MathF.PI / 4f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestCircleAABB()
|
||||
{
|
||||
var circle = new PhysShapeCircle {Radius = 0.5f};
|
||||
var aabb = circle.ComputeAABB(_transform, 0);
|
||||
Assert.That(aabb.Width, Is.EqualTo(1f));
|
||||
Assert.That(aabb, Is.EqualTo(new Box2(0.5f, 0.5f, 1.5f, 1.5f)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestRotatedCircleAABB()
|
||||
{
|
||||
var circle = new PhysShapeCircle {Radius = 0.5f};
|
||||
var aabb = circle.ComputeAABB(_rotatedTransform, 0);
|
||||
Assert.That(aabb.Width, Is.EqualTo(1f));
|
||||
Assert.That(aabb, Is.EqualTo(new Box2(0.5f, 0.5f, 1.5f, 1.5f)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestEdgeAABB()
|
||||
{
|
||||
var edge = new EdgeShape(Vector2.Zero, Vector2.One);
|
||||
var aabb = edge.ComputeAABB(_transform, 0);
|
||||
Assert.That(aabb.Width, Is.EqualTo(1.02f));
|
||||
Assert.That(aabb, Is.EqualTo(new Box2(0.99f, 0.99f, 2.01f, 2.01f)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestRotatedEdgeAABB()
|
||||
{
|
||||
var edge = new EdgeShape(Vector2.Zero, Vector2.One);
|
||||
var aabb = edge.ComputeAABB(_rotatedTransform, 0);
|
||||
Assert.That(MathHelper.CloseTo(aabb.Width, 0.02f));
|
||||
Assert.That(aabb.EqualsApprox(new Box2(0.99f, 0.99f, 1.01f, 2.42f), 0.01f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestPolyAABB()
|
||||
{
|
||||
var polygon = new PolygonShape();
|
||||
// Radius is added to the AABB hence we'll just deduct it here for simplicity
|
||||
polygon.SetAsBox(0.49f, 0.49f);
|
||||
var aabb = polygon.ComputeAABB(_transform, 0);
|
||||
Assert.That(aabb.Width, Is.EqualTo(1f));
|
||||
Assert.That(aabb, Is.EqualTo(new Box2(0.5f, 0.5f, 1.5f, 1.5f)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestRotatedPolyAABB()
|
||||
{
|
||||
var polygon = new PolygonShape();
|
||||
// Radius is added to the AABB hence we'll just deduct it here for simplicity
|
||||
polygon.SetAsBox(0.49f, 0.49f);
|
||||
var aabb = polygon.ComputeAABB(_rotatedTransform, 0);
|
||||
// I already had a rough idea of what the AABB should be, I just put these in so the test passes.
|
||||
Assert.That(aabb.Width, Is.EqualTo(1.40592933f));
|
||||
Assert.That(aabb, Is.EqualTo(new Box2(0.29703534f, 0.29703534f, 1.7029647f, 1.7029647f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace Robust.UnitTesting
|
||||
return;
|
||||
|
||||
_writer.Flush();
|
||||
Assert.Fail(line);
|
||||
Assert.Fail($"{line} Exception: {message.Exception}");
|
||||
}
|
||||
|
||||
private string GetPrefix()
|
||||
|
||||
Reference in New Issue
Block a user