Compare commits

...

10 Commits

Author SHA1 Message Date
Pieter-Jan Briers
32fa6ebb53 Version: 136.0.2 2024-03-10 20:47:22 +01:00
Pieter-Jan Briers
27378d3620 Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit b89d13d39ffa53b9ca687946c6b56e86449d50bd)
2024-03-10 20:47:22 +01:00
Vera Aguilera Puerto
dd12110c34 Version: 136.0.1 2023-07-12 08:55:01 +02:00
Vera Aguilera Puerto
a811cfc1a1 Changelog for CEF bugfix 2023-07-12 08:53:51 +02:00
Amy
229a45bea2 Enables debugging and error handling on Linux with CEF enabled (#4181)
Co-authored-by: amylizzle <amylizzle@users.noreply.github.com>
2023-07-12 08:42:30 +02:00
metalgearsloth
78376ccca1 Fix grid fixture warnings (#4180) 2023-07-10 18:09:18 +10:00
metalgearsloth
e4a1415627 Fix erroneous Vector2.Length call (#4178) 2023-07-10 17:53:54 +10:00
metalgearsloth
69589195e0 MapLoader perf stuff (#4179) 2023-07-10 17:53:46 +10:00
Leon Friedrich
ce3b92aea2 Try prevent infinite measure/arrange loops (#4176) 2023-07-10 17:36:22 +10:00
Leon Friedrich
5dc980ae92 Revert #3827 (#4177) 2023-07-10 17:35:55 +10:00
20 changed files with 177 additions and 116 deletions

View File

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

View File

@@ -54,6 +54,16 @@ END TEMPLATE-->
*None yet*
## 136.0.2
## 136.0.1
### Bugfixes
* Fixed debugging on Linux when CEF is enabled.
## 136.0.0
### New features
@@ -62,7 +72,7 @@ END TEMPLATE-->
### Bugfixes
* Fixed OutputPanel scroll-bar not functioning properly.
* Fixed OutputPanel scroll-bar not functioning properly.
## 135.0.0
@@ -76,7 +86,7 @@ END TEMPLATE-->
### Breaking changes
* Several methods were moved out of the `UserInterface` components and into the UI system.
* Several methods were moved out of the `UserInterface` components and into the UI system.
* The BUI constructor arguments have changed and now require an EntityUid to be given instead of a component.

View File

@@ -85,12 +85,10 @@ namespace Robust.Client.WebView.Cef
_app = new RobustCefApp(_sawmill);
// We pass no main arguments...
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
// And nothing seemed to work. Odd.
// So these arguments look like nonsense, but it turns out CEF is just *like that*.
// The first argument is literally nonsense, but it needs to be there as otherwise the second argument doesn't apply
// The second argument turns off CEF's bullshit error handling, which breaks dotnet's error handling.
CefRuntime.Initialize(new CefMainArgs(new string[]{"binary","--disable-in-process-stack-traces"}), settings, _app, IntPtr.Zero);
if (_cfg.GetCVar(WCVars.WebResProtocol))
{

View File

@@ -47,7 +47,7 @@ namespace Robust.Client.GameObjects
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
_label.Visible = true;
_label.Text = $"Speed: {body.LinearVelocity.Length:0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
}
}
}

View File

@@ -14,6 +14,7 @@ namespace Robust.Client.Physics
{
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public bool EnableDebug
{
@@ -27,7 +28,7 @@ namespace Robust.Client.Physics
if (_enableDebug)
{
var overlay = new GridSplitNodeOverlay(EntityManager, _map, this);
var overlay = new GridSplitNodeOverlay(_map, this, _transform);
_overlay.AddOverlay(overlay);
RaiseNetworkEvent(new RequestGridNodesMessage());
}
@@ -39,7 +40,7 @@ namespace Robust.Client.Physics
}
}
private bool _enableDebug = false;
private bool _enableDebug;
private readonly Dictionary<EntityUid, Dictionary<Vector2i, List<List<Vector2i>>>> _nodes = new();
private readonly Dictionary<EntityUid, List<(Vector2, Vector2)>> _connections = new();
@@ -69,71 +70,76 @@ namespace Robust.Client.Physics
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private IEntityManager _entManager;
private IMapManager _mapManager;
private GridFixtureSystem _system;
private readonly IMapManager _mapManager;
private readonly GridFixtureSystem _system;
private readonly SharedTransformSystem _transform;
public GridSplitNodeOverlay(IEntityManager entManager, IMapManager mapManager, GridFixtureSystem system)
public GridSplitNodeOverlay(IMapManager mapManager, GridFixtureSystem system, SharedTransformSystem transform)
{
_entManager = entManager;
_mapManager = mapManager;
_system = system;
_transform = transform;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
foreach (var iGrid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds))
{
// May not have received nodes yet.
if (!_system._nodes.TryGetValue(iGrid.Owner, out var nodes)) continue;
var state = (_system, _transform, args.WorldBounds, worldHandle);
var gridXform = xformQuery.GetComponent(iGrid.Owner);
worldHandle.SetTransform(gridXform.WorldMatrix);
var chunkEnumerator = iGrid.GetMapChunks(args.WorldBounds);
while (chunkEnumerator.MoveNext(out var chunk))
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref state,
static (EntityUid uid, MapGridComponent grid,
ref (GridFixtureSystem system, SharedTransformSystem transform, Box2Rotated worldBounds, DrawingHandleWorld worldHandle) tuple) =>
{
if (!nodes.TryGetValue(chunk.Indices, out var chunkNodes)) continue;
// May not have received nodes yet.
if (!tuple.system._nodes.TryGetValue(uid, out var nodes))
return true;
for (var i = 0; i < chunkNodes.Count; i++)
tuple.worldHandle.SetTransform(tuple.transform.GetWorldMatrix(uid));
var chunkEnumerator = grid.GetMapChunks(tuple.worldBounds);
while (chunkEnumerator.MoveNext(out var chunk))
{
var group = chunkNodes[i];
var offset = chunk.Indices * chunk.ChunkSize;
var color = GetColor(chunk, i);
if (!nodes.TryGetValue(chunk.Indices, out var chunkNodes)) continue;
foreach (var index in group)
for (var i = 0; i < chunkNodes.Count; i++)
{
worldHandle.DrawRect(new Box2(offset + index, offset + index + 1).Enlarged(-0.1f), color);
var group = chunkNodes[i];
var offset = chunk.Indices * chunk.ChunkSize;
var color = GetColor(chunk, i);
foreach (var index in group)
{
tuple.worldHandle.DrawRect(new Box2(offset + index, offset + index + 1).Enlarged(-0.1f), color);
}
}
}
}
var connections = _system._connections[iGrid.Owner];
var connections = tuple.system._connections[uid];
foreach (var (start, end) in connections)
{
worldHandle.DrawLine(start, end, Color.Aquamarine);
}
}
foreach (var (start, end) in connections)
{
tuple.worldHandle.DrawLine(start, end, Color.Aquamarine);
}
static Color GetColor(MapChunk chunk, int index)
{
// Just want something that doesn't give similar indices at 0,0 but is also deterministic.
// Add an offset to yIndex so we at least have some colour that isn't grey at 0,0
var actualIndex = chunk.Indices.X * 20 + (chunk.Indices.Y + 20) * 35 + index * 50;
var red = (byte) (actualIndex % 255);
var green = (byte) (actualIndex * 20 % 255);
var blue = (byte) (actualIndex * 30 % 255);
return new Color(red, green, blue, 85);
}
return true;
}, true);
worldHandle.SetTransform(Matrix3.Identity);
}
private Color GetColor(MapChunk chunk, int index)
{
// Just want something that doesn't give similar indices at 0,0 but is also deterministic.
// Add an offset to yIndex so we at least have some colour that isn't grey at 0,0
var actualIndex = chunk.Indices.X * 20 + (chunk.Indices.Y + 20) * 35 + index * 50;
var red = (byte) (actualIndex % 255);
var green = (byte) (actualIndex * 20 % 255);
var blue = (byte) (actualIndex * 30 % 255);
return new Color(red, green, blue, 85);
}
}
}
}

