mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a654a6cf43 | ||
|
|
6211cf2e03 | ||
|
|
a522b4cf86 | ||
|
|
6d33be8c0f | ||
|
|
cb1d4ae843 | ||
|
|
039b70f502 | ||
|
|
7892cc895f | ||
|
|
77108284b8 | ||
|
|
5e21dbdd7f | ||
|
|
8274623edb | ||
|
|
e923d69083 | ||
|
|
6e8ab5ce78 | ||
|
|
f905ea631b | ||
|
|
be54c41891 | ||
|
|
33184ecfa5 | ||
|
|
11cf0c1703 | ||
|
|
528544b7a2 | ||
|
|
8571d7e7b5 | ||
|
|
0f06423b7a | ||
|
|
eb9e0ffefc | ||
|
|
903619ecef | ||
|
|
879c6ea538 | ||
|
|
5478545aeb | ||
|
|
650929dcbb | ||
|
|
a289659b49 | ||
|
|
85d15c21e1 | ||
|
|
bcd1566440 | ||
|
|
749ac2c364 | ||
|
|
5eed3bc281 | ||
|
|
d78f378493 | ||
|
|
eef44c15cf | ||
|
|
3d1b2418f9 | ||
|
|
6b49a86ee5 | ||
|
|
cd13cd3cd8 | ||
|
|
2b8d8d6636 | ||
|
|
409fe1a125 | ||
|
|
ab5db4641c | ||
|
|
064e8ee365 | ||
|
|
02dcff7eae | ||
|
|
e1e5f8de54 | ||
|
|
d5ba822a79 | ||
|
|
f448c6b8fa | ||
|
|
5e1d80be35 | ||
|
|
01546f32da | ||
|
|
aeeaaaefc5 | ||
|
|
b6c8060af1 | ||
|
|
99685838da | ||
|
|
8917b29255 | ||
|
|
f0c4d7c5eb | ||
|
|
6a00c62d3c | ||
|
|
fc3116fca5 | ||
|
|
98c1397b3a | ||
|
|
2464bb6c2f | ||
|
|
709142acee | ||
|
|
af4e3e5e1c | ||
|
|
d51a18c6ea | ||
|
|
d5c3d4c0c9 | ||
|
|
a4474d8df8 | ||
|
|
d66f7c7c06 | ||
|
|
b6879869d6 | ||
|
|
815b8e0c48 | ||
|
|
ef4e3baa7f | ||
|
|
270ddb5a53 |
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -16,3 +16,8 @@
|
||||
|
||||
# Be they Fluent translations or Freemarker templates, I know them both!
|
||||
*.ftl @RemieRichards
|
||||
|
||||
# commands commands commands commands
|
||||
**/Toolshed/** @moonheart08
|
||||
*Command.cs @moonheart08
|
||||
*Commands.cs @moonheart08
|
||||
|
||||
4
.github/workflows/build-docfx.yml
vendored
4
.github/workflows/build-docfx.yml
vendored
@@ -7,12 +7,12 @@ jobs:
|
||||
docfx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
|
||||
|
||||
4
.github/workflows/build-test.yml
vendored
4
.github/workflows/build-test.yml
vendored
@@ -15,12 +15,12 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Install dependencies
|
||||
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -35,12 +35,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
|
||||
|
||||
4
.github/workflows/publish-client.yml
vendored
4
.github/workflows/publish-client.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
||||
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
|
||||
echo ("::set-output name=version::{0}" -f $ver)
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
|
||||
|
||||
4
.github/workflows/test-content.yml
vendored
4
.github/workflows/test-content.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out content
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
repository: space-wizards/space-station-14
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Disable submodule autoupdate
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 78aa82cef0...f19cea8010
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
139
RELEASE-NOTES.md
139
RELEASE-NOTES.md
@@ -54,6 +54,145 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 156.0.2
|
||||
|
||||
|
||||
## 156.0.1
|
||||
|
||||
|
||||
## 156.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Revert container changes from 155.0.0.
|
||||
|
||||
|
||||
## 155.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* MapInitEvent now gets raised for components that get added to entities that have already been map-initialized.
|
||||
|
||||
### New features
|
||||
|
||||
* VirtualWritableDirProvider now supports file renaming/moving.
|
||||
* Added a new command for toggling the replay UI (`replay_toggleui`).
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed formatting of localization file errors.
|
||||
* Directed event subscriptions will no longer error if the corresponding component is queued for deletion.
|
||||
|
||||
|
||||
## 154.2.0
|
||||
|
||||
|
||||
|
||||
### New features
|
||||
|
||||
* Added support for advertising to multiple hubs simultaneously.
|
||||
* Added new functions to ContainerSystem that recursively look for a component on a contained entity's parents.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix Direction.TurnCw/TurnCcw to South returning Invalid.
|
||||
|
||||
|
||||
## 154.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add MathHelper.Max for TimeSpans.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Make joint initialisation only log under IsFirstTimePredicted on client.
|
||||
|
||||
### Other
|
||||
|
||||
* Mark the proxy Dirty(component) as obsolete in line with EntityManager (Dirty(EntityUid, Component) should be used in its place).
|
||||
|
||||
|
||||
## 154.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Change ignored prototypes to skip prototypes even if the prototype type is found.
|
||||
* Moved IPlayerData interface to shared.
|
||||
|
||||
### New features
|
||||
|
||||
* Added a multiline text submit keybind function.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed multiline edits scrollbar margins.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added more event sources.
|
||||
* Made Toolshed types oneOff IoC injections.
|
||||
|
||||
|
||||
## 153.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Removed SharedUserInterfaceComponent component references.
|
||||
* Removed EntityDeletedMessage.
|
||||
|
||||
### Other
|
||||
|
||||
* Performance improvements for replay recording.
|
||||
* Lidgren has been updated to [v0.2.6](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.2.6/RELEASE-NOTES.md).
|
||||
* Make EntityManager.AddComponent with a component instance set the owner if its default, add system proxy for it.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added some `EventSource` providers for PVS and replay recording: `Robust.Pvs` and `Robust.ReplayRecording`.
|
||||
* Added RecursiveMoveBenchmark.
|
||||
* Removed redundant prototype resolving.
|
||||
* Removed CollisionWake component removal subscription.
|
||||
* Removed redundant DebugTools.AssertNotNull(netId) in ClientGameStateManager
|
||||
|
||||
|
||||
## 152.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `Robust.Server.GameObjects.BoundUserInterface.InteractionRangeSqrd` is now a get-only property. Modify `InteractionRange` instead if you want to change it on active UIs.
|
||||
* Remove IContainerManager.
|
||||
* Remove and obsolete ComponentExt methods.
|
||||
* Remove EntityStarted and ComponentDeleted C# events.
|
||||
* Convert Tile.TypeId to an int. Old maps that were saved with TypeId being an ushort will still be properly deserialized.
|
||||
|
||||
### New features
|
||||
|
||||
* `BoundUserInterfaceCheckRangeEvent` can be used to implement custom logic for BUI range checks.
|
||||
* Add support for long values in CVars.
|
||||
* Allow user code to implement own logic for bound user interface range checks.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix timers counting down slower than real time and drifting.
|
||||
* Add missing System using statement to generated component states.
|
||||
* Fix build with USE_SYSTEM_SQLITE.
|
||||
* Fix prototype manager not being initialized in robust server simulation tests.
|
||||
* Fix not running serialization hooks when copying non-byref data definition fields without a custom type serializer.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove warning for glibc 2.37.
|
||||
* Remove personally-identifiable file paths from client logs.
|
||||
|
||||
### Internal
|
||||
|
||||
* Disable obsoletion and inherited member hidden warnings in serialization source generated code.
|
||||
* Update CI workflows to use setup-dotnet 3.2.0 and checkout 3.6.0.
|
||||
* Fix entity spawn tests having instance per test lifecycle with a non static OneTimeTearDown method.
|
||||
* Add new PVS test to check that there is no issue with entity states referencing other entities that the client is not yet aware of.
|
||||
|
||||
|
||||
## 151.0.0
|
||||
|
||||
|
||||
|
||||
171
Robust.Benchmarks/Transform/RecursiveMoveBenchmark.cs
Normal file
171
Robust.Benchmarks/Transform/RecursiveMoveBenchmark.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Transform;
|
||||
|
||||
/// <summary>
|
||||
/// This benchmark tests various transform/move related functions with an entity that has many children.
|
||||
/// </summary>
|
||||
[Virtual, MemoryDiagnoser]
|
||||
public class RecursiveMoveBenchmark
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private SharedTransformSystem _transform = default!;
|
||||
private ContainerSystem _container = default!;
|
||||
private PvsSystem _pvs = default!;
|
||||
private EntityCoordinates _mapCoords;
|
||||
private EntityCoordinates _gridCoords;
|
||||
private EntityUid _ent;
|
||||
private EntityUid _child;
|
||||
private TransformComponent _childXform = default!;
|
||||
private EntityQuery<TransformComponent> _query;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
|
||||
if (!_simulation.Resolve<IConfigurationManager>().GetCVar(CVars.NetPVS))
|
||||
throw new InvalidOperationException("PVS must be enabled");
|
||||
|
||||
_entMan = _simulation.Resolve<IEntityManager>();
|
||||
_transform = _entMan.System<SharedTransformSystem>();
|
||||
_container = _entMan.System<ContainerSystem>();
|
||||
_pvs = _entMan.System<PvsSystem>();
|
||||
_query = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Create map & grid
|
||||
var mapMan = _simulation.Resolve<IMapManager>();
|
||||
var mapSys = _entMan.System<SharedMapSystem>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var map = mapMan.GetMapEntityId(mapId);
|
||||
var gridComp = mapMan.CreateGrid(mapId);
|
||||
var grid = gridComp.Owner;
|
||||
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
|
||||
_mapCoords = new EntityCoordinates(map, 100, 100);
|
||||
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
|
||||
|
||||
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
|
||||
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
|
||||
_ent = _entMan.Spawn();
|
||||
|
||||
// Quick check that SetCoordinates actually changes the parent as expected
|
||||
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
// Add 5 direct children in slots to represent clothing.
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var id = $"inventory{i}";
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, id);
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// body parts
|
||||
_container.EnsureContainer<Container>(_ent, "body");
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
// Simple organ
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// body part that has another body part / limb
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
|
||||
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Backpack
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// Misc backpack contents.
|
||||
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Emergency box inside of the backpack
|
||||
var box = backpackStorage.ContainedEntities.First();
|
||||
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Deepest child.
|
||||
_child = boxContainer.ContainedEntities.First();
|
||||
_childXform = _query.GetComponent(_child);
|
||||
|
||||
_pvs.ProcessCollections();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This implicitly measures move events, including PVS and entity lookups. Though given that most of the entities
|
||||
/// are in containers, this will bias the entity lookup aspect.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void MoveEntity()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="MoveEntity"/>, but also processes queued PVS chunk updates.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void MoveAndUpdateChunks()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
_pvs.ProcessCollections();
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
_pvs.ProcessCollections();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public Vector2 GetWorldPos()
|
||||
{
|
||||
return _transform.GetWorldPosition(_childXform);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public EntityUid GetRootUid()
|
||||
{
|
||||
var xform = _childXform;
|
||||
while (xform.ParentUid.IsValid())
|
||||
{
|
||||
xform = _query.GetComponent(xform.ParentUid);
|
||||
}
|
||||
return xform.ParentUid;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,6 @@ namespace Robust.Client
|
||||
_reflectionManager.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
_prototypeManager.ResolveResults();
|
||||
_userInterfaceManager.Initialize();
|
||||
_eyeManager.Initialize();
|
||||
_entityManager.Initialize();
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Prometheus;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Replays;
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
|
||||
[RegisterComponent]
|
||||
public sealed partial class ClientUserInterfaceComponent : SharedUserInterfaceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
@@ -206,8 +206,10 @@ namespace Robust.Client.GameObjects
|
||||
SetEntityContextActive(_inputManager, controlled);
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
protected override void PostInject()
|
||||
{
|
||||
base.PostInject();
|
||||
|
||||
_sawmillInputContext = _logManager.GetSawmill("input.context");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +500,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (netId, comp) in netComps.Value)
|
||||
{
|
||||
DebugTools.AssertNotNull(netId);
|
||||
if (!comp.NetSyncEnabled)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace Robust.Client.Input
|
||||
common.AddFunction(EngineKeyFunctions.TextWordDelete);
|
||||
common.AddFunction(EngineKeyFunctions.TextNewline);
|
||||
common.AddFunction(EngineKeyFunctions.TextSubmit);
|
||||
common.AddFunction(EngineKeyFunctions.MultilineTextSubmit);
|
||||
common.AddFunction(EngineKeyFunctions.TextCopy);
|
||||
common.AddFunction(EngineKeyFunctions.TextCut);
|
||||
common.AddFunction(EngineKeyFunctions.TextPaste);
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Client.Map
|
||||
|
||||
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
|
||||
|
||||
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
|
||||
private readonly Dictionary<int, Box2[]> _tileRegions = new();
|
||||
|
||||
public Box2 ErrorTileRegion { get; private set; }
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Robust.Client.Map
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(ushort tileType)
|
||||
public Box2[]? TileAtlasRegion(int tileType)
|
||||
{
|
||||
if (_tileRegions.TryGetValue(tileType, out var region))
|
||||
{
|
||||
|
||||
@@ -27,6 +27,6 @@ namespace Robust.Client.Map
|
||||
/// Gets the region inside the texture atlas to use to draw a tile type.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2[]? TileAtlasRegion(ushort tileType);
|
||||
Box2[]? TileAtlasRegion(int tileType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
|
||||
public interface IPlayerManager : ISharedPlayerManager
|
||||
{
|
||||
new IEnumerable<ICommonSession> Sessions { get; }
|
||||
|
||||
|
||||
22
Robust.Client/Replays/Commands/ReplayToggleUiCommand.cs
Normal file
22
Robust.Client/Replays/Commands/ReplayToggleUiCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Client.Replays.UI;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Replays.Commands;
|
||||
|
||||
public sealed class ReplayToggleUiCommand : BaseReplayCommand
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
public override string Command => "replay_toggleui";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var screen = _userInterfaceManager.ActiveScreen;
|
||||
if (screen == null || !screen.TryGetWidget(out ReplayControlWidget? replayWidget))
|
||||
return;
|
||||
|
||||
replayWidget.Visible = !replayWidget.Visible;
|
||||
}
|
||||
}
|
||||
@@ -63,10 +63,8 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
|
||||
base.FrameUpdate(args);
|
||||
if (_playback.Replay is not { } replay)
|
||||
{
|
||||
Visible = false;
|
||||
return;
|
||||
}
|
||||
Visible = true;
|
||||
|
||||
TickSlider.MinValue = 0;
|
||||
TickSlider.MaxValue = (float)replay.ReplayTime[^1].TotalSeconds;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
<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" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />
|
||||
|
||||
@@ -776,9 +776,11 @@ public sealed class TextEdit : Control
|
||||
{
|
||||
var size = base.ArrangeOverride(finalSize);
|
||||
|
||||
_scrollBar.Page = size.Y * UIScale;
|
||||
var renderBoxSize = _renderBox.Size;
|
||||
|
||||
UpdateLineBreaks((int)(size.X * UIScale));
|
||||
_scrollBar.Page = renderBoxSize.Y * UIScale;
|
||||
|
||||
UpdateLineBreaks((int)(renderBoxSize.X * UIScale));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -38,20 +38,22 @@ namespace Robust.Client.UserInterface
|
||||
/// <param name="tooltip">control to position (current size will be used to determine bounds)</param>
|
||||
public static void PositionTooltip(Vector2 screenBounds, Vector2 screenPosition, Control tooltip)
|
||||
{
|
||||
LayoutContainer.SetPosition(tooltip, screenPosition);
|
||||
|
||||
tooltip.Measure(Vector2Helpers.Infinity);
|
||||
var combinedMinSize = tooltip.DesiredSize;
|
||||
var (right, bottom) = tooltip.Position + combinedMinSize;
|
||||
|
||||
LayoutContainer.SetPosition(tooltip, new Vector2(screenPosition.X, screenPosition.Y - combinedMinSize.Y));
|
||||
|
||||
var right = tooltip.Position.X + combinedMinSize.X;
|
||||
var top = tooltip.Position.Y - combinedMinSize.Y;
|
||||
|
||||
if (right > screenBounds.X)
|
||||
{
|
||||
LayoutContainer.SetPosition(tooltip, new(screenPosition.X - combinedMinSize.X, tooltip.Position.Y));
|
||||
}
|
||||
|
||||
if (bottom > screenBounds.Y)
|
||||
if (top < 0f)
|
||||
{
|
||||
LayoutContainer.SetPosition(tooltip, new(tooltip.Position.X, screenPosition.Y - combinedMinSize.Y));
|
||||
LayoutContainer.SetPosition(tooltip, new(tooltip.Position.X, 0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ internal partial class UserInterfaceManager
|
||||
private float? _tooltipDelay;
|
||||
private bool _showingTooltip;
|
||||
private Control? _suppliedTooltip;
|
||||
private const float TooltipDelay = 1;
|
||||
private const float TooltipDelay = 0.25f;
|
||||
|
||||
private WindowRoot? _focusedRoot;
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Robust.Client.Utility
|
||||
|
||||
public static Direction TurnCw(this Direction dir)
|
||||
{
|
||||
return (Direction)(((int)dir - 1) % 8);
|
||||
return (Direction)(((int)dir + 7) % 8);
|
||||
}
|
||||
|
||||
public static Direction TurnCcw(this Direction dir)
|
||||
|
||||
@@ -87,12 +87,14 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Exceptions;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
#pragma warning disable RA0002 // Robust access analyzer
|
||||
|
||||
{{namespaceString}}
|
||||
|
||||
{{containingTypesStart}}
|
||||
|
||||
[RobustAutoGenerated]
|
||||
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
|
||||
{
|
||||
{{GetConstructor(definition)}}
|
||||
@@ -179,7 +181,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
// Implicit constructor
|
||||
#pragma warning disable CS8618
|
||||
public {{definition.Type.Name}}()
|
||||
#pragma warning enable CS8618
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
}
|
||||
""");
|
||||
@@ -444,18 +446,6 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
|
||||
""");
|
||||
}
|
||||
|
||||
var instantiator = string.Empty;
|
||||
if (IsDataDefinition(type))
|
||||
{
|
||||
instantiator = $"{tempVarName} = {name}.Instantiate();";
|
||||
}
|
||||
else if (!type.IsAbstract &&
|
||||
HasEmptyPublicConstructor(type) &&
|
||||
(type.IsReferenceType || IsNullableType(type)))
|
||||
{
|
||||
instantiator = $"{tempVarName} = new();";
|
||||
}
|
||||
|
||||
var hasHooks = ImplementsInterface(type, SerializationHooksNamespace) || !type.IsSealed;
|
||||
builder.AppendLine($$"""
|
||||
if (!serialization.TryCustomCopy(this.{{name}}, ref {{tempVarName}}, hookCtx, {{hasHooks.ToString().ToLower()}}, context))
|
||||
@@ -469,7 +459,6 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
|
||||
else if (IsDataDefinition(type) && !type.IsAbstract &&
|
||||
type is not INamedTypeSymbol { TypeKind: TypeKind.Interface })
|
||||
{
|
||||
var nullability = type.IsValueType ? string.Empty : "?";
|
||||
var nullable = !type.IsValueType || IsNullableType(type);
|
||||
|
||||
if (nullable)
|
||||
@@ -485,8 +474,7 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
|
||||
}
|
||||
|
||||
builder.AppendLine($$"""
|
||||
{{instantiator}}
|
||||
{{name}}{{nullability}}.Copy(ref {{tempVarName}}, serialization, hookCtx, context);
|
||||
serialization.CopyTo({{name}}, ref {{tempVarName}}, hookCtx, context{{nullableOverride}});
|
||||
""");
|
||||
|
||||
if (nullable)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.4.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -286,23 +286,6 @@ internal static class Types
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool HasEmptyPublicConstructor(ITypeSymbol type)
|
||||
{
|
||||
if (type is not INamedTypeSymbol named)
|
||||
return false;
|
||||
|
||||
foreach (var constructor in named.InstanceConstructors)
|
||||
{
|
||||
if (constructor.DeclaredAccessibility == Accessibility.Public &&
|
||||
constructor.Parameters.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsVirtualClass(ITypeSymbol type)
|
||||
{
|
||||
return type.IsReferenceType && !type.IsSealed && type.TypeKind != TypeKind.Interface;
|
||||
|
||||
@@ -369,7 +369,6 @@ namespace Robust.Server
|
||||
// otherwise the prototypes will be cleared
|
||||
_prototype.Initialize();
|
||||
_prototype.LoadDefaultPrototypes();
|
||||
_prototype.ResolveResults();
|
||||
_refMan.Initialize();
|
||||
|
||||
IoCManager.Resolve<ToolshedManager>().Initialize();
|
||||
@@ -389,6 +388,11 @@ namespace Robust.Server
|
||||
_protoLoadMan.Initialize();
|
||||
_netResMan.Initialize();
|
||||
|
||||
// String serializer has to be locked before PostInit as content can depend on it (e.g., replays that start
|
||||
// automatically recording on startup).
|
||||
AddFinalStringsToSerializer();
|
||||
_stringSerializer.LockStrings();
|
||||
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
|
||||
|
||||
_statusHost.Start();
|
||||
@@ -398,9 +402,6 @@ namespace Robust.Server
|
||||
|
||||
_watchdogApi.Initialize();
|
||||
|
||||
AddFinalStringsToSerializer();
|
||||
_stringSerializer.LockStrings();
|
||||
|
||||
if (OperatingSystem.IsWindows() && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
{
|
||||
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
|
||||
|
||||
@@ -47,10 +47,13 @@ internal sealed partial class MetricsManager
|
||||
// Task.Run this so it gets run on another thread pool thread.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
MetricsEvents.Log.RequestStart();
|
||||
|
||||
var resp = ctx.Response;
|
||||
var req = ctx.Request;
|
||||
try
|
||||
{
|
||||
MetricsEvents.Log.ScrapeStart();
|
||||
|
||||
var stream = resp.OutputStream;
|
||||
// prometheus-net is a terrible library and have to do all this insanity,
|
||||
@@ -74,6 +77,8 @@ internal sealed partial class MetricsManager
|
||||
}), cancel);
|
||||
|
||||
await stream.DisposeAsync();
|
||||
|
||||
MetricsEvents.Log.ScrapeStop();
|
||||
}
|
||||
catch (ScrapeFailedException e)
|
||||
{
|
||||
@@ -97,6 +102,8 @@ internal sealed partial class MetricsManager
|
||||
finally
|
||||
{
|
||||
resp.Close();
|
||||
|
||||
MetricsEvents.Log.RequestStop();
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -9,6 +10,7 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using EventSource = System.Diagnostics.Tracing.EventSource;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -168,6 +170,24 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
[EventSource(Name = "Robust.MetricsManager")]
|
||||
private sealed class MetricsEvents : EventSource
|
||||
{
|
||||
public static MetricsEvents Log { get; } = new();
|
||||
|
||||
[Event(1)]
|
||||
public void ScrapeStart() => WriteEvent(1);
|
||||
|
||||
[Event(2)]
|
||||
public void ScrapeStop() => WriteEvent(2);
|
||||
|
||||
[Event(3)]
|
||||
public void RequestStart() => WriteEvent(3);
|
||||
|
||||
[Event(4)]
|
||||
public void RequestStop() => WriteEvent(4);
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IMetricsManager
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Server.GameObjects
|
||||
/// </summary>
|
||||
/// <seealso cref="BoundUserInterface"/>
|
||||
[PublicAPI]
|
||||
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
|
||||
[RegisterComponent]
|
||||
public sealed partial class ServerUserInterfaceComponent : SharedUserInterfaceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
@@ -33,7 +33,9 @@ namespace Robust.Server.GameObjects
|
||||
[PublicAPI]
|
||||
public sealed class BoundUserInterface
|
||||
{
|
||||
public float InteractionRangeSqrd;
|
||||
public float InteractionRange;
|
||||
|
||||
public float InteractionRangeSqrd => InteractionRange * InteractionRange;
|
||||
|
||||
public Enum UiKey { get; }
|
||||
public EntityUid Owner { get; }
|
||||
@@ -58,8 +60,7 @@ namespace Robust.Server.GameObjects
|
||||
UiKey = data.UiKey;
|
||||
Owner = owner;
|
||||
|
||||
// One Abs(), because negative values imply no limit
|
||||
InteractionRangeSqrd = data.InteractionRange * MathF.Abs(data.InteractionRange);
|
||||
InteractionRange = data.InteractionRange;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
private ISawmill _logWriter = default!;
|
||||
|
||||
private static readonly MapLoadOptions DefaultLoadOptions = new();
|
||||
private const int MapFormatVersion = 5;
|
||||
private const int MapFormatVersion = 6;
|
||||
private const int BackwardsVersion = 2;
|
||||
|
||||
private MapSerializationContext _context = default!;
|
||||
@@ -384,11 +384,11 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
|
||||
// Load tile mapping so that we can map the stored tile IDs into the ones actually used at runtime.
|
||||
var tileMap = data.RootMappingNode.Get<MappingDataNode>("tilemap");
|
||||
_context.TileMap = new Dictionary<ushort, string>(tileMap.Count);
|
||||
_context.TileMap = new Dictionary<int, string>(tileMap.Count);
|
||||
|
||||
foreach (var (key, value) in tileMap.Children)
|
||||
{
|
||||
var tileId = (ushort) ((ValueDataNode)key).AsInt();
|
||||
var tileId = ((ValueDataNode)key).AsInt();
|
||||
var tileDefName = ((ValueDataNode)value).Value;
|
||||
_context.TileMap.Add(tileId, tileDefName);
|
||||
}
|
||||
@@ -960,7 +960,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
{
|
||||
// Although we could use tiledefmanager it might write tiledata we don't need so we'll compress it
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
var tileDefs = new HashSet<ushort>();
|
||||
var tileDefs = new HashSet<int>();
|
||||
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
@@ -977,7 +977,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
|
||||
var tileMap = new MappingDataNode();
|
||||
rootNode.Add("tilemap", tileMap);
|
||||
var ordered = new List<ushort>(tileDefs);
|
||||
var ordered = new List<int>(tileDefs);
|
||||
ordered.Sort();
|
||||
|
||||
foreach (var tyleId in ordered)
|
||||
|
||||
@@ -128,12 +128,13 @@ namespace Robust.Server.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
foreach (var (activeUis, xform) in EntityQuery<ActiveUserInterfaceComponent, TransformComponent>(true))
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var query = AllEntityQuery<ActiveUserInterfaceComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var activeUis, out var xform))
|
||||
{
|
||||
foreach (var ui in activeUis.Interfaces)
|
||||
{
|
||||
CheckRange(activeUis, ui, xform, query);
|
||||
CheckRange(uid, activeUis, ui, xform, xformQuery);
|
||||
|
||||
if (!ui.StateDirty)
|
||||
continue;
|
||||
@@ -160,9 +161,9 @@ namespace Robust.Server.GameObjects
|
||||
/// <summary>
|
||||
/// Verify that the subscribed clients are still in range of the interface.
|
||||
/// </summary>
|
||||
private void CheckRange(ActiveUserInterfaceComponent activeUis, BoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
|
||||
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, BoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
|
||||
{
|
||||
if (ui.InteractionRangeSqrd <= 0)
|
||||
if (ui.InteractionRange <= 0)
|
||||
return;
|
||||
|
||||
// We have to cache the set of sessions because Unsubscribe modifies the original.
|
||||
@@ -181,6 +182,20 @@ namespace Robust.Server.GameObjects
|
||||
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
|
||||
continue;
|
||||
|
||||
// Handle pluggable BoundUserInterfaceCheckRangeEvent
|
||||
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, ui, session);
|
||||
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
|
||||
continue;
|
||||
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
|
||||
|
||||
if (uiMap != xform.MapID)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
@@ -509,4 +524,64 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
[PublicAPI]
|
||||
public struct BoundUserInterfaceCheckRangeEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity owning the UI being checked for.
|
||||
/// </summary>
|
||||
public readonly EntityUid Target;
|
||||
|
||||
/// <summary>
|
||||
/// The UI itself.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public readonly BoundUserInterface UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// The player for which the UI is being checked.
|
||||
/// </summary>
|
||||
public readonly IPlayerSession Player;
|
||||
|
||||
/// <summary>
|
||||
/// The result of the range check.
|
||||
/// </summary>
|
||||
public BoundUserInterfaceRangeResult Result;
|
||||
|
||||
public BoundUserInterfaceCheckRangeEvent(
|
||||
EntityUid target,
|
||||
BoundUserInterface userInterface,
|
||||
IPlayerSession player)
|
||||
{
|
||||
Target = target;
|
||||
UserInterface = userInterface;
|
||||
Player = player;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible results for a <see cref="BoundUserInterfaceCheckRangeEvent"/>.
|
||||
/// </summary>
|
||||
public enum BoundUserInterfaceRangeResult : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Run built-in range check.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Range check passed, UI is accessible.
|
||||
/// </summary>
|
||||
Pass,
|
||||
|
||||
/// <summary>
|
||||
/// Range check failed, UI is inaccessible.
|
||||
/// </summary>
|
||||
Fail
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
@@ -23,8 +24,8 @@ using SharpZstd.Interop;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Prometheus;
|
||||
using Robust.Server.Replays;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace Robust.Server.GameStates
|
||||
|
||||
private PvsSystem _pvs = default!;
|
||||
|
||||
[Dependency] private readonly IServerEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IServerNetManager _networkManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
@@ -219,10 +220,16 @@ namespace Robust.Server.GameStates
|
||||
{
|
||||
try
|
||||
{
|
||||
var guid = i >= 0 ? players[i].UserId.UserId : default;
|
||||
|
||||
PvsEventSource.Log.WorkStart(_gameTiming.CurTick.Value, i, guid);
|
||||
|
||||
if (i >= 0)
|
||||
SendStateUpdate(i, resource, inputSystem, players[i], pvsData, mQuery, tQuery, ref oldestAckValue);
|
||||
else
|
||||
_replay.Update();
|
||||
|
||||
PvsEventSource.Log.WorkStop(_gameTiming.CurTick.Value, i, guid);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
@@ -373,5 +380,35 @@ namespace Robust.Server.GameStates
|
||||
_networkManager.ServerSendMessage(pvsMessage, channel);
|
||||
}
|
||||
}
|
||||
|
||||
[EventSource(Name = "Robust.Pvs")]
|
||||
public sealed class PvsEventSource : System.Diagnostics.Tracing.EventSource
|
||||
{
|
||||
public static PvsEventSource Log { get; } = new();
|
||||
|
||||
[Event(1)]
|
||||
public void WorkStart(uint tick, int playerIdx, Guid playerGuid) => WriteEvent(1, tick, playerIdx, playerGuid);
|
||||
|
||||
[Event(2)]
|
||||
public void WorkStop(uint tick, int playerIdx, Guid playerGuid) => WriteEvent(2, tick, playerIdx, playerGuid);
|
||||
|
||||
[NonEvent]
|
||||
private unsafe void WriteEvent(int eventId, uint arg1, int arg2, Guid arg3)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
var descrs = stackalloc EventData[3];
|
||||
|
||||
descrs[0].DataPointer = (IntPtr)(&arg1);
|
||||
descrs[0].Size = 4;
|
||||
descrs[1].DataPointer = (IntPtr)(&arg2);
|
||||
descrs[1].Size = 4;
|
||||
descrs[2].DataPointer = (IntPtr)(&arg3);
|
||||
descrs[2].Size = 16;
|
||||
|
||||
WriteEventCore(eventId, 3, descrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -48,7 +47,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
|
||||
var chunk = instantiationDelegate != null ? instantiationDelegate() : new MapChunk(ind.X, ind.Y, size);
|
||||
|
||||
IReadOnlyDictionary<ushort, string>? tileMap = null;
|
||||
IReadOnlyDictionary<int, string>? tileMap = null;
|
||||
|
||||
if (context is MapSerializationContext serContext)
|
||||
{
|
||||
@@ -65,11 +64,14 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
|
||||
var tileDefinitionManager = dependencies.Resolve<ITileDefinitionManager>();
|
||||
|
||||
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
|
||||
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;
|
||||
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var id = reader.ReadUInt16();
|
||||
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
|
||||
var flags = (TileRenderFlag)reader.ReadByte();
|
||||
var variant = reader.ReadByte();
|
||||
|
||||
@@ -98,6 +100,8 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
var gridNode = new ValueDataNode();
|
||||
root.Add("tiles", gridNode);
|
||||
|
||||
root.Add("version", new ValueDataNode("6"));
|
||||
|
||||
gridNode.Value = SerializeTiles(value);
|
||||
|
||||
return root;
|
||||
@@ -106,7 +110,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
private static string SerializeTiles(MapChunk chunk)
|
||||
{
|
||||
// number of bytes written per tile, because sizeof(Tile) is useless.
|
||||
const int structSize = 4;
|
||||
const int structSize = 6;
|
||||
|
||||
var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
|
||||
var barr = new byte[nTiles];
|
||||
@@ -82,7 +82,7 @@ namespace Robust.Server.Placement
|
||||
var alignRcv = msg.Align;
|
||||
var isTile = msg.IsTile;
|
||||
|
||||
ushort tileType = 0;
|
||||
int tileType = 0;
|
||||
var entityTemplateName = "";
|
||||
|
||||
if (isTile) tileType = msg.TileType;
|
||||
@@ -177,7 +177,7 @@ namespace Robust.Server.Placement
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceNewTile(ushort tileType, EntityCoordinates coordinates, NetUserId placingUserId)
|
||||
private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId placingUserId)
|
||||
{
|
||||
if (!coordinates.IsValid(_entityManager)) return;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Server.Player
|
||||
@@ -13,7 +15,7 @@ namespace Robust.Server.Player
|
||||
/// <summary>
|
||||
/// Manages each players session when connected to the server.
|
||||
/// </summary>
|
||||
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
|
||||
public interface IPlayerManager : ISharedPlayerManager
|
||||
{
|
||||
BoundKeyMap KeyMap { get; }
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Server.Player
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Server.Player
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="SpaceWizards.HttpListener" Version="0.1.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' == 'True'" /> <!-- Cannot be private since Content.Server/Database/ServerDbManager.cs depends on SQLitePCL.raw -->
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="prometheus-net" Version="4.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" PrivateAssets="compile" />
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -9,6 +5,12 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robust.Server.ServerHub;
|
||||
|
||||
@@ -21,12 +23,12 @@ internal sealed class HubManager
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private string? _advertiseUrl;
|
||||
private string _masterUrl = "";
|
||||
private IReadOnlyList<string> _hubUrls = Array.Empty<string>();
|
||||
private TimeSpan _nextPing;
|
||||
private TimeSpan _interval;
|
||||
|
||||
private bool _active;
|
||||
private bool _firstAdvertisement = true;
|
||||
private readonly HashSet<string> _hubUrlsAdvertisedTo = new HashSet<string>();
|
||||
private HttpClient? _httpClient;
|
||||
|
||||
public async void Start()
|
||||
@@ -38,7 +40,10 @@ internal sealed class HubManager
|
||||
return;
|
||||
|
||||
_cfg.OnValueChanged(CVars.HubAdvertiseInterval, UpdateInterval, true);
|
||||
_cfg.OnValueChanged(CVars.HubMasterUrl, s => _masterUrl = s, true);
|
||||
_cfg.OnValueChanged(CVars.HubUrls, s => _hubUrls = s.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToList()
|
||||
, true);
|
||||
|
||||
var url = _cfg.GetCVar(CVars.HubServerUrl);
|
||||
if (string.IsNullOrEmpty(url))
|
||||
@@ -100,32 +105,37 @@ internal sealed class HubManager
|
||||
DebugTools.AssertNotNull(_advertiseUrl);
|
||||
DebugTools.AssertNotNull(_httpClient);
|
||||
|
||||
var apiUrl = $"{_masterUrl}api/servers/advertise";
|
||||
|
||||
try
|
||||
foreach (var hubUrl in _hubUrls)
|
||||
{
|
||||
using var response = await _httpClient!.PostAsJsonAsync(apiUrl, new AdvertiseRequest(_advertiseUrl!));
|
||||
var apiUrl = $"{hubUrl}api/servers/advertise";
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
try
|
||||
{
|
||||
var errorText = await response.Content.ReadAsStringAsync();
|
||||
_sawmill.Log(
|
||||
LogLevel.Error,
|
||||
"Error status while advertising server: [{StatusCode}] {Response}",
|
||||
response.StatusCode,
|
||||
errorText);
|
||||
return;
|
||||
}
|
||||
using var response = await _httpClient!.PostAsJsonAsync(apiUrl, new AdvertiseRequest(_advertiseUrl!));
|
||||
|
||||
if (_firstAdvertisement)
|
||||
{
|
||||
_sawmill.Info("Successfully advertised to hub with address {ServerHubAddress}", _advertiseUrl);
|
||||
_firstAdvertisement = false;
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorText = await response.Content.ReadAsStringAsync();
|
||||
_sawmill.Error("Error status while advertising server: [{StatusCode}] {ErrorText}, to {HubUrl}",
|
||||
response.StatusCode,
|
||||
errorText,
|
||||
hubUrl);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_hubUrlsAdvertisedTo.Contains(hubUrl))
|
||||
{
|
||||
_sawmill.Info("Successfully advertised to {HubUrl} with address {AdvertiseUrl}",
|
||||
hubUrl,
|
||||
_advertiseUrl);
|
||||
_hubUrlsAdvertisedTo.Add(hubUrl);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Log(LogLevel.Error, e, "Exception while trying to advertise server to {HubUrl}",
|
||||
hubUrl);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Log(LogLevel.Error, e, $"Exception while trying to advertise server to hub");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
@@ -18,6 +7,18 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using HttpListener = SpaceWizards.HttpListener.HttpListener;
|
||||
using HttpListenerContext = SpaceWizards.HttpListener.HttpListenerContext;
|
||||
|
||||
@@ -46,11 +47,11 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
private string? _serverNameCache;
|
||||
private string? _serverDescCache;
|
||||
private string[]? _serverTagsCache;
|
||||
private IReadOnlyList<string> _serverTagsCache = Array.Empty<string>();
|
||||
|
||||
public async Task ProcessRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
var apiContext = (IStatusHandlerContext) new ContextImpl(context);
|
||||
var apiContext = (IStatusHandlerContext)new ContextImpl(context);
|
||||
|
||||
_httpSawmill.Info(
|
||||
$"{apiContext.RequestMethod} {apiContext.Url.PathAndQuery} from {apiContext.RemoteEndPoint}");
|
||||
@@ -110,17 +111,10 @@ namespace Robust.Server.ServerStatus
|
||||
// Writes/reads of references are atomic in C# so no further synchronization necessary.
|
||||
_cfg.OnValueChanged(CVars.GameHostName, n => _serverNameCache = n, true);
|
||||
_cfg.OnValueChanged(CVars.GameDesc, n => _serverDescCache = n, true);
|
||||
_cfg.OnValueChanged(CVars.HubTags, t =>
|
||||
{
|
||||
var tags = t.Split(",", StringSplitOptions.RemoveEmptyEntries);
|
||||
for (var i = 0; i < tags.Length; i++)
|
||||
{
|
||||
tags[i] = tags[i].Trim();
|
||||
}
|
||||
_serverTagsCache = tags;
|
||||
},
|
||||
true
|
||||
);
|
||||
_cfg.OnValueChanged(CVars.HubTags, t => _serverTagsCache = t.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToList(),
|
||||
true);
|
||||
|
||||
if (!_cfg.GetCVar(CVars.StatusEnabled))
|
||||
{
|
||||
@@ -287,7 +281,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
public void Respond(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
Respond(text, (int) code, contentType);
|
||||
Respond(text, (int)code, contentType);
|
||||
}
|
||||
|
||||
public void Respond(string text, int code = 200, string contentType = MediaTypeNames.Text.Plain)
|
||||
@@ -307,7 +301,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
public void Respond(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
Respond(data, (int) code, contentType);
|
||||
Respond(data, (int)code, contentType);
|
||||
}
|
||||
|
||||
public void Respond(byte[] data, int code = 200, string contentType = MediaTypeNames.Text.Plain)
|
||||
@@ -330,7 +324,7 @@ namespace Robust.Server.ServerStatus
|
||||
{
|
||||
RespondShared();
|
||||
|
||||
_context.Response.StatusCode = (int) HttpStatusCode.NoContent;
|
||||
_context.Response.StatusCode = (int)HttpStatusCode.NoContent;
|
||||
_context.Response.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -338,7 +332,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
public Task RespondAsync(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = "text/plain")
|
||||
{
|
||||
return RespondAsync(text, (int) code, contentType);
|
||||
return RespondAsync(text, (int)code, contentType);
|
||||
}
|
||||
|
||||
public async Task RespondAsync(string text, int code = 200, string contentType = "text/plain")
|
||||
@@ -358,7 +352,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
public Task RespondAsync(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = "text/plain")
|
||||
{
|
||||
return RespondAsync(data, (int) code, contentType);
|
||||
return RespondAsync(data, (int)code, contentType);
|
||||
}
|
||||
|
||||
public async Task RespondAsync(byte[] data, int code = 200, string contentType = "text/plain")
|
||||
@@ -415,7 +409,7 @@ namespace Robust.Server.ServerStatus
|
||||
{
|
||||
RespondShared();
|
||||
|
||||
_context.Response.StatusCode = (int) code;
|
||||
_context.Response.StatusCode = (int)code;
|
||||
|
||||
return Task.FromResult(_context.Response.OutputStream);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ tags = ""
|
||||
# want to use HTTPS (with a reverse proxy), or other advanced scenarios.
|
||||
# Must be in the form of an ss14:// or ss14s:// URI pointing to the status API.
|
||||
server_url = ""
|
||||
# Comma-separated list of URLs of hub servers to advertise to.
|
||||
hub_urls = "https://central.spacestation14.io/hub/"
|
||||
|
||||
[build]
|
||||
# *Absolutely all of these can be supplied using a "build.json" file*
|
||||
|
||||
@@ -148,6 +148,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
}
|
||||
|
||||
return $@"// <auto-generated />
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Analyzers;
|
||||
@@ -157,7 +158,7 @@ namespace {nameSpace};
|
||||
|
||||
public partial class {componentName}
|
||||
{{
|
||||
[Serializable, NetSerializable]
|
||||
[System.Serializable, NetSerializable]
|
||||
public class {stateName} : ComponentState
|
||||
{{{stateFields}
|
||||
}}
|
||||
|
||||
@@ -338,6 +338,16 @@ namespace Robust.Shared.Maths
|
||||
return MathF.Max(MathF.Min(a, b), MathF.Min(MathF.Max(a, b), c));
|
||||
}
|
||||
|
||||
public static TimeSpan Min(TimeSpan a, TimeSpan b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
public static TimeSpan Max(TimeSpan a, TimeSpan b)
|
||||
{
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
#endregion MinMax
|
||||
|
||||
#region Mod
|
||||
@@ -592,6 +602,11 @@ namespace Robust.Shared.Maths
|
||||
return a + (b - a) * blend;
|
||||
}
|
||||
|
||||
public static TimeSpan Lerp(TimeSpan a, TimeSpan b, double t)
|
||||
{
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
#endregion Lerp
|
||||
|
||||
#region InterpolateCubic
|
||||
|
||||
@@ -1303,10 +1303,10 @@ namespace Robust.Shared
|
||||
CVarDef.Create("hub.tags", "", CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// URL of the master hub server to advertise to.
|
||||
/// Comma-separated list of URLs of hub servers to advertise to.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> HubMasterUrl =
|
||||
CVarDef.Create("hub.master_url", "https://central.spacestation14.io/hub/", CVar.SERVERONLY);
|
||||
public static readonly CVarDef<string> HubUrls =
|
||||
CVarDef.Create("hub.hub_urls", "https://central.spacestation14.io/hub/", CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// URL of this server to advertise.
|
||||
|
||||
@@ -127,6 +127,11 @@ namespace Robust.Shared.Configuration
|
||||
return Parse.Float(input);
|
||||
}
|
||||
|
||||
if (type == typeof(long))
|
||||
{
|
||||
return long.Parse(input);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Robust.Shared.Configuration
|
||||
using var file = File.OpenRead(configFile);
|
||||
var result = LoadFromTomlStream(file);
|
||||
_configFile = configFile;
|
||||
_sawmill.Info($"Configuration Loaded from '{Path.GetFullPath(configFile)}'");
|
||||
_sawmill.Info($"Configuration loaded from file");
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -612,6 +612,11 @@ namespace Robust.Shared.Configuration
|
||||
return Enum.Parse(type, value);
|
||||
}
|
||||
|
||||
if (type == typeof(long))
|
||||
{
|
||||
return long.Parse(value);
|
||||
}
|
||||
|
||||
// Must be a string.
|
||||
return value;
|
||||
}
|
||||
@@ -633,7 +638,11 @@ namespace Robust.Shared.Configuration
|
||||
return obj.Get<float>();
|
||||
|
||||
case TomlObjectType.Int:
|
||||
return obj.Get<int>();
|
||||
var val = obj.Get<long>();
|
||||
if (val is >= int.MinValue and <= int.MaxValue)
|
||||
return obj.Get<int>();
|
||||
|
||||
return val;
|
||||
|
||||
case TomlObjectType.String:
|
||||
return obj.Get<string>();
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Shared.Console
|
||||
/// Basic interface to handle console commands. Any class implementing this will be
|
||||
/// registered with the console system through reflection.
|
||||
/// </summary>
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors), Obsolete("New commands should utilize RtShellCommand.")]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors), Obsolete("New commands should utilize ToolshedCommand.")]
|
||||
public interface IConsoleCommand
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robust.Shared.Containers
|
||||
/// <param name="entity">Entity that might be inside a container.</param>
|
||||
/// <param name="manager">The container manager that this entity is inside of.</param>
|
||||
/// <returns>If a container manager was found.</returns>
|
||||
public static bool TryGetContainerMan(this EntityUid entity, [NotNullWhen(true)] out IContainerManager? manager, IEntityManager? entMan = null)
|
||||
public static bool TryGetContainerMan(this EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
DebugTools.Assert(entMan.EntityExists(entity));
|
||||
@@ -132,9 +132,10 @@ namespace Robust.Shared.Containers
|
||||
/// <see cref="SharedContainerSystem.TryGetManagerComp"/>
|
||||
/// </summary>
|
||||
[Obsolete("Use SharedContainerSystem.TryGetManagerComp() instead")]
|
||||
private static bool TryGetManagerComp(this EntityUid entity, [NotNullWhen(true)] out IContainerManager? manager, IEntityManager? entMan = null)
|
||||
private static bool TryGetManagerComp(this EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager, IEntityManager? entMan = null)
|
||||
{
|
||||
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
|
||||
IoCManager.Resolve(ref entMan);
|
||||
return entMan.System<SharedContainerSystem>()
|
||||
.TryGetManagerComp(entity, out manager);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,9 @@ namespace Robust.Shared.Containers
|
||||
/// <summary>
|
||||
/// Holds data about a set of entity containers on this entity.
|
||||
/// </summary>
|
||||
[ComponentReference(typeof(IContainerManager))]
|
||||
[NetworkedComponent]
|
||||
[RegisterComponent, ComponentProtoName("ContainerContainer")]
|
||||
public sealed partial class ContainerManagerComponent : Component, IContainerManager, ISerializationHooks
|
||||
public sealed partial class ContainerManagerComponent : Component, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages containers on an entity.
|
||||
/// </summary>
|
||||
/// <seealso cref="IContainer" />
|
||||
public partial interface IContainerManager : IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes a new container of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID for the new container.</param>
|
||||
/// <typeparam name="T">The type of the new container</typeparam>
|
||||
/// <returns>The new container.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID</exception>
|
||||
T MakeContainer<T>(string id)
|
||||
where T : IContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove the entity from some container on this entity.
|
||||
/// </summary>
|
||||
/// <param name="reparent">If false, this operation will not rigger a move or parent change event. Ignored if
|
||||
/// destination is not null</param>
|
||||
/// <param name="force">If true, this will not perform can-remove checks.</param>
|
||||
/// <param name="destination">Where to place the entity after removing. Avoids unnecessary broadphase updates.
|
||||
/// If not specified, and reparent option is true, then the entity will either be inserted into a parent
|
||||
/// container, the grid, or the map.</param>
|
||||
/// <param name="localRotation">Optional final local rotation after removal. Avoids redundant move events.</param>
|
||||
bool Remove(EntityUid toremove,
|
||||
TransformComponent? xform = null,
|
||||
MetaDataComponent? meta = null,
|
||||
bool reparent = true,
|
||||
bool force = false,
|
||||
EntityCoordinates? destination = null,
|
||||
Angle? localRotation = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the container with the specified ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID to look up.</param>
|
||||
/// <returns>The container.</returns>
|
||||
/// <exception cref="KeyNotFoundException">Thrown if the container does not exist.</exception>
|
||||
IContainer GetContainer(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether we have a container with the specified ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The entity ID to check.</param>
|
||||
/// <returns>True if we already have a container, false otherwise.</returns>
|
||||
bool HasContainer(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to retrieve a container with specified ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID to look up.</param>
|
||||
/// <param name="container">The container if it was found, <c>null</c> if not found.</param>
|
||||
/// <returns>True if the container was found, false otherwise.</returns>
|
||||
bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to retrieve a container that contains a specific entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity that is inside the container.</param>
|
||||
/// <param name="container">The container if it was found, <c>null</c> if not found.</param>
|
||||
/// <returns>True if the container was found, false otherwise.</returns>
|
||||
/// <returns>True if the container was found, false otherwise.</returns>
|
||||
bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out IContainer? container);
|
||||
|
||||
bool ContainsEntity(EntityUid entity);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ namespace Robust.Shared.Containers
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
private EntityQuery<MetaDataComponent> _metas;
|
||||
private EntityQuery<TransformComponent> _xforms;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -25,6 +28,9 @@ namespace Robust.Shared.Containers
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentStartup>(OnStartupValidation);
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentGetState>(OnContainerGetState);
|
||||
|
||||
_metas = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
_xforms = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args)
|
||||
@@ -220,6 +226,62 @@ namespace Robust.Shared.Containers
|
||||
return IsEntityOrParentInContainer(xform.ParentUid, metas: metas, xforms: xforms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first instance of a component on the recursive parented containers that hold an entity
|
||||
/// </summary>
|
||||
public bool TryFindComponentOnEntityContainerOrParent<T>(
|
||||
EntityUid uid,
|
||||
EntityQuery<T> entityQuery,
|
||||
[NotNullWhen(true)] ref T? foundComponent,
|
||||
MetaDataComponent? meta = null,
|
||||
TransformComponent? xform = null) where T : Component
|
||||
{
|
||||
if (!_metas.Resolve(uid, ref meta))
|
||||
return false;
|
||||
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
|
||||
return false;
|
||||
|
||||
if (!_xforms.Resolve(uid, ref xform))
|
||||
return false;
|
||||
|
||||
if (!xform.ParentUid.Valid)
|
||||
return false;
|
||||
|
||||
if (entityQuery.Resolve(xform.ParentUid, ref foundComponent, false))
|
||||
return true;
|
||||
|
||||
return TryFindComponentOnEntityContainerOrParent(xform.ParentUid, entityQuery, ref foundComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all instances of a component on the recursive parented containers that hold an entity
|
||||
/// </summary>
|
||||
public bool TryFindComponentsOnEntityContainerOrParent<T>(
|
||||
EntityUid uid,
|
||||
EntityQuery<T> entityQuery,
|
||||
List<T> foundComponents,
|
||||
MetaDataComponent? meta = null,
|
||||
TransformComponent? xform = null) where T : Component
|
||||
{
|
||||
if (!_metas.Resolve(uid, ref meta))
|
||||
return foundComponents.Any();
|
||||
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
|
||||
return foundComponents.Any();
|
||||
|
||||
if (!_xforms.Resolve(uid, ref xform))
|
||||
return foundComponents.Any();
|
||||
|
||||
if (!xform.ParentUid.Valid)
|
||||
return foundComponents.Any();
|
||||
|
||||
if (EntityManager.TryGetComponent(xform.ParentUid, out T? foundComponent))
|
||||
foundComponents.Add(foundComponent);
|
||||
|
||||
return TryFindComponentsOnEntityContainerOrParent(xform.ParentUid, entityQuery, foundComponents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the two entities are not contained, or are contained in the same container.
|
||||
/// </summary>
|
||||
@@ -440,7 +502,7 @@ namespace Robust.Shared.Containers
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool TryGetManagerComp(EntityUid entity, [NotNullWhen(true)] out IContainerManager? manager)
|
||||
internal bool TryGetManagerComp(EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager)
|
||||
{
|
||||
DebugTools.Assert(Exists(entity));
|
||||
|
||||
|
||||
@@ -71,12 +71,9 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
else
|
||||
{
|
||||
_sawmill.Debug("Robust directory is {0}", ourPath);
|
||||
loadDirs.Add(Path.GetDirectoryName(ourPath)!);
|
||||
}
|
||||
|
||||
_sawmill.Debug(".NET runtime directory is {0}", dotnetDir);
|
||||
|
||||
if (EngineModuleDirectories != null)
|
||||
{
|
||||
foreach (var moduleDir in EngineModuleDirectories)
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
if (directory.Children.TryGetValue(segment, out var child))
|
||||
{
|
||||
if (!(child is DirectoryNode childDir))
|
||||
if (child is not DirectoryNode childDir)
|
||||
{
|
||||
throw new ArgumentException("A file already exists at that location.");
|
||||
}
|
||||
@@ -55,6 +55,7 @@ namespace Robust.Shared.ContentPack
|
||||
var newDir = new DirectoryNode();
|
||||
|
||||
directory.Children.Add(segment, newDir);
|
||||
directory = newDir;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +206,28 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
public void Rename(ResPath oldPath, ResPath newPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!oldPath.IsRooted)
|
||||
throw new ArgumentException("Path must be rooted", nameof(oldPath));
|
||||
|
||||
if (!newPath.IsRooted)
|
||||
throw new ArgumentException("Path must be rooted", nameof(newPath));
|
||||
|
||||
if (!TryGetNodeAt(oldPath.Directory, out var parent) || parent is not DirectoryNode sourceDir)
|
||||
throw new ArgumentException("Source directory does not exist.");
|
||||
|
||||
if (!TryGetNodeAt(newPath.Directory, out var target) || target is not DirectoryNode targetDir)
|
||||
throw new ArgumentException("Target directory does not exist.");
|
||||
|
||||
var newFile = newPath.Filename;
|
||||
if (targetDir.Children.ContainsKey(newFile))
|
||||
throw new ArgumentException("Target node already exists");
|
||||
|
||||
var oldFile = oldPath.Filename;
|
||||
if (!sourceDir.Children.TryGetValue(oldFile, out var node))
|
||||
throw new ArgumentException("Node does not exist in original directory.");
|
||||
|
||||
sourceDir.Children.Remove(oldFile);
|
||||
targetDir.Children.Add(newFile, node);
|
||||
}
|
||||
|
||||
public void OpenOsWindow(ResPath path)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Shared.Enums
|
||||
public EntityUid MobUid { get; set; }
|
||||
public string? PlacementOption { get; set; }
|
||||
public int Range { get; set; }
|
||||
public ushort TileType { get; set; }
|
||||
public int TileType { get; set; }
|
||||
public int Uses { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,11 +258,12 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WARNING: Do not subscribe to this unless you know what you are doing!
|
||||
/// The component has been added to the entity. This is the first function
|
||||
/// to be called after the component has been allocated and (optionally) deserialized.
|
||||
/// </summary>
|
||||
[ComponentEvent]
|
||||
public sealed class ComponentAdd : EntityEventArgs { }
|
||||
public readonly record struct ComponentAdd;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when all of the entity's other components have been added and are available,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -8,27 +9,6 @@ namespace Robust.Shared.GameObjects
|
||||
[PublicAPI]
|
||||
public static class ComponentExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Convenience wrapper to implement "create component if it does not already exist".
|
||||
/// Always gives you back a component, and creates it if it does not exist yet.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to fetch or create the component on.</param>
|
||||
/// <param name="component">The existing component, or the new component if none existed yet.</param>
|
||||
/// <typeparam name="T">The type of the component to fetch or create.</typeparam>
|
||||
/// <returns>True if the component already existed, false if it had to be created.</returns>
|
||||
public static bool EnsureComponent<T>(this EntityUid entity, out T component) where T : Component, new()
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (entMan.TryGetComponent<T>(entity, out var comp))
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
component = entMan.AddComponent<T>(entity);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience wrapper to implement "create component if it does not already exist".
|
||||
/// Always gives you back a component, and creates it if it does not exist yet.
|
||||
@@ -36,6 +16,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="entity">The entity to fetch or create the component on.</param>
|
||||
/// <typeparam name="T">The type of the component to fetch or create.</typeparam>
|
||||
/// <returns>The existing component, or the new component if none existed yet.</returns>
|
||||
[Obsolete]
|
||||
public static T EnsureComponent<T>(this EntityUid entity) where T : Component, new()
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
@@ -47,35 +28,6 @@ namespace Robust.Shared.GameObjects
|
||||
return entMan.AddComponent<T>(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience wrapper to implement "create component if it does not already exist".
|
||||
/// Always gives you back a component, and creates it if it does not exist yet.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to fetch or create the component on.</param>
|
||||
/// <param name="component">The existing component, or the new component if none existed yet.</param>
|
||||
/// <param name="warning">
|
||||
/// The custom warning message to log if the component did not exist already.
|
||||
/// Defaults to a predetermined warning if null.
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of the component to fetch or create.</typeparam>
|
||||
/// <returns>True if the component already existed, false if it had to be created.</returns>
|
||||
public static bool EnsureComponentWarn<T>(this EntityUid entity, out T component, string? warning = null) where T : Component, new()
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (entMan.TryGetComponent<T>(entity, out var comp))
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
warning ??= $"Entity {entity} at {entMan.GetComponent<TransformComponent>(entity).MapPosition} did not have a {typeof(T)}";
|
||||
|
||||
Logger.Warning(warning);
|
||||
|
||||
component = entMan.AddComponent<T>(entity);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience wrapper to implement "create component if it does not already exist".
|
||||
/// Always gives you back a component, and creates it if it does not exist yet.
|
||||
@@ -87,6 +39,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Defaults to a predetermined warning if null.
|
||||
/// </param>
|
||||
/// <returns>The existing component, or the new component if none existed yet.</returns>
|
||||
[Obsolete]
|
||||
public static T EnsureComponentWarn<T>(this EntityUid entity, string? warning = null) where T : Component, new()
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
@@ -102,6 +55,7 @@ namespace Robust.Shared.GameObjects
|
||||
return entMan.AddComponent<T>(entity);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static IComponent SetAndDirtyIfChanged<TValue>(
|
||||
this IComponent comp,
|
||||
ref TValue backingField,
|
||||
|
||||
@@ -548,6 +548,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
while (enumerator.MoveNext(out var component, out var reg))
|
||||
{
|
||||
if (component.Deleted)
|
||||
continue;
|
||||
|
||||
if (reg.ReferenceEvent != dispatchByReference)
|
||||
ThrowByRefMisMatch();
|
||||
|
||||
@@ -569,7 +572,11 @@ namespace Robust.Shared.GameObjects
|
||||
if (reg.ReferenceEvent != byRef)
|
||||
ThrowByRefMisMatch();
|
||||
|
||||
found.Add(new OrderedEventDispatch((ref Unit ev) => reg.Handler(euid, component, ref ev), reg.Order));
|
||||
found.Add(new OrderedEventDispatch((ref Unit ev) =>
|
||||
{
|
||||
if (!component.Deleted)
|
||||
reg.Handler(euid, component, ref ev);
|
||||
}, reg.Order));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,7 +747,7 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
component = _entityManager.GetComponent(_uid, compType);
|
||||
component = _entityManager.GetComponentInternal(_uid, compType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public event Action<RemovedComponentEventArgs>? ComponentRemoved;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<DeletedComponentEventArgs>? ComponentDeleted;
|
||||
|
||||
public void InitializeComponents()
|
||||
{
|
||||
if (Initialized)
|
||||
@@ -118,7 +115,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
DebugTools.Assert(metadata == null || metadata.Owner == uid);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
metadata ??= GetComponent<MetaDataComponent>(uid);
|
||||
DebugTools.Assert(metadata.EntityLifeStage == EntityLifeStage.PreInit);
|
||||
metadata.EntityLifeStage = EntityLifeStage.Initializing;
|
||||
@@ -185,7 +184,9 @@ namespace Robust.Shared.GameObjects
|
||||
public Component AddComponent(EntityUid uid, ushort netId)
|
||||
{
|
||||
var newComponent = (Component)_componentFactory.GetComponent(netId);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
newComponent.Owner = uid;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
AddComponent(uid, newComponent);
|
||||
return newComponent;
|
||||
}
|
||||
@@ -193,7 +194,9 @@ namespace Robust.Shared.GameObjects
|
||||
public T AddComponent<T>(EntityUid uid) where T : Component, new()
|
||||
{
|
||||
var newComponent = _componentFactory.GetComponent<T>();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
newComponent.Owner = uid;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
AddComponent(uid, newComponent);
|
||||
return newComponent;
|
||||
}
|
||||
@@ -202,19 +205,21 @@ namespace Robust.Shared.GameObjects
|
||||
where T : Component
|
||||
{
|
||||
private readonly IEntityManager _entMan;
|
||||
private readonly EntityUid _owner;
|
||||
public readonly CompIdx CompType;
|
||||
public readonly T Comp;
|
||||
|
||||
public CompInitializeHandle(IEntityManager entityManager, T comp, CompIdx compType)
|
||||
public CompInitializeHandle(IEntityManager entityManager, EntityUid owner, T comp, CompIdx compType)
|
||||
{
|
||||
_entMan = entityManager;
|
||||
_owner = owner;
|
||||
Comp = comp;
|
||||
CompType = compType;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var metadata = _entMan.GetComponent<MetaDataComponent>(Comp.Owner);
|
||||
var metadata = _entMan.GetComponent<MetaDataComponent>(_owner);
|
||||
|
||||
if (!metadata.EntityInitialized && !metadata.EntityInitializing)
|
||||
return;
|
||||
@@ -237,18 +242,16 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var reg = _componentFactory.GetRegistration<T>();
|
||||
var newComponent = (T)_componentFactory.GetComponent(reg);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
newComponent.Owner = uid;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
if (!uid.IsValid() || !EntityExists(uid))
|
||||
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
|
||||
|
||||
if (newComponent == null) throw new ArgumentNullException(nameof(newComponent));
|
||||
|
||||
if (newComponent.Owner != uid) throw new InvalidOperationException("Component is not owned by entity.");
|
||||
|
||||
AddComponentInternal(uid, newComponent, false, true);
|
||||
|
||||
return new CompInitializeHandle<T>(this, newComponent, reg.Idx);
|
||||
return new CompInitializeHandle<T>(this, uid, newComponent, reg.Idx);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -259,7 +262,16 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (component == null) throw new ArgumentNullException(nameof(component));
|
||||
|
||||
if (component.Owner != uid) throw new InvalidOperationException("Component is not owned by entity.");
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (component.Owner == default)
|
||||
{
|
||||
component.Owner = uid;
|
||||
}
|
||||
else if (component.Owner != uid)
|
||||
{
|
||||
throw new InvalidOperationException("Component is not owned by entity.");
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
AddComponentInternal(uid, component, overwrite, false);
|
||||
}
|
||||
@@ -335,6 +347,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (metadata.EntityInitialized)
|
||||
component.LifeStartup(this);
|
||||
|
||||
if (metadata.EntityLifeStage >= EntityLifeStage.MapInitialized)
|
||||
EventBus.RaiseComponentEvent(component, MapInitEventInstance);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -606,8 +621,6 @@ namespace Robust.Shared.GameObjects
|
||||
// TODO if terminating the entity, maybe defer this?
|
||||
// _entCompIndex.Remove(uid) gets called later on anyways.
|
||||
_entCompIndex.Remove(entityUid, component);
|
||||
|
||||
ComponentDeleted?.Invoke(new DeletedComponentEventArgs(new ComponentEventArgs(component, entityUid), terminating));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -755,6 +768,18 @@ namespace Robust.Shared.GameObjects
|
||||
return _netComponents[uid][netId];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponent GetComponentInternal(EntityUid uid, CompIdx type)
|
||||
{
|
||||
var dict = _entTraitArray[type.Value];
|
||||
if (dict.TryGetValue(uid, out var comp))
|
||||
{
|
||||
return comp;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {type}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetComponent<T>(EntityUid uid, [NotNullWhen(true)] out T? component)
|
||||
@@ -1422,7 +1447,9 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
if (component != null)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
DebugTools.Assert(uid == component.Owner, "Specified Entity is not the component's Owner!");
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1515,7 +1542,9 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
if (component != null)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
DebugTools.Assert(uid == component.Owner, "Specified Entity is not the component's Owner!");
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public event Action<EntityUid>? EntityAdded;
|
||||
public event Action<EntityUid>? EntityInitialized;
|
||||
public event Action<EntityUid>? EntityStarted;
|
||||
public event Action<EntityUid>? EntityDeleted;
|
||||
|
||||
/// <summary>
|
||||
@@ -361,12 +360,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntities() => Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Marks this entity as dirty so that it will be updated over the network.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling Dirty on a component will call this directly.
|
||||
/// </remarks>
|
||||
/// <inheritdoc />
|
||||
public virtual void DirtyEntity(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
// We want to retrieve MetaDataComponent even if its Deleted flag is set.
|
||||
@@ -383,7 +377,8 @@ namespace Robust.Shared.GameObjects
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
if (metadata.EntityLastModifiedTick == _gameTiming.CurTick) return;
|
||||
if (metadata.EntityLastModifiedTick == _gameTiming.CurTick)
|
||||
return;
|
||||
|
||||
metadata.EntityLastModifiedTick = _gameTiming.CurTick;
|
||||
|
||||
@@ -393,12 +388,14 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("use override with an EntityUid")]
|
||||
public void Dirty(Component component, MetaDataComponent? meta = null)
|
||||
{
|
||||
Dirty(component.Owner, component, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dirty(EntityUid uid, Component component, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (component.LifeStage >= ComponentLifeStage.Removing || !component.NetSyncEnabled)
|
||||
@@ -547,20 +544,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
_eventBus.OnEntityDeleted(uid);
|
||||
|
||||
// Another try-catch, so quickly after the other one?!
|
||||
// Yes. Both of these are try-catch blocks for *events*, which take our precious execution flow away from
|
||||
// us and into whatever spooky code subscribed to this. We don't want an exception in user code suddenly
|
||||
// fucking up entity deletion and leaving us with a frankesteintity, now do we?
|
||||
try
|
||||
{
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntityDeletedMessage(uid));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while raising {nameof(EntityDeletedMessage)} on '{ToPrettyString(uid, metadata)}'\n{e}");
|
||||
}
|
||||
|
||||
Entities.Remove(uid);
|
||||
}
|
||||
|
||||
@@ -680,7 +663,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
return CreateEntity(prototype, uid, context);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allocates an entity and loads components but does not do initialization.
|
||||
/// </summary>
|
||||
@@ -739,7 +722,6 @@ namespace Robust.Shared.GameObjects
|
||||
public void StartEntity(EntityUid entity)
|
||||
{
|
||||
StartComponents(entity);
|
||||
EntityStarted?.Invoke(entity);
|
||||
}
|
||||
|
||||
public void RunMapInit(EntityUid entity, MetaDataComponent meta)
|
||||
|
||||
@@ -190,8 +190,11 @@ public partial class EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks an entity as dirty.
|
||||
/// Marks this entity as dirty so that it will be updated over the network.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling Dirty on a component will call this directly.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void DirtyEntity(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
@@ -202,6 +205,7 @@ public partial class EntitySystem
|
||||
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use Dirty(EntityUid, Component, MetaDataComponent?")]
|
||||
protected void Dirty(Component component, MetaDataComponent? meta = null)
|
||||
{
|
||||
EntityManager.Dirty(component, meta);
|
||||
@@ -533,6 +537,13 @@ public partial class EntitySystem
|
||||
return EntityManager.AddComponent<T>(uid);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.AddComponent<T>(EntityUid, T, bool)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void AddComp<T>(EntityUid uid, T component, bool overwrite = false) where T : Component
|
||||
{
|
||||
EntityManager.AddComponent(uid, component, overwrite);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.EnsureComponent<T>(EntityUid)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected T EnsureComp<T>(EntityUid uid) where T : Component, new()
|
||||
@@ -697,14 +708,14 @@ public partial class EntitySystem
|
||||
/// <inheritdoc cref="IEntityManager.SpawnNextToOrDrop" />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityUid SpawnNextToOrDrop(
|
||||
string? protoName,
|
||||
EntityUid target,
|
||||
TransformComponent? xform = null,
|
||||
string? protoName,
|
||||
EntityUid target,
|
||||
TransformComponent? xform = null,
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
return EntityManager.SpawnNextToOrDrop(protoName, target, xform, overrides);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.SpawnInContainerOrDrop" />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityUid SpawnInContainerOrDrop(
|
||||
@@ -712,7 +723,7 @@ public partial class EntitySystem
|
||||
EntityUid containerUid,
|
||||
string containerId,
|
||||
TransformComponent? xform = null,
|
||||
ContainerManagerComponent? container = null,
|
||||
ContainerManagerComponent? container = null,
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
return EntityManager.SpawnInContainerOrDrop(protoName, containerUid, containerId, xform, container, overrides);
|
||||
|
||||
@@ -203,7 +203,10 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#endregion
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
|
||||
void IPostInjectInit.PostInject() => PostInject();
|
||||
|
||||
protected virtual void PostInject()
|
||||
{
|
||||
Log = LogManager.GetSawmill(SawmillName);
|
||||
|
||||
|
||||
@@ -20,17 +20,20 @@ using Robust.Shared.Exceptions;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntitySystemManager : IEntitySystemManager
|
||||
public sealed class EntitySystemManager : IEntitySystemManager, IPostInjectInit
|
||||
{
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[IoC.Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[IoC.Dependency] private readonly ProfManager _profManager = default!;
|
||||
[IoC.Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[IoC.Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
#endif
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
internal DependencyCollection SystemDependencyCollection = default!;
|
||||
private readonly List<Type> _systemTypes = new();
|
||||
|
||||
@@ -138,7 +141,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
foreach (var type in systems)
|
||||
{
|
||||
Logger.DebugS("go.sys", "Initializing entity system {0}", type);
|
||||
_sawmill.Debug("Initializing entity system {0}", type);
|
||||
|
||||
SystemDependencyCollection.Register(type);
|
||||
_systemTypes.Add(type);
|
||||
@@ -406,6 +409,11 @@ namespace Robust.Shared.GameObjects
|
||||
return System.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("go.sys");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SystemChangedArgs : EventArgs
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntityDeletedMessage : EntityEventArgs
|
||||
{
|
||||
public EntityUid Entity { get; }
|
||||
|
||||
public EntityDeletedMessage(EntityUid entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Instances are dynamically instantiated by a <c>ComponentFactory</c>, and will have their IoC Dependencies resolved.
|
||||
/// </remarks>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public partial interface IComponent : ISerializationGenerated<IComponent>
|
||||
public partial interface IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The current lifetime stage of this component. You can use this to check
|
||||
|
||||
@@ -18,12 +18,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
event Action<RemovedComponentEventArgs>? ComponentRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// A component was deleted. This is usually deferred until some time after it was removed.
|
||||
/// Usually you will want to subscribe to <see cref="ComponentRemoved"/>.
|
||||
/// </summary>
|
||||
event Action<DeletedComponentEventArgs>? ComponentDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// Calls Initialize() on all registered components of the entity.
|
||||
/// </summary>
|
||||
@@ -263,6 +257,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>The component with the specified network id.</returns>
|
||||
IComponent GetComponent(EntityUid uid, ushort netId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type, even if it has been marked for deletion.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <param name="type">A trait or component type to check for.</param>
|
||||
/// <returns>The component of Type from the Entity.</returns>
|
||||
IComponent GetComponentInternal(EntityUid uid, CompIdx type);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
|
||||
@@ -53,7 +53,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
event Action<EntityUid>? EntityAdded;
|
||||
event Action<EntityUid>? EntityInitialized;
|
||||
event Action<EntityUid>? EntityStarted;
|
||||
event Action<EntityUid>? EntityDeleted;
|
||||
event Action<EntityUid>? EntityDirtied; // only raised after initialization
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Shared.GameObjects
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CollisionWakeComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<CollisionWakeComponent, ComponentShutdown>(OnRemove);
|
||||
|
||||
SubscribeLocalEvent<CollisionWakeComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<CollisionWakeComponent, ComponentHandleState>(OnHandleState);
|
||||
@@ -59,7 +59,7 @@ namespace Robust.Shared.GameObjects
|
||||
args.State = new CollisionWakeComponent.CollisionWakeState(component.Enabled);
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, CollisionWakeComponent component, ComponentRemove args)
|
||||
private void OnRemove(EntityUid uid, CollisionWakeComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.Enabled
|
||||
&& !Terminating(uid)
|
||||
|
||||
@@ -8,8 +8,13 @@ public sealed class DebugExceptionSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DebugExceptionOnAddComponent, ComponentAdd>((_, _, _) => throw new NotSupportedException());
|
||||
SubscribeLocalEvent<DebugExceptionOnAddComponent, ComponentAdd>(OnCompAdd);
|
||||
SubscribeLocalEvent<DebugExceptionInitializeComponent, ComponentInit>((_, _, _) => throw new NotSupportedException());
|
||||
SubscribeLocalEvent<DebugExceptionStartupComponent, ComponentStartup>((_, _, _) => throw new NotSupportedException());
|
||||
}
|
||||
|
||||
private void OnCompAdd(EntityUid uid, DebugExceptionOnAddComponent component, ComponentAdd args)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +193,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
|
||||
private void OnBroadphaseAdd(EntityUid uid, BroadphaseComponent component, ComponentAdd args)
|
||||
{
|
||||
component.DynamicTree = new DynamicTreeBroadPhase();
|
||||
component.StaticTree = new DynamicTreeBroadPhase();
|
||||
component.StaticSundriesTree = new DynamicTree<EntityUid>(
|
||||
(in EntityUid value) => GetTreeAABB(value, uid));
|
||||
component.SundriesTree = new DynamicTree<EntityUid>(
|
||||
|
||||
@@ -4,7 +4,14 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public abstract class SharedInputSystem : EntitySystem
|
||||
{
|
||||
private readonly CommandBindRegistry _bindRegistry = new();
|
||||
private CommandBindRegistry _bindRegistry = default!;
|
||||
|
||||
protected override void PostInject()
|
||||
{
|
||||
base.PostInject();
|
||||
|
||||
_bindRegistry = new CommandBindRegistry(Log);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the keyFunction -> handler bindings for the simulation.
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Robust.Shared.Input.Binding
|
||||
/// <inheritdoc cref="ICommandBindRegistry"/>
|
||||
public sealed class CommandBindRegistry : ICommandBindRegistry
|
||||
{
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
// all registered bindings
|
||||
private List<TypedCommandBind> _bindings = new();
|
||||
// handlers in the order they should be resolved for the given key function.
|
||||
@@ -17,6 +19,11 @@ namespace Robust.Shared.Input.Binding
|
||||
private Dictionary<BoundKeyFunction, List<InputCmdHandler>> _bindingsForKey = new();
|
||||
private bool _graphDirty = false;
|
||||
|
||||
public CommandBindRegistry(ISawmill sawmill)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register<TOwner>(CommandBinds commandBinds)
|
||||
{
|
||||
@@ -30,11 +37,11 @@ namespace Robust.Shared.Input.Binding
|
||||
{
|
||||
// feel free to delete this if there's an actual need for registering multiple
|
||||
// bindings for a given type in separate calls to Register()
|
||||
Logger.Warning("Command binds already registered for type {0}, but you are trying" +
|
||||
" to register more. This may " +
|
||||
"be a programming error. Did you register these under the wrong type, or " +
|
||||
"did you forget to unregister these bindings when" +
|
||||
" your system / manager is shutdown?", owner.Name);
|
||||
_sawmill.Warning("Command binds already registered for type {0}, but you are trying" +
|
||||
" to register more. This may " +
|
||||
"be a programming error. Did you register these under the wrong type, or " +
|
||||
"did you forget to unregister these bindings when" +
|
||||
" your system / manager is shutdown?", owner.Name);
|
||||
}
|
||||
|
||||
foreach (var binding in commandBinds.Bindings)
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace Robust.Shared.Input
|
||||
public static readonly BoundKeyFunction TextWordDelete = "TextWordDelete";
|
||||
public static readonly BoundKeyFunction TextNewline = "TextNewline";
|
||||
public static readonly BoundKeyFunction TextSubmit = "TextSubmit";
|
||||
public static readonly BoundKeyFunction MultilineTextSubmit = "MultilineTextSubmit";
|
||||
public static readonly BoundKeyFunction TextSelectAll = "TextSelectAll";
|
||||
public static readonly BoundKeyFunction TextCopy = "TextCopy";
|
||||
public static readonly BoundKeyFunction TextCut = "TextCut";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Linguini.Bundle.Errors;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Localization;
|
||||
|
||||
@@ -18,52 +19,53 @@ internal static class LocHelper
|
||||
|
||||
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource, string? newLine)
|
||||
{
|
||||
newLine ??= Environment.NewLine;
|
||||
|
||||
var lines = new ValueList<(int start, int end)>();
|
||||
|
||||
// Isolate the lines in the context
|
||||
// Also figure out the line number for the 1st line (1-indexed).
|
||||
var startLineNumber = -1;
|
||||
var markOffset = 0;
|
||||
var curLineNumber = 0;
|
||||
var lineEnumerator = new LineEnumerator(resource);
|
||||
while (lineEnumerator.MoveNext(out var lineStart, out var lineEnd))
|
||||
{
|
||||
curLineNumber += 1;
|
||||
if (span.StartSpan >= lineEnd)
|
||||
continue;
|
||||
|
||||
if (span.EndSpan <= lineStart)
|
||||
break;
|
||||
|
||||
lines.Add((lineStart, lineEnd));
|
||||
if (startLineNumber == -1)
|
||||
startLineNumber = curLineNumber;
|
||||
|
||||
if (span.StartMark < lineEnd && span.StartMark >= lineStart)
|
||||
markOffset = span.StartMark - lineStart;
|
||||
}
|
||||
|
||||
// Figure out width of line number column.
|
||||
var lastLine = lines.Count + startLineNumber - 1;
|
||||
var lastLineNumberWidth = $"{lastLine}".Length;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
|
||||
var lines = new List<ReadOnlyMemory<char>>(5);
|
||||
var currLineOffset = 0;
|
||||
var lastStart = 0;
|
||||
for (var i = 0; i < span.StartMark - span.StartSpan; i++)
|
||||
curLineNumber = startLineNumber;
|
||||
foreach (var (lineStart, lineEnd) in lines)
|
||||
{
|
||||
switch (errContext[i])
|
||||
{
|
||||
// Reset current line so that mark aligns with the reported error
|
||||
// We cheat here a bit, since we both `\r\n` and `\n` end with '\n'
|
||||
case '\n':
|
||||
if (i > 0 && errContext[i - 1] == '\r')
|
||||
{
|
||||
lines.Add(resource.Slice(lastStart, currLineOffset - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add(resource.Slice(lastStart, currLineOffset));
|
||||
}
|
||||
var linePadded = $"{curLineNumber}".PadLeft(lastLineNumberWidth);
|
||||
var line = resource.Span[lineStart..lineEnd];
|
||||
sb.Append($" {linePadded} |{line.TrimEnd()}");
|
||||
sb.Append(newLine);
|
||||
|
||||
lastStart = currLineOffset + 1;
|
||||
currLineOffset = 0;
|
||||
break;
|
||||
default:
|
||||
currLineOffset++;
|
||||
break;
|
||||
}
|
||||
curLineNumber += 1;
|
||||
}
|
||||
|
||||
lines.Add(resource.Slice(lastStart, resource.Length - lastStart));
|
||||
sb.Append(' ', markOffset + lastLineNumberWidth + 3);
|
||||
sb.Append('^', span.EndMark - span.StartMark);
|
||||
sb.Append($" {message}");
|
||||
|
||||
|
||||
var lastLine = $"{span.Row + lines.Count - 1}".Length;
|
||||
for (var index = 0; index < lines.Count; index++)
|
||||
{
|
||||
var line = lines[index];
|
||||
|
||||
sb.Append(newLine ?? Environment.NewLine).Append(' ').Append($"{span.Row + index}".PadLeft(lastLine))
|
||||
.Append(" |").Append(line);
|
||||
}
|
||||
|
||||
sb.Append(newLine ?? Environment.NewLine)
|
||||
.Append(' ', currLineOffset + lastLine + 3)
|
||||
.Append('^', span.EndMark - span.StartMark)
|
||||
.Append($" {message}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,12 +248,17 @@ namespace Robust.Shared.Localization
|
||||
|
||||
var resources = files.AsParallel().Select(path =>
|
||||
{
|
||||
using var fileStream = resourceManager.ContentFileRead(path);
|
||||
using var reader = new StreamReader(fileStream, EncodingHelpers.UTF8);
|
||||
string contents;
|
||||
|
||||
var parser = new LinguiniParser(reader);
|
||||
using (var fileStream = resourceManager.ContentFileRead(path))
|
||||
using (var reader = new StreamReader(fileStream, EncodingHelpers.UTF8))
|
||||
{
|
||||
contents = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
var parser = new LinguiniParser(contents);
|
||||
var resource = parser.Parse();
|
||||
return (path, resource, parser.GetReadonlyData);
|
||||
return (path, resource, contents);
|
||||
});
|
||||
|
||||
foreach (var (path, resource, data) in resources)
|
||||
@@ -264,11 +269,11 @@ namespace Robust.Shared.Localization
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteWarningForErrs(ResPath path, List<ParseError> errs, ReadOnlyMemory<char> resource)
|
||||
private void WriteWarningForErrs(ResPath path, List<ParseError> errs, string resource)
|
||||
{
|
||||
foreach (var err in errs)
|
||||
{
|
||||
_logSawmill.Warning("{path}:\n{exception}", path, err.FormatCompileErrors(resource));
|
||||
_logSawmill.Error($"{path}:\n{err.FormatCompileErrors(resource.AsMemory())}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +281,7 @@ namespace Robust.Shared.Localization
|
||||
{
|
||||
foreach (var err in errs)
|
||||
{
|
||||
_logSawmill.Warning("Error extracting `{locId}`\n{e1}", locId, err);
|
||||
_logSawmill.Error("Error extracting `{locId}`\n{e1}", locId, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ internal sealed class MapSerializationContext : ISerializationContext, IEntityLo
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
|
||||
// Run-specific data
|
||||
public Dictionary<ushort, string>? TileMap;
|
||||
public Dictionary<int, string>? TileMap;
|
||||
public readonly Dictionary<string, IComponent> CurrentReadingEntityComponents = new();
|
||||
public HashSet<string> CurrentlyIgnoredComponents = new();
|
||||
public string? CurrentComponent;
|
||||
@@ -128,7 +128,11 @@ internal sealed class MapSerializationContext : ISerializationContext, IEntityLo
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
Logger.ErrorS("map", "Encountered an invalid entityUid '{0}' while serializing a map.", value);
|
||||
dependencies
|
||||
.Resolve<ILogManager>()
|
||||
.GetSawmill("map")
|
||||
.Error("Encountered an invalid entityUid '{0}' while serializing a map.", value);
|
||||
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
@@ -147,7 +151,11 @@ internal sealed class MapSerializationContext : ISerializationContext, IEntityLo
|
||||
if (int.TryParse(node.Value, out var val) && _uidEntityMap.TryGetValue(val, out var entity))
|
||||
return entity;
|
||||
|
||||
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
|
||||
dependencies
|
||||
.Resolve<ILogManager>()
|
||||
.GetSawmill("map")
|
||||
.Error("Error in map file: found local entity UID '{0}' which does not exist.", val);
|
||||
|
||||
return EntityUid.Invalid;
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
|
||||
/// <summary>
|
||||
/// Internal type ID of this tile.
|
||||
/// </summary>
|
||||
public readonly ushort TypeId;
|
||||
public readonly int TypeId;
|
||||
|
||||
/// <summary>
|
||||
/// Rendering flags.
|
||||
@@ -41,37 +41,13 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
|
||||
/// <param name="typeId">Internal type ID.</param>
|
||||
/// <param name="flags">Flags used by toolbox's rendering.</param>
|
||||
/// <param name="variant">The visual variant this tile is using.</param>
|
||||
public Tile(ushort typeId, TileRenderFlag flags = 0, byte variant = 0)
|
||||
public Tile(int typeId, TileRenderFlag flags = 0, byte variant = 0)
|
||||
{
|
||||
TypeId = typeId;
|
||||
Flags = flags;
|
||||
Variant = variant;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicit conversion of <c>Tile</c> to <c>uint</c> . This should only
|
||||
/// be used in special cases like serialization. Do NOT use this in
|
||||
/// content.
|
||||
/// </summary>
|
||||
public static explicit operator uint(Tile tile)
|
||||
{
|
||||
return ((uint)tile.TypeId << 16) | (uint)tile.Flags << 8 | tile.Variant;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicit conversion of <c>uint</c> to <c>Tile</c> . This should only
|
||||
/// be used in special cases like serialization. Do NOT use this in
|
||||
/// content.
|
||||
/// </summary>
|
||||
public static explicit operator Tile(uint tile)
|
||||
{
|
||||
return new(
|
||||
(ushort)(tile >> 16),
|
||||
(TileRenderFlag)(tile >> 8),
|
||||
(byte)tile
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for equality by value between two objects.
|
||||
/// </summary>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Robust.Shared.Network.Messages
|
||||
/// </summary>
|
||||
public bool Replacement { get; set; }
|
||||
public bool IsTile { get; set; }
|
||||
public ushort TileType { get; set; }
|
||||
public int TileType { get; set; }
|
||||
public string EntityTemplateName { get; set; }
|
||||
public EntityCoordinates EntityCoordinates { get; set; }
|
||||
public Direction DirRcv { get; set; }
|
||||
@@ -44,7 +44,7 @@ namespace Robust.Shared.Network.Messages
|
||||
IsTile = buffer.ReadBoolean();
|
||||
Replacement = buffer.ReadBoolean();
|
||||
|
||||
if (IsTile) TileType = buffer.ReadUInt16();
|
||||
if (IsTile) TileType = buffer.ReadInt32();
|
||||
else EntityTemplateName = buffer.ReadString();
|
||||
|
||||
EntityCoordinates = buffer.ReadEntityCoordinates();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.BroadPhase;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
@@ -11,12 +12,12 @@ namespace Robust.Shared.Physics
|
||||
/// <summary>
|
||||
/// Stores all non-static bodies.
|
||||
/// </summary>
|
||||
public IBroadPhase DynamicTree = default!;
|
||||
public IBroadPhase DynamicTree = new DynamicTreeBroadPhase();
|
||||
|
||||
/// <summary>
|
||||
/// Stores all static bodies.
|
||||
/// </summary>
|
||||
public IBroadPhase StaticTree = default!;
|
||||
public IBroadPhase StaticTree = new DynamicTreeBroadPhase();
|
||||
|
||||
/// <summary>
|
||||
/// Stores all other non-static entities not in another tree.
|
||||
|
||||
@@ -5,9 +5,7 @@ using System.Numerics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
@@ -146,8 +144,8 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
var jointsA = jointComponentA.Joints;
|
||||
var jointsB = jointComponentB.Joints;
|
||||
|
||||
|
||||
Log.Debug($"Initializing joint {joint.ID}");
|
||||
if (_gameTiming.IsFirstTimePredicted)
|
||||
Log.Debug($"Initializing joint {joint.ID}");
|
||||
|
||||
// Check for existing joints
|
||||
if (!ignoreExisting && jointsA.TryGetValue(joint.ID, out var existing))
|
||||
|
||||
@@ -25,13 +25,13 @@ public readonly struct PlacementEntityEvent
|
||||
|
||||
public readonly struct PlacementTileEvent
|
||||
{
|
||||
public readonly ushort TileType;
|
||||
public readonly int TileType;
|
||||
|
||||
public readonly EntityCoordinates Coordinates;
|
||||
|
||||
public readonly NetUserId? PlacerNetUserId;
|
||||
|
||||
public PlacementTileEvent(ushort tileType, EntityCoordinates coordinates, NetUserId? placerNetUserId)
|
||||
public PlacementTileEvent(int tileType, EntityCoordinates coordinates, NetUserId? placerNetUserId)
|
||||
{
|
||||
TileType = tileType;
|
||||
Coordinates = coordinates;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Server.Player
|
||||
namespace Robust.Shared.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores player-specific data that is not lost upon reconnect.
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Shared.Players
|
||||
{
|
||||
|
||||
@@ -7,10 +7,8 @@ using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
@@ -155,11 +153,11 @@ public partial class PrototypeManager
|
||||
private ExtractedMappingData? ExtractMapping(MappingDataNode dataNode)
|
||||
{
|
||||
var type = dataNode.Get<ValueDataNode>("type").Value;
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
return null;
|
||||
|
||||
if (!_kindNames.TryGetValue(type, out var kind))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
return null;
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
@@ -46,11 +42,11 @@ public partial class PrototypeManager
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
{
|
||||
var typeId = node.GetNode("type").AsString();
|
||||
if (_ignoredPrototypeTypes.Contains(typeId))
|
||||
continue;
|
||||
|
||||
if (!_kindNames.TryGetValue(typeId, out var type))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(typeId))
|
||||
continue;
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{typeId}'");
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,7 @@ namespace Robust.Shared.Prototypes
|
||||
public virtual void Initialize()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PrototypeManager)} has already been initialized.");
|
||||
}
|
||||
return;
|
||||
|
||||
Sawmill = _logManager.GetSawmill("proto");
|
||||
|
||||
|
||||
@@ -124,6 +124,11 @@ public interface IReplayRecordingManager
|
||||
/// Thrown if we are currently recording (<see cref="IsRecording"/> true).
|
||||
/// </exception>
|
||||
Task WaitWriteTasks();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there are any currently running write tasks.
|
||||
/// </summary>
|
||||
bool IsWriting();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
27
Robust.Shared/Replays/SharedReplayRecordingManager.Events.cs
Normal file
27
Robust.Shared/Replays/SharedReplayRecordingManager.Events.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
namespace Robust.Shared.Replays;
|
||||
|
||||
internal abstract partial class SharedReplayRecordingManager
|
||||
{
|
||||
[EventSource(Name = "Robust.ReplayRecording")]
|
||||
public sealed class RecordingEventSource : EventSource
|
||||
{
|
||||
public static RecordingEventSource Log { get; } = new();
|
||||
|
||||
[Event(1)]
|
||||
public void WriteTaskStart(int task) => WriteEvent(1, task);
|
||||
|
||||
[Event(2)]
|
||||
public void WriteTaskStop(int task) => WriteEvent(2, task);
|
||||
|
||||
[Event(3)]
|
||||
public void WriteBatchStart(int index) => WriteEvent(3, index);
|
||||
|
||||
[Event(4)]
|
||||
public void WriteBatchStop(int index) => WriteEvent(4, index);
|
||||
|
||||
[Event(5)]
|
||||
public void WriteQueueBlocked() => WriteEvent(5);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -25,7 +26,7 @@ internal abstract partial class SharedReplayRecordingManager
|
||||
// and even then not for much longer than a couple hundred ms at most.
|
||||
private readonly List<Task> _finalizingWriteTasks = new();
|
||||
|
||||
private static void WriteYaml(RecordingState state, ResPath path, YamlDocument data)
|
||||
private void WriteYaml(RecordingState state, ResPath path, YamlDocument data)
|
||||
{
|
||||
var memStream = new MemoryStream();
|
||||
using var writer = new StreamWriter(memStream);
|
||||
@@ -43,7 +44,7 @@ internal abstract partial class SharedReplayRecordingManager
|
||||
WriteBytes(state, path, memStream.AsMemory());
|
||||
}
|
||||
|
||||
private static void WritePooledBytes(
|
||||
private void WritePooledBytes(
|
||||
RecordingState state,
|
||||
ResPath path,
|
||||
byte[] bytes,
|
||||
@@ -67,6 +68,45 @@ internal abstract partial class SharedReplayRecordingManager
|
||||
});
|
||||
}
|
||||
|
||||
private void WriteTickBatch(
|
||||
RecordingState state,
|
||||
ResPath path,
|
||||
byte[] bytes,
|
||||
int length)
|
||||
{
|
||||
DebugTools.Assert(path.IsRelative, "Zip path should be relative");
|
||||
|
||||
WriteQueueTask(state, () =>
|
||||
{
|
||||
byte[]? buf = null;
|
||||
try
|
||||
{
|
||||
// Compress stream to buffer.
|
||||
// First 4 bytes of buffer are reserved for the length of the uncompressed stream.
|
||||
var bound = ZStd.CompressBound(length);
|
||||
buf = ArrayPool<byte>.Shared.Rent(4 + bound);
|
||||
var compressedLength = state.CompressionContext.Compress2(
|
||||
buf.AsSpan(4, bound),
|
||||
bytes.AsSpan(0, length));
|
||||
|
||||
BitConverter.TryWriteBytes(buf, length);
|
||||
|
||||
Interlocked.Add(ref state.UncompressedSize, length);
|
||||
Interlocked.Add(ref state.CompressedSize, compressedLength);
|
||||
|
||||
var entry = state.Zip.CreateEntry(path.ToString(), CompressionLevel.NoCompression);
|
||||
using var stream = entry.Open();
|
||||
stream.Write(buf, 0, compressedLength + 4);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(bytes);
|
||||
if (buf != null)
|
||||
ArrayPool<byte>.Shared.Return(buf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void WriteToml(RecordingState state, IEnumerable<string> enumerable, ResPath path)
|
||||
{
|
||||
var memStream = new MemoryStream();
|
||||
@@ -75,7 +115,7 @@ internal abstract partial class SharedReplayRecordingManager
|
||||
WriteBytes(state, path, memStream.AsMemory());
|
||||
}
|
||||
|
||||
private static void WriteBytes(
|
||||
private void WriteBytes(
|
||||
RecordingState recState,
|
||||
ResPath path,
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
@@ -91,14 +131,18 @@ internal abstract partial class SharedReplayRecordingManager
|
||||
});
|
||||
}
|
||||
|
||||
private static void WriteQueueTask(RecordingState recState, Action a)
|
||||
private void WriteQueueTask(RecordingState recState, Action a)
|
||||
{
|
||||
var task = recState.WriteCommandChannel.WriteAsync(a);
|
||||
|
||||
// If we have to wait here, it's because the channel is full.
|
||||
// Synchronous waiting is safe here: the writing code doesn't rely on the synchronization context.
|
||||
if (!task.IsCompletedSuccessfully)
|
||||
{
|
||||
RecordingEventSource.Log.WriteQueueBlocked();
|
||||
_sawmill.Warning("Forced to wait on replay write queue. Consider increasing replay.write_channel_size!");
|
||||
task.AsTask().Wait();
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateWriteTasks()
|
||||
@@ -143,6 +187,12 @@ internal abstract partial class SharedReplayRecordingManager
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWriting()
|
||||
{
|
||||
UpdateWriteTasks();
|
||||
return _finalizingWriteTasks.Count > 0;
|
||||
}
|
||||
|
||||
public Task WaitWriteTasks()
|
||||
{
|
||||
if (IsRecording)
|
||||
@@ -154,23 +204,42 @@ internal abstract partial class SharedReplayRecordingManager
|
||||
return Task.WhenAll(_finalizingWriteTasks);
|
||||
}
|
||||
|
||||
private static async Task WriteQueueLoop(ChannelReader<Action> reader, ZipArchive archive)
|
||||
#pragma warning disable RA0004
|
||||
private static void WriteQueueLoop(
|
||||
TaskCompletionSource taskCompletionSource,
|
||||
ChannelReader<Action> reader,
|
||||
ZipArchive archive,
|
||||
ZStdCompressionContext compressionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
var i = 0;
|
||||
while (true)
|
||||
{
|
||||
var result = await reader.WaitToReadAsync();
|
||||
var result = reader.WaitToReadAsync().AsTask().Result;
|
||||
|
||||
if (!result)
|
||||
break;
|
||||
|
||||
var action = await reader.ReadAsync();
|
||||
var action = reader.ReadAsync().AsTask().Result;
|
||||
RecordingEventSource.Log.WriteTaskStart(i);
|
||||
action();
|
||||
RecordingEventSource.Log.WriteTaskStop(i);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
taskCompletionSource.TrySetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
taskCompletionSource.TrySetException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
archive.Dispose();
|
||||
compressionContext.Dispose();
|
||||
}
|
||||
}
|
||||
#pragma warning restore RA0004
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Asynchronous;
|
||||
@@ -107,7 +108,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
|
||||
try
|
||||
{
|
||||
WriteGameState(continueRecording: false);
|
||||
WriteBatch(continueRecording: false);
|
||||
_sawmill.Info("Replay recording stopped!");
|
||||
}
|
||||
catch
|
||||
@@ -137,7 +138,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
_sawmill.Info("Reached requested replay recording length. Stopping recording.");
|
||||
|
||||
if (!continueRecording || _recState.Buffer.Length > _tickBatchSize)
|
||||
WriteGameState(continueRecording);
|
||||
WriteBatch(continueRecording);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -201,12 +202,19 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
new BoundedChannelOptions(NetConf.GetCVar(CVars.ReplayWriteChannelSize))
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = true,
|
||||
AllowSynchronousContinuations = false
|
||||
SingleWriter = true
|
||||
}
|
||||
);
|
||||
|
||||
var writeTask = Task.Run(() => WriteQueueLoop(commandQueue.Reader, zip));
|
||||
var writeTaskTcs = new TaskCompletionSource();
|
||||
// This is on its own thread instead of the thread pool.
|
||||
// Official SS14 servers write replays to an NFS mount,
|
||||
// which causes some write calls to have significant latency (~1s).
|
||||
// We want to avoid clogging thread pool threads with that, so...
|
||||
var writeThread = new Thread(() => WriteQueueLoop(writeTaskTcs, commandQueue.Reader, zip, context));
|
||||
writeThread.Priority = ThreadPriority.BelowNormal;
|
||||
writeThread.Name = "Replay Recording Thread";
|
||||
writeThread.Start();
|
||||
|
||||
_recState = new RecordingState(
|
||||
zip,
|
||||
@@ -216,7 +224,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
Timing.CurTime,
|
||||
recordingEnd,
|
||||
commandQueue.Writer,
|
||||
writeTask,
|
||||
writeTaskTcs.Task,
|
||||
directory,
|
||||
filePath,
|
||||
state
|
||||
@@ -252,26 +260,35 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
_queuedMessages.Add(obj);
|
||||
}
|
||||
|
||||
private void WriteGameState(bool continueRecording = true)
|
||||
private void WriteBatch(bool continueRecording = true)
|
||||
{
|
||||
DebugTools.Assert(_recState != null);
|
||||
|
||||
var batchIndex = _recState.Index++;
|
||||
RecordingEventSource.Log.WriteBatchStart(batchIndex);
|
||||
|
||||
_recState.Buffer.Position = 0;
|
||||
|
||||
// Compress stream to buffer.
|
||||
// First 4 bytes of buffer are reserved for the length of the uncompressed stream.
|
||||
var bound = ZStd.CompressBound((int)_recState.Buffer.Length);
|
||||
var buf = ArrayPool<byte>.Shared.Rent(4 + bound);
|
||||
var length = _recState.CompressionContext.Compress2(buf.AsSpan(4, bound), _recState.Buffer.AsSpan());
|
||||
BitConverter.TryWriteBytes(buf, (int)_recState.Buffer.Length);
|
||||
WritePooledBytes(
|
||||
_recState,
|
||||
ReplayZipFolder / $"{DataFilePrefix}{_recState.Index++}.{Ext}",
|
||||
buf, 4 + length, CompressionLevel.NoCompression);
|
||||
var uncompressed = _recState.Buffer.AsSpan();
|
||||
var poolData = ArrayPool<byte>.Shared.Rent(uncompressed.Length);
|
||||
uncompressed.CopyTo(poolData);
|
||||
|
||||
_recState.UncompressedSize += (int)_recState.Buffer.Length;
|
||||
_recState.CompressedSize += length;
|
||||
if (_recState.UncompressedSize >= _maxUncompressedSize || _recState.CompressedSize >= _maxCompressedSize)
|
||||
WriteTickBatch(
|
||||
_recState,
|
||||
ReplayZipFolder / $"{DataFilePrefix}{batchIndex}.{Ext}",
|
||||
poolData,
|
||||
uncompressed.Length);
|
||||
|
||||
RecordingEventSource.Log.WriteBatchStop(batchIndex);
|
||||
|
||||
// Note: these values are ASYNCHRONOUSLY updated from the replay write thread.
|
||||
// This means reading them here won't get the most up-to-date values,
|
||||
// and we'll probably always be off-by-one.
|
||||
// That's considered acceptable.
|
||||
var uncompressedSize = Interlocked.Read(ref _recState.UncompressedSize);
|
||||
var compressedSize = Interlocked.Read(ref _recState.CompressedSize);
|
||||
|
||||
if (uncompressedSize >= _maxUncompressedSize || compressedSize >= _maxCompressedSize)
|
||||
{
|
||||
_sawmill.Info("Reached max replay recording size. Stopping recording.");
|
||||
continueRecording = false;
|
||||
@@ -288,8 +305,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
if (_recState == null)
|
||||
return;
|
||||
|
||||
_recState.CompressionContext.Dispose();
|
||||
// File stream is always disposed from the worker task.
|
||||
// File stream & compression context is always disposed from the worker task.
|
||||
_recState.WriteCommandChannel.Complete();
|
||||
|
||||
_recState = null;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<PackageReference Include="Linguini.Bundle" Version="0.1.3" />
|
||||
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -167,6 +168,7 @@ namespace Robust.Shared.Timing
|
||||
// announce we are falling behind
|
||||
if ((_timing.RealTime - _lastKeepUp).TotalSeconds >= 15.0)
|
||||
{
|
||||
GameLoopEventSource.Log.CannotKeepUp();
|
||||
_sawmill.Warning("MainLoop: Cannot keep up!");
|
||||
_lastKeepUp = _timing.RealTime;
|
||||
}
|
||||
@@ -174,6 +176,7 @@ namespace Robust.Shared.Timing
|
||||
|
||||
_timing.StartFrame();
|
||||
realFrameEvent = new FrameEventArgs((float)_timing.RealFrameTime.TotalSeconds);
|
||||
GameLoopEventSource.Log.InputStart();
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
@@ -189,6 +192,8 @@ namespace Robust.Shared.Timing
|
||||
_runtimeLog.LogException(exp, "GameLoop Input");
|
||||
}
|
||||
#endif
|
||||
GameLoopEventSource.Log.InputStop();
|
||||
|
||||
_timing.InSimulation = true;
|
||||
var tickPeriod = _timing.CalcAdjustedTickPeriod();
|
||||
|
||||
@@ -216,6 +221,8 @@ namespace Robust.Shared.Timing
|
||||
try
|
||||
{
|
||||
#endif
|
||||
GameLoopEventSource.Log.TickStart(_timing.CurTick.Value);
|
||||
|
||||
using var tickGroup = _prof.Group("Tick");
|
||||
_prof.WriteValue("Tick", ProfData.Int64(_timing.CurTick.Value));
|
||||
|
||||
@@ -232,6 +239,8 @@ namespace Robust.Shared.Timing
|
||||
{
|
||||
Tick?.Invoke(this, simFrameEvent);
|
||||
}
|
||||
|
||||
GameLoopEventSource.Log.TickStop(_timing.CurTick.Value);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception exp)
|
||||
@@ -271,6 +280,7 @@ namespace Robust.Shared.Timing
|
||||
|
||||
// update out of the simulation
|
||||
|
||||
GameLoopEventSource.Log.UpdateStart();
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
@@ -285,6 +295,7 @@ namespace Robust.Shared.Timing
|
||||
_runtimeLog.LogException(exp, "GameLoop Update");
|
||||
}
|
||||
#endif
|
||||
GameLoopEventSource.Log.UpdateStop();
|
||||
|
||||
// render the simulation
|
||||
#if EXCEPTION_TOLERANCE
|
||||
@@ -314,6 +325,8 @@ namespace Robust.Shared.Timing
|
||||
_prof.WriteGroupEnd(profFrameGroupStart, "Frame", profFrameSw);
|
||||
_prof.MarkIndex(profFrameStart, ProfIndexType.Frame);
|
||||
|
||||
GameLoopEventSource.Log.SleepStart();
|
||||
|
||||
// Set sleep to 1 if you want to be nice and give the rest of the timeslice up to the os scheduler.
|
||||
// Set sleep to 0 if you want to use 100% cpu, but still cooperate with the scheduler.
|
||||
// do not call sleep if you want to be 'that thread' and hog 100% cpu.
|
||||
@@ -335,6 +348,8 @@ namespace Robust.Shared.Timing
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
GameLoopEventSource.Log.SleepStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,4 +385,37 @@ namespace Robust.Shared.Timing
|
||||
/// </summary>
|
||||
Delay = 1,
|
||||
}
|
||||
|
||||
[EventSource(Name = "Robust.GameLoop")]
|
||||
internal sealed class GameLoopEventSource : EventSource
|
||||
{
|
||||
public static GameLoopEventSource Log { get; } = new();
|
||||
|
||||
[Event(1)]
|
||||
public void CannotKeepUp() => WriteEvent(1);
|
||||
|
||||
[Event(2)]
|
||||
public void InputStart() => WriteEvent(2);
|
||||
|
||||
[Event(3)]
|
||||
public void InputStop() => WriteEvent(3);
|
||||
|
||||
[Event(4)]
|
||||
public void TickStart(uint tick) => WriteEvent(4, tick);
|
||||
|
||||
[Event(5)]
|
||||
public void TickStop(uint tick) => WriteEvent(5, tick);
|
||||
|
||||
[Event(6)]
|
||||
public void UpdateStart() => WriteEvent(6);
|
||||
|
||||
[Event(7)]
|
||||
public void UpdateStop() => WriteEvent(7);
|
||||
|
||||
[Event(8)]
|
||||
public void SleepStart() => WriteEvent(8);
|
||||
|
||||
[Event(9)]
|
||||
public void SleepStop() => WriteEvent(9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,10 @@ internal sealed unsafe class PrecisionSleepLinuxNanosleep : PrecisionSleep
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value
|
||||
// ReSharper disable IdentifierTypo
|
||||
// ReSharper disable InconsistentNaming
|
||||
[DllImport("libc.so.6", SetLastError=true)]
|
||||
private static extern int nanosleep(timespec* req, timespec* rem);
|
||||
|
||||
@@ -151,4 +155,8 @@ internal sealed unsafe class PrecisionSleepLinuxNanosleep : PrecisionSleep
|
||||
public long tv_sec;
|
||||
public long tv_usec;
|
||||
}
|
||||
// ReSharper restore InconsistentNaming
|
||||
// ReSharper restore IdentifierTypo
|
||||
#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value
|
||||
#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Shared.Timing
|
||||
/// <summary>
|
||||
/// Counts the time (in milliseconds) before firing again.
|
||||
/// </summary>
|
||||
private int _timeCounter;
|
||||
private float _timeCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Time (in milliseconds) between firings.
|
||||
@@ -46,7 +46,7 @@ namespace Robust.Shared.Timing
|
||||
{
|
||||
if (IsActive)
|
||||
{
|
||||
_timeCounter -= (int)(frameTime * 1000);
|
||||
_timeCounter -= frameTime * 1000;
|
||||
|
||||
if (_timeCounter <= 0)
|
||||
{
|
||||
|
||||
@@ -77,7 +77,7 @@ public sealed class ToolshedEnvironment
|
||||
}
|
||||
|
||||
var command = (ToolshedCommand)Activator.CreateInstance(ty)!;
|
||||
IoCManager.InjectDependencies(command);
|
||||
IoCManager.Resolve<IDependencyCollection>().InjectDependencies(command, oneOff: true);
|
||||
|
||||
_commands.Add(command.Name, command);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public sealed class ToolshedEnvironment
|
||||
}
|
||||
|
||||
var command = (ToolshedCommand)Activator.CreateInstance(ty)!;
|
||||
IoCManager.InjectDependencies(command);
|
||||
IoCManager.Resolve<IDependencyCollection>().InjectDependencies(command, oneOff: true);
|
||||
|
||||
_commands.Add(command.Name, command);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed partial class ToolshedManager
|
||||
}
|
||||
else
|
||||
{
|
||||
var parser = (ITypeParser) _typeFactory.CreateInstanceUnchecked(parserType);
|
||||
var parser = (ITypeParser) _typeFactory.CreateInstanceUnchecked(parserType, oneOff: true);
|
||||
parser.PostInject();
|
||||
_log.Info($"Setting up {parserType.PrettyName()}, {parser.Parses.PrettyName()}");
|
||||
_consoleTypeParsers.Add(parser.Parses, parser);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user