Compare commits

...

63 Commits

Author SHA1 Message Date
Pieter-Jan Briers
a654a6cf43 Version: 156.0.2 2024-03-10 21:23:36 +01:00
Pieter-Jan Briers
6211cf2e03 global.json force .NET 7 2024-03-10 21:23:14 +01:00
Pieter-Jan Briers
a522b4cf86 Version: 156.0.1 2024-03-10 20:50:29 +01:00
Pieter-Jan Briers
6d33be8c0f Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 688efac67b634c613539b783a9fb6e679948cd53)
2024-03-10 20:50:29 +01:00
metalgearsloth
cb1d4ae843 Version: 156.0.0 2023-09-10 21:48:19 +10:00
metalgearsloth
039b70f502 Revert "Remove IContainer and move some functions to the system (#4351)" (#4361) 2023-09-10 21:46:17 +10:00
metalgearsloth
7892cc895f Tooltip QoL (#4330) 2023-09-10 20:23:52 +10:00
ElectroJr
77108284b8 Version: 155.0.0 2023-09-09 22:26:10 -04:00
Leon Friedrich
5e21dbdd7f Remove IContainer and move some functions to the system (#4351) 2023-09-10 12:17:00 +10:00
ShadowCommander
8274623edb Add a command to hide replay UI (#4355) 2023-09-10 10:49:34 +10:00
Leon Friedrich
e923d69083 Miscellaneous replay related changes (#4354) 2023-09-10 10:48:42 +10:00
PrPleGoo
6e8ab5ce78 Ignore deleted components while raising events. (#4311) 2023-09-10 10:48:00 +10:00
metalgearsloth
f905ea631b Raise MapInitEvent on components added after spawn (#4290) 2023-09-10 10:47:18 +10:00
Pieter-Jan Briers
be54c41891 Fix localization file error formatting. 2023-09-09 20:10:55 +02:00
DrSmugleaf
33184ecfa5 Version: 154.2.0 2023-09-08 14:44:37 -07:00
Morb
11cf0c1703 Fix turn into Invalid direction (#4350) 2023-09-08 11:14:14 +10:00
DrSmugleaf
528544b7a2 Remove redundant new() constraint from EntitySystem.AddComp (#4353) 2023-09-07 11:10:35 +10:00
chromiumboy
8571d7e7b5 Added method to search nested containers for a specified component (#4337) 2023-09-06 10:24:14 +10:00
DrSmugleaf
0f06423b7a Remove RobustAutoGenerated from partials generated by serialization (#4338) 2023-09-06 10:23:28 +10:00
PrPleGoo
eb9e0ffefc Advertise to multiple hubs simultaneously (#4285) 2023-09-06 00:25:11 +02:00
metalgearsloth
903619ecef Version: 154.1.0 2023-09-05 00:14:15 +10:00
DrSmugleaf
879c6ea538 Make joint initialization only log under IsFirstTimePredicted (#4346) 2023-09-02 21:45:21 -07:00
metalgearsloth
5478545aeb Mark Dirty(comp) as obsolete (#4344) 2023-09-03 06:56:04 +10:00
Wrexbe (Josh)
650929dcbb Add Timespan helpers (#4342) 2023-08-31 11:51:06 -07:00
DrSmugleaf
a289659b49 Version: 154.0.0 2023-08-30 21:22:39 -07:00
DrSmugleaf
85d15c21e1 Move IPlayerData interface to shared (#4339) 2023-08-30 21:17:32 -07:00
DrSmugleaf
bcd1566440 Respect ignored prototypes even if the kind name is registered (#4340) 2023-08-30 21:01:47 -07:00
Julian Giebel
749ac2c364 Fix some multiline edit issues (#4332) 2023-08-30 09:36:36 +10:00
Pieter-Jan Briers
5eed3bc281 Make toolshed stuff oneOff IoC injections.
Removes a ton of IoC injector delegates.
2023-08-29 21:56:57 +02:00
Pieter-Jan Briers
d78f378493 More event sources 2023-08-29 21:43:02 +02:00
DrSmugleaf
eef44c15cf Version: 153.0.0 2023-08-28 16:00:34 -07:00
DrSmugleaf
3d1b2418f9 Remove redundant DebugTools.AssertNotNull(netId) in ClientGameStateManager (#4333) 2023-08-28 15:57:46 -07:00
DrSmugleaf
6b49a86ee5 Make EntityManager.AddComponent with a comp instance set the owner if its default, add system proxy (#4328) 2023-08-28 15:31:17 -07:00
metalgearsloth
cd13cd3cd8 Delete EntityDeletedMessage (#4329) 2023-08-29 05:21:29 +10:00
metalgearsloth
2b8d8d6636 Remove UI comprefs (#4320) 2023-08-28 03:49:57 +10:00
Pieter-Jan Briers
409fe1a125 Some warning fixes 2023-08-27 15:35:15 +02:00
Pieter-Jan Briers
ab5db4641c Update Lidgren to v0.2.6 2023-08-27 13:18:19 +02:00
metalgearsloth
064e8ee365 Minor CompAdd stuff (#4327) 2023-08-27 12:54:09 +02:00
metalgearsloth
02dcff7eae Remove CollisionWake comp removal sub (#4326) 2023-08-27 15:47:46 +10:00
metalgearsloth
e1e5f8de54 Fix master build (#4325) 2023-08-27 15:33:15 +10:00
Leon Friedrich
d5ba822a79 Remove redundant prototype resolving (#4322) 2023-08-27 15:24:25 +10:00
Leon Friedrich
f448c6b8fa Add RecursiveMoveBenchmark (#4323) 2023-08-27 15:24:04 +10:00
Pieter-Jan Briers
5e1d80be35 Attempts to fix replay recording performance issues.
Replays now use a dedicated thread (rather than thread pool) for write operations.

Moved batch operations to this thread as well. They were previously happening during PVS. Looking at some trace files these compression ops can easily take 5+ ms in some cases, so moving them somewhere else is appreciated.

Added EventSource instrumentation for PVS and replay recording.
2023-08-27 02:15:15 +02:00
DrSmugleaf
01546f32da Version: 152.0.0 2023-08-26 15:31:30 -07:00
DrSmugleaf
aeeaaaefc5 Fix not running hooks when copying non-byref data definition fields without a custom serializer (#4324) 2023-08-26 15:20:46 -07:00
Leon Friedrich
b6c8060af1 Add new PVS test (#4312) 2023-08-26 22:23:32 +10:00
DrSmugleaf
99685838da Fix entity spawn tests having instance per test lifecycle with non static setup and tear downs (#4321) 2023-08-26 22:16:47 +10:00
ike709
8917b29255 Convert Tile.TypeId to an int (#4307)
Co-authored-by: ike709 <ike709@github.com>
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
2023-08-26 22:16:14 +10:00
DrSmugleaf
f0c4d7c5eb Update CI to use setup-dotnet 3.2.0 and checkout 3.6.0 (#4319) 2023-08-25 15:45:19 -07:00
Pieter-Jan Briers
6a00c62d3c Allow content to implement own logic for BUI range checks. (#4301)
They were currently inconsistent with interaction logic in SS14. Please fix and thank.
2023-08-26 09:29:43 +12:00
metalgearsloth
fc3116fca5 Remove ComponentDeleted C# event (#4317)
No one's used it for 12 years probably no reason to obs first.
2023-08-26 09:24:33 +12:00
metalgearsloth
98c1397b3a Remove EntityStarted C# event (#4318) 2023-08-25 19:57:43 +02:00
Leon Friedrich
2464bb6c2f Remove and obsolete ComponentExt functions (#4313) 2023-08-25 23:20:39 +10:00
Leon Friedrich
709142acee Fix prototype manager not being initialized in tests (#4294) 2023-08-24 19:02:02 +02:00
Kevin Zheng
af4e3e5e1c Remove personally-identifiable file paths from client logs (#4267) 2023-08-24 18:56:48 +02:00
Kevin Zheng
d51a18c6ea Fix build with USE_SYSTEM_SQLITE (#4266) 2023-08-24 18:55:54 +02:00
metalgearsloth
d5c3d4c0c9 Add system to CompNetworkGenerator (#4310)
Robust doesn't global using this but content does so any automatic comp states on engine don't work.
2023-08-24 04:26:30 -07:00
metalgearsloth
a4474d8df8 Remove IContainerManager (#4308) 2023-08-24 16:39:35 +10:00
DrSmugleaf
d66f7c7c06 Disable obsoletion and inherited member hidden warnings in serialization source generated code (#4302) 2023-08-24 13:04:32 +10:00
DrSmugleaf
b6879869d6 Add support for long values in CVars (#4299) 2023-08-24 00:57:09 +02:00
Errant
815b8e0c48 removed warning for glibc (#4296) 2023-08-24 00:56:38 +02:00
Arimah Greene
ef4e3baa7f Fix Timer drift (#4300) 2023-08-24 00:54:30 +02:00
Moony
270ddb5a53 Update CODEOWNERS 2023-08-23 16:23:38 -05:00
119 changed files with 2005 additions and 982 deletions

5
.github/CODEOWNERS vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -54,6 +54,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

View 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;
}
}

View File

@@ -167,7 +167,6 @@ namespace Robust.Client
_reflectionManager.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDefaultPrototypes();
_prototypeManager.ResolveResults();
_userInterfaceManager.Initialize();
_eyeManager.Initialize();
_entityManager.Initialize();

View File

@@ -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;

View File

@@ -6,7 +6,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
[RegisterComponent]
public sealed partial class ClientUserInterfaceComponent : SharedUserInterfaceComponent
{
[ViewVariables]

View File

@@ -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");
}
}

View File

@@ -500,7 +500,6 @@ namespace Robust.Client.GameStates
foreach (var (netId, comp) in netComps.Value)
{
DebugTools.AssertNotNull(netId);
if (!comp.NetSyncEnabled)
continue;

View File

@@ -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);

View File

@@ -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))
{

View File

@@ -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);
}
}

View File

@@ -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; }

View 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;
}
}

View File

@@ -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;

View File

@@ -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" />

View File

@@ -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;
}

View File

@@ -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));
}
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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];

View File

@@ -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;

View File

@@ -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; }

View File

@@ -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

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Player

View File

@@ -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;

View File

@@ -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" />

View File

@@ -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");
}
}

View File

@@ -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);
}

View File

@@ -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*

View 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}
}}

View File

@@ -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

View File

@@ -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.

View File

@@ -127,6 +127,11 @@ namespace Robust.Shared.Configuration
return Parse.Float(input);
}
if (type == typeof(long))
{
return long.Parse(input);
}
throw new NotSupportedException();
}
}

View File

@@ -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>();

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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!;

View File

@@ -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);
}
}

View File

@@ -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));

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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&lt;T&gt;(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&lt;T&gt;(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);

View File

@@ -203,7 +203,10 @@ namespace Robust.Shared.GameObjects
#endregion
void IPostInjectInit.PostInject()
void IPostInjectInit.PostInject() => PostInject();
protected virtual void PostInject()
{
Log = LogManager.GetSawmill(SawmillName);

View File

@@ -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

View File

@@ -1,12 +0,0 @@
namespace Robust.Shared.GameObjects
{
public sealed class EntityDeletedMessage : EntityEventArgs
{
public EntityUid Entity { get; }
public EntityDeletedMessage(EntityUid entity)
{
Entity = entity;
}
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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)

View File

@@ -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();
}
}

View File

@@ -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>(

View File

@@ -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.

View File

@@ -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)

View File

@@ -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";

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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();

View File

@@ -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.

View File

@@ -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))

View File

@@ -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;

View File

@@ -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.

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
namespace Robust.Shared.Players
{

View File

@@ -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}'");
}

View File

@@ -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}'");
}

View File

@@ -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");

View File

@@ -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>

View 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);
}
}

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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.
}

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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