View File

@@ -17,7 +17,7 @@
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" />
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />

View File

@@ -32,6 +32,7 @@ namespace Robust.Client.UserInterface
private VAlignment _verticalAlignment = VAlignment.Stretch;
private Thickness _margin;
private bool _measuring;
private bool _arranging;
/// <summary>
/// The desired minimum size this control needs for layout to avoid cutting off content or such.
@@ -469,13 +470,12 @@ namespace Robust.Client.UserInterface
/// </summary>
public void InvalidateMeasure()
{
if (!IsMeasureValid)
if (!IsMeasureValid || _measuring)
return;
IsMeasureValid = false;
IsArrangeValid = false;
UserInterfaceManagerInternal.QueueMeasureUpdate(this);
InvalidateArrange();
}
/// <summary>
@@ -484,7 +484,7 @@ namespace Robust.Client.UserInterface
/// </summary>
public void InvalidateArrange()
{
if (!IsArrangeValid)
if (!IsArrangeValid || _arranging)
{
// Already queued for a layout update, don't bother.
return;
@@ -508,7 +508,16 @@ namespace Robust.Client.UserInterface
if (!IsMeasureValid || PreviousMeasure != availableSize)
{
IsMeasureValid = true;
var desired = MeasureCore(availableSize);
_measuring = true;
Vector2 desired;
try
{
desired = MeasureCore(availableSize);
}
finally
{
_measuring = false;
}
if (desired.X < 0 || desired.Y < 0 || !float.IsFinite(desired.X) || !float.IsFinite(desired.Y))
throw new InvalidOperationException("Invalid size returned from Measure()");
@@ -540,16 +549,7 @@ namespace Robust.Client.UserInterface
var constrained = ApplySizeConstraints(this, withoutMargin);
Vector2 measured;
try
{
_measuring = true;
measured = MeasureOverride(constrained);
}
finally
{
_measuring = false;
}
var measured = MeasureOverride(constrained);
if (!float.IsNaN(SetWidth))
{
@@ -604,14 +604,22 @@ namespace Robust.Client.UserInterface
/// </summary>
public void Arrange(UIBox2 finalRect)
{
if (!IsMeasureValid)
Measure(PreviousMeasure ?? finalRect.Size);
if (!IsArrangeValid || PreviousArrange != finalRect)
_arranging = true;
try
{
IsArrangeValid = true;
ArrangeCore(finalRect);
PreviousArrange = finalRect;
if (!IsMeasureValid)
Measure(PreviousMeasure ?? finalRect.Size);
if (!IsArrangeValid || PreviousArrange != finalRect)
{
IsArrangeValid = true;
ArrangeCore(finalRect);
PreviousArrange = finalRect;
}
}
finally
{
_arranging = false;
}
}

View File

@@ -148,7 +148,6 @@ namespace Robust.Client.UserInterface
internal void DoStyleUpdate()
{
_stylingDirty = false;
_styleProperties.Clear();
if (_stylesheetUpdateNeeded)
@@ -229,6 +228,10 @@ namespace Robust.Client.UserInterface
}
StylePropertiesChanged();
// Setting this at the end of the function to prevent style updates from ever re-queueing a style update,
// which would cause an infinite loop.
_stylingDirty = false;
}
protected virtual void StylePropertiesChanged()

View File

@@ -223,23 +223,8 @@ namespace Robust.Client.UserInterface.Controls
var first = GetChild(0);
var second = GetChild(1);
var firstDesiredSize = firstMinSize ?? (Vertical ? first.DesiredSize.Y : first.DesiredSize.X);
var secondDesiredSize = secondMinSize ?? (Vertical ? second.DesiredSize.Y : second.DesiredSize.X);
var firstOrientedMinSize = Vertical ? first.MinSize.Y : first.MinSize.X;
var secondOrientedMinSize = Vertical ? second.MinSize.Y : second.MinSize.X;
if (firstOrientedMinSize > firstDesiredSize && firstOrientedMinSize != 0)
{
first.Measure(controlSize);
}
if (secondOrientedMinSize > secondDesiredSize && secondOrientedMinSize != 0)
{
second.Measure(controlSize);
}
firstMinSize = Vertical ? first.DesiredSize.Y : first.DesiredSize.X;
secondMinSize = Vertical ? second.DesiredSize.Y : second.DesiredSize.X;
firstMinSize ??= (Vertical ? first.DesiredSize.Y : first.DesiredSize.X);
secondMinSize ??= (Vertical ? second.DesiredSize.Y : second.DesiredSize.X);
var size = Vertical ? controlSize.Y : controlSize.X;
_splitStart = MathHelper.Clamp(_splitStart, firstMinSize.Value,

View File

@@ -152,12 +152,17 @@ internal sealed partial class UserInterfaceManager
_styleUpdateQueue.Enqueue(control);
}
/// <summary>
/// Queues a control so that it gets remeasured in the next frame update. Does not queue an arrange update.
/// </summary>
public void QueueMeasureUpdate(Control control)
{
_measureUpdateQueue.Enqueue(control);
_arrangeUpdateQueue.Enqueue(control);
}
/// <summary>
/// Queues a control so that it gets rearranged in the next frame update. Does not queue a measure update.
/// </summary>
public void QueueArrangeUpdate(Control control)
{
_arrangeUpdateQueue.Enqueue(control);

View File

@@ -38,6 +38,7 @@ internal sealed partial class UserInterfaceManager
newRoot.StyleSheetUpdate();
newRoot.InvalidateMeasure();
QueueMeasureUpdate(newRoot);
QueueArrangeUpdate(newRoot);
if (window.IsFocused)
FocusRoot(newRoot);

View File

@@ -26,6 +26,7 @@ using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface
@@ -54,6 +55,12 @@ namespace Robust.Client.UserInterface
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IRuntimeLog _runtime = default!;
/// <summary>
/// Upper limit on the number of times that controls can be measured / arranged each tick before being deferred
/// to the next frame update. This is just meant to prevent infinite loops from completely locking up the UI.
/// </summary>
public const int ControlUpdateLimit = 25_000;
[ViewVariables] public InterfaceTheme ThemeDefaults { get; private set; } = default!;
[ViewVariables]
public Stylesheet? Stylesheet
@@ -220,6 +227,12 @@ namespace Robust.Client.UserInterface
var total = 0;
while (_styleUpdateQueue.Count != 0)
{
if (total >= ControlUpdateLimit)
{
_sawmillUI.Warning($"Hit style update limit. Queued: {_styleUpdateQueue.Count}. Next in queue: {_styleUpdateQueue.Peek()}. Parent: {_styleUpdateQueue.Peek().Parent}");
break;
}
var control = _styleUpdateQueue.Dequeue();
if (control.Disposed)
@@ -237,12 +250,20 @@ namespace Robust.Client.UserInterface
var total = 0;
while (_measureUpdateQueue.Count != 0)
{
if (total >= ControlUpdateLimit)
{
_sawmillUI.Warning($"Hit measure update limit. Queued: {_measureUpdateQueue.Count}. Next in queue: {_measureUpdateQueue.Peek()}. Parent: {_measureUpdateQueue.Peek().Parent}");
break;
}
var control = _measureUpdateQueue.Dequeue();
if (control.Disposed)
continue;
RunMeasure(control);
if (!control.IsMeasureValid && control.IsInsideTree)
_sawmillUI.Warning($"Control's measure is invalid after measuring. Control: {control}. Parent: {control.Parent}.");
total += 1;
}
@@ -254,12 +275,19 @@ namespace Robust.Client.UserInterface
var total = 0;
while (_arrangeUpdateQueue.Count != 0)
{
if (total >= ControlUpdateLimit)
{
_sawmillUI.Warning($"Hit arrange update limit. Queued: {_arrangeUpdateQueue.Count}. Next in queue: {_arrangeUpdateQueue.Peek()}. Parent: {_arrangeUpdateQueue.Peek().Parent}");
break;
}
var control = _arrangeUpdateQueue.Dequeue();
if (control.Disposed)
continue;
RunArrange(control);
if (!control.IsArrangeValid && control.IsInsideTree)
_sawmillUI.Warning($"Control's arrangement is invalid after arranging. Control: {control}. Parent: {control.Parent}.");
total += 1;
}

View File

@@ -424,12 +424,16 @@ public sealed class MapLoaderSystem : EntitySystem
}
var entities = (SequenceDataNode) metaDef["entities"];
EntityPrototype? proto = null;
if (type != null)
_prototypeManager.TryIndex(type, out proto);
foreach (var entityDef in entities.Cast<MappingDataNode>())
{
var uid = entityDef.Get<ValueDataNode>("uid").AsInt();
var entity = _serverEntityManager.AllocEntity(type);
var entity = _serverEntityManager.AllocEntity(proto);
data.Entities.Add(entity);
data.UidEntityMap.Add(uid, entity);
data.EntitiesToDeserialize.Add(entity, entityDef);
@@ -462,11 +466,13 @@ public sealed class MapLoaderSystem : EntitySystem
}
else if (ev.RenamedPrototypes.TryGetValue(typeNode.Value, out var newType))
{
entity = _serverEntityManager.AllocEntity(newType);
_prototypeManager.TryIndex<EntityPrototype>(newType, out var prototype);
entity = _serverEntityManager.AllocEntity(prototype);
}
else
{
entity = _serverEntityManager.AllocEntity(typeNode.Value);
_prototypeManager.TryIndex<EntityPrototype>(typeNode.Value, out var prototype);
entity = _serverEntityManager.AllocEntity(prototype);
}
}
else

View File

@@ -8,7 +8,7 @@ namespace Robust.Server.GameObjects
// These methods are used by the map loader to do multi-stage entity construction during map load.
// I would recommend you refer to the MapLoader for usage.
EntityUid AllocEntity(string? prototypeName, EntityUid uid = default);
EntityUid AllocEntity(EntityPrototype? prototype, EntityUid uid = default);
void FinishEntityLoad(EntityUid entity, IEntityLoadContext? context = null);

View File

@@ -54,9 +54,9 @@ namespace Robust.Server.GameObjects
base.Initialize();
}
EntityUid IServerEntityManagerInternal.AllocEntity(string? prototypeName, EntityUid uid)
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype, EntityUid uid)
{
return AllocEntity(prototypeName, out _, uid);
return AllocEntity(prototype, out _, uid);
}
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, IEntityLoadContext? context)

View File

@@ -675,29 +675,20 @@ namespace Robust.Shared.GameObjects
/// Allocates an entity and stores it but does not load components or do initialization.
/// </summary>
private protected EntityUid AllocEntity(
string? prototypeName,
EntityPrototype? prototype,
out MetaDataComponent metadata,
EntityUid uid = default)
{
EntityPrototype? prototype = null;
if (!string.IsNullOrWhiteSpace(prototypeName))
{
// If the prototype doesn't exist then we throw BEFORE we allocate the entity.
prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
}
var entity = AllocEntity(out metadata, uid);
metadata._entityPrototype = prototype;
Dirty(metadata, metadata);
Dirty(entity, metadata, metadata);
return entity;
}
/// <summary>
/// Allocates an entity and stores it but does not load components or do initialization.
/// </summary>
private protected EntityUid AllocEntity(out MetaDataComponent metadata, EntityUid uid = default)
private EntityUid AllocEntity(out MetaDataComponent metadata, EntityUid uid = default)
{
if (uid == default)
{
@@ -735,7 +726,9 @@ namespace Robust.Shared.GameObjects
if (prototypeName == null)
return AllocEntity(out _, uid);
var entity = AllocEntity(prototypeName, out var metadata, uid);
PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype);
var entity = AllocEntity(prototype, out var metadata, uid);
try
{
EntityPrototype.LoadEntity(metadata.EntityPrototype, entity, ComponentFactory, this, _serManager, context);

View File

@@ -131,6 +131,11 @@ namespace Robust.Shared.Map
void FindGridsIntersecting<TState>(MapId mapId, Box2 worldAABB, ref TState state, GridCallback<TState> callback, bool approx = false, bool includeMap = true);
void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, GridCallback callback, bool approx = false, bool includeMap = true);
void FindGridsIntersecting<TState>(MapId mapId, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback, bool approx = false, bool includeMap = true);
/// <summary>
/// Returns the grids intersecting this AABB.
/// </summary>

View File

@@ -107,6 +107,18 @@ internal partial class MapManager
state = state2.state;
}
public void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, GridCallback callback, bool approx = false,
bool includeMap = true)
{
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), callback, approx, includeMap);
}
public void FindGridsIntersecting<TState>(MapId mapId, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback,
bool approx = false, bool includeMap = true)
{
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, callback, approx, includeMap);
}
private static bool IsIntersecting(
Box2 aabb,
EntityUid gridUid,

View File

@@ -3,6 +3,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;

View File

@@ -19,7 +19,7 @@
<PackageReference Include="Linguini.Bundle" Version="0.1.3" />
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />