Compare commits

...

53 Commits

Author SHA1 Message Date
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
moonheart08
6133fe0808 Version: 151.0.0 2023-08-23 16:05:56 -05:00
Moony
909fd326a0 Toolshed part 2 (#4256)
* Save work.

* three billion tweaks

* Rune-aware parser.

* a

* all shedded out for the night

* a

* oogh

* Publicizes a lot of common generic commands, so custom toolshed envs can include them.

* Implement parsing for all number types.

* i think i might implode

* a

* Tests.

* a

* Enum parser test.

* do u like parsers

* oopls

* ug fixes

* Toolshed is approaching a non-insignificant part of the engine's size.

* Pool toolshed's tests, also type tests.

* bwa

* tests pass :yay:

* Update Robust.Shared/CVars.cs

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>

* how did this not fail tests

* awa

* many levels of silly

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
2023-08-23 16:03:34 -05:00
metalgearsloth
876de4065a Version: 150.0.1 2023-08-23 19:16:17 +10:00
metalgearsloth
60e159f0d0 Fix some merge artifacts (#4297) 2023-08-23 19:15:29 +10:00
252 changed files with 7370 additions and 1856 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,118 @@ END TEMPLATE-->
*None yet*
## 154.2.0
### 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
## 150.0.1
### Bugfixes
* Fix some partial datadefs.
## 150.0.0
### Breaking changes

View File

@@ -161,9 +161,263 @@ command-description-EqualCommand =
Performs an equality comparison, returning true if the inputs are equal.
command-description-NotEqualCommand =
Performs an equality comparison, returning true if the inputs are not equal.
command-description-append =
Appends a value to the input enumerable.
command-description-DefaultIfNullCommand =
Replaces the input with the type's default value if it is null, albeit only for value types (not objects).
command-description-OrValueCommand =
If the input is null, uses the provided alternate value.
command-description-DebugPrintCommand =
Prints the given value transparently, for debug prints in a command run.
command-description-i =
Integer constant.
command-description-f =
Float constant.
command-description-s =
String constant.
command-description-b =
Bool constant.
command-description-join =
Joins two sequences together into one sequence.
command-description-reduce =
Given a block to use as a reducer, turns a sequence into a single value.
The left hand side of the block is implied, and the right hand is stored in $value.
command-description-rep =
Repeats the input value N times to form a sequence.
command-description-take =
Takes N values from the input sequence
command-description-spawn-at =
Spawns an entity at the given coordinates.
command-description-spawn-on =
Spawns an entity on the given entity, at it's coordinates.
command-description-spawn-attached =
Spawns an entity attached to the given entity, at (0 0) relative to it.
command-description-mappos =
Returns an entity's coordinates relative to it's current map.
command-description-pos =
Returns an entity's coordinates.
command-description-tp-coords =
Teleports the target to the given coordinates.
command-description-tp-to =
Teleports the target to the given other entity.
command-description-tp-into =
Teleports the target "into" the given other entity, attaching it at (0 0) relative to it.
command-description-comp-get =
Gets the given component from the given entity.
command-description-comp-add =
Adds the given component to the given entity.
command-description-comp-ensure =
Ensures the given entity has the given component.
command-description-comp-has =
Check if the given entity has the given component.
command-description-AddVecCommand =
Adds a scalar (single value) to every element in the input.
command-description-SubVecCommand =
Subtracts a scalar (single value) from every element in the input.
command-description-MulVecCommand =
Multiplies a scalar (single value) by every element in the input.
command-description-DivVecCommand =
Divides every element in the input by a scalar (single value).
command-description-rng-to =
Returns a number from its input to its argument (i.e. n..m inclusive)
command-description-rng-from =
Returns a number to its input from its argument (i.e. m..n inclusive)
command-description-rng-prob =
Returns a boolean based on the input probability/chance (from 0 to 1)
command-description-sum =
Computes the sum of the input.
command-description-bin =
"Bins" the input, counting up how many times each unique element occurs.
command-description-extremes =
Returns the two extreme ends of a list, interwoven.
command-description-sortby =
Sorts the input least to greatest by the computed key.
command-description-sortmapby =
Sorts the input least to greatest by the computed key, replacing the value with it's computed key afterward.
command-description-sort =
Sorts the input least to greatest.
command-description-sortdownby =
Sorts the input greatest to least by the computed key.
command-description-sortmapdownby =
Sorts the input greatest to least by the computed key, replacing the value with it's computed key afterward.
command-description-sortdown =
Sorts the input greatest to least.
command-description-iota =
Returns a list of numbers 1 to N.
command-description-to =
Returns a list of numbers N to M.
command-description-curtick =
The current game tick.
command-description-curtime =
The current game time (a TimeSpan)
command-description-realtime =
The current realtime since startup (a TimeSpan)
command-description-servertime =
The current server game time, or zero if we are the server (a TimeSpan)
command-description-replace =
Replaces the input entities with the given prototype, preserving position and rotation (but nothing else)
command-description-allcomps =
Returns all components on the given entity.
command-description-entitysystemupdateorder-tick =
Lists the tick update order of entity systems.
command-description-entitysystemupdateorder-frame =
Lists the frame update order of entity systems.
command-description-more =
Prints the contents of $more, i.e. any extras that Toolshed didn't print from the last command.
command-description-ModulusCommand =
Computes the modulus of two values.
This is usually remainder, check C#'s documentation for the type.
command-description-ModVecCommand =
Performs the modulus operation over the input with the given constant right-hand value.
command-description-BitAndNotCommand =
Performs bitwise AND-NOT over the input.
command-description-BitOrNotCommand =
Performs bitwise OR-NOT over the input.
command-description-BitXnorCommand =
Performs bitwise XNOR over the input.
command-description-BitNotCommand =
Performs bitwise NOT on the input.
command-description-abs =
Computes the absolute value of the input (removing the sign)
command-description-average =
Computes the average (arithmetic mean) of the input.
command-description-bibytecount =
Returns the size of the input in bytes, given that the input implements IBinaryInteger.
This is NOT sizeof.
command-description-shortestbitlength =
Returns the minimum number of bits needed to represent the input value.
command-description-countleadzeros =
Counts the number of leading binary zeros in the input value.
command-description-counttrailingzeros =
Counts the number of trailing binary zeros in the input value.
command-description-fpi =
pi (3.14159...) as a float.
command-description-fe =
e (2.71828...) as a float.
command-description-ftau =
tau (6.28318...) as a float.
command-description-fepsilon =
The epsilon value for a float, exactly 1.4e-45.
command-description-dpi =
pi (3.14159...) as a double.
command-description-de =
e (2.71828...) as a double.
command-description-dtau =
tau (6.28318...) as a double.
command-description-depsilon =
The epsilon value for a double, exactly 4.9406564584124654E-324.
command-description-hpi =
pi (3.14...) as a half.
command-description-he =
e (2.71...) as a half.
command-description-htau =
tau (6.28...) as a half.
command-description-hepsilon =
The epsilon value for a half, exactly 5.9604645E-08.
command-description-floor =
Returns the floor of the input value (rounding toward zero).
command-description-ceil =
Returns the ceil of the input value (rounding away from zero).
command-description-round =
Rounds the input value.
command-description-trunc =
Truncates the input value.
command-description-round2frac =
Rounds the input value to the specified number of fractional digits.
command-description-exponentbytecount =
Returns the number of bytes required to store the exponent.
command-description-significandbytecount =
Returns the number of bytes required to store the significand.
command-description-significandbitcount =
Returns the exact bit length of the significand.
command-description-exponentshortestbitcount =
Returns the minimum number of bits to store the exponent.
command-description-stepnext =
Steps to the next float value, adding one to the significand with carry.
command-description-stepprev =
Steps to the previous float value, subtracting one from the significand with carry.
command-description-checkedto =
Converts from the input numeric type to the target, erroring if not possible.
command-description-saturateto =
Converts from the input numeric type to the target, saturating if the value is out of range.
For example, converting 382 to a byte would saturate to 255 (the maximum value of a byte).
command-description-truncto =
Converts from the input numeric type to the target, with truncation.
In the case of integers, this is a bit cast with sign extension.
command-description-iscanonical =
Returns whether the input is in canonical form.
command-description-iscomplex =
Returns whether the input is a complex number (by value, not by type)
command-description-iseven =
Returns whether the input is even.
Not a javascript package.
command-description-isodd =
Returns whether the input is odd.
command-description-isfinite =
Returns whether the input is finite.
command-description-isimaginary =
Returns whether the input is purely imaginary (no real part).
command-description-isinfinite =
Returns whether the input is infinite.
command-description-isinteger =
Returns whether the input is an integer (by value, not by type)
command-description-isnan =
Returns whether the input is Not a Number (NaN).
This is a special floating point value, so this is by value, not by type.
command-description-isnegative =
Returns whether the input is negative.
command-description-ispositive =
Returns whether the input is positive.
command-description-isreal =
Returns whether the input is purely real (no imaginary part).
command-description-issubnormal =
Returns whether the input is in sub-normal form.
command-description-iszero =
Returns whether the input is zero.
command-description-pow =
Computes the power of its lefthand to its righthand. x^y.
command-description-sqrt =
Computes the square root of its input.
command-description-cbrt =
Computes the cube root of its input.
command-description-root =
Computes the Nth root of its input.
command-description-hypot =
Computes the hypotenuse of a triangle with the given sides A and B.
command-description-sin =
Computes the sine of the input.
command-description-sinpi =
Computes the sine of the input multiplied by pi.
command-description-asin =
Computes the arcsine of the input.
command-description-asinpi =
Computes the arcsine of the input multiplied by pi.
command-description-cos =
Computes the cosine of the input.
command-description-cospi =
Computes the cosine of the input multiplied by pi.
command-description-acos =
Computes the arcosine of the input.
command-description-acospi =
Computes the arcosine of the input multiplied by pi.
command-description-tan =
Computes the tangent of the input.
command-description-tanpi =
Computes the tangent of the input multiplied by pi.
command-description-atan =
Computes the arctangent of the input.
command-description-atanpi =
Computes the arctangent of the input multiplied by pi.
command-description-iterate =
Iterates the given function over the input N times, returning a list of results.
Think of this like successively applying the function to a value, tracking all the intermediate values.
command-description-pick =
Picks a random value from the input.
command-description-tee =
Tees the input into the given block, ignoring the block's result.
This essentially lets you have a branch in your code to do multiple operations on one value.
command-description-cmd-info =
Returns a CommandSpec for the given command.
On it's own, this means it'll print the comamnd's help message.
command-description-comp-rm =
Removes the given component from the entity.

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

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

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

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

View File

@@ -142,7 +142,8 @@ namespace Robust.Server.Console
ctx.WriteLine(err.Describe());
}
shell.WriteLine(_toolshed.PrettyPrintType(res));
shell.WriteLine(FormattedMessage.FromMarkupPermissive(_toolshed.PrettyPrintType(res, out var more, moreUsed: true)));
ctx.WriteVar("more", more);
}
}
catch (Exception e)
@@ -217,8 +218,8 @@ namespace Robust.Server.Console
if ((result.Options.Length == 0 && result.Hint is null) || message.Args.Length <= 1)
{
var parser = new ForwardParser(message.ArgString, _toolshed);
CommandRun.TryParse(false, true, parser, null, null, false, out _, out var completions, out _);
var parser = new ParserContext(message.ArgString, _toolshed);
CommandRun.TryParse(true, parser, null, null, false, out _, out var completions, out _);
if (completions == null)
{
goto done;

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

@@ -10,7 +10,14 @@ namespace Robust.Shared.Maths
[Serializable]
[StructLayout(LayoutKind.Sequential)]
// ReSharper disable once InconsistentNaming
public struct Vector2i : IEquatable<Vector2i>, ISpanFormattable
public struct Vector2i :
IEquatable<Vector2i>,
ISpanFormattable,
IAdditionOperators<Vector2i, Vector2i, Vector2i>,
ISubtractionOperators<Vector2i, Vector2i, Vector2i>,
IMultiplyOperators<Vector2i, Vector2i, Vector2i>,
IMultiplyOperators<Vector2i, int, Vector2i>,
IComparisonOperators<Vector2i, Vector2i, bool>
{
public static readonly Vector2i Zero = (0, 0);
public static readonly Vector2i One = (1, 1);
@@ -213,5 +220,25 @@ namespace Robust.Shared.Maths
out charsWritten,
$"({X}, {Y})");
}
public static bool operator >(Vector2i left, Vector2i right)
{
return left.LengthSquared > right.LengthSquared;
}
public static bool operator >=(Vector2i left, Vector2i right)
{
return left.LengthSquared >= right.LengthSquared;
}
public static bool operator <(Vector2i left, Vector2i right)
{
return left.LengthSquared < right.LengthSquared;
}
public static bool operator <=(Vector2i left, Vector2i right)
{
return left.LengthSquared <= right.LengthSquared;
}
}
}

View File

@@ -42,7 +42,14 @@ namespace Robust.Shared.Maths
/// </remarks>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector3 : IEquatable<Vector3>, ISpanFormattable
public struct Vector3 :
IEquatable<Vector3>,
ISpanFormattable,
IAdditionOperators<Vector3, Vector3, Vector3>,
ISubtractionOperators<Vector3, Vector3, Vector3>,
IMultiplyOperators<Vector3, Vector3, Vector3>,
IMultiplyOperators<Vector3, float, Vector3>,
IComparisonOperators<Vector3, Vector3, bool>
{
#region Fields
@@ -1204,5 +1211,25 @@ namespace Robust.Shared.Maths
}
#endregion
public static bool operator >(Vector3 left, Vector3 right)
{
return left.LengthSquared > right.LengthSquared;
}
public static bool operator >=(Vector3 left, Vector3 right)
{
return left.LengthSquared >= right.LengthSquared;
}
public static bool operator <(Vector3 left, Vector3 right)
{
return left.LengthSquared < right.LengthSquared;
}
public static bool operator <=(Vector3 left, Vector3 right)
{
return left.LengthSquared <= right.LengthSquared;
}
}
}

View File

@@ -30,6 +30,7 @@ using System.Runtime.InteropServices;
using System.Xml.Serialization;
using Robust.Shared.Utility;
using Vector2 = System.Numerics.Vector2;
using System.Numerics;
namespace Robust.Shared.Maths
{
@@ -39,7 +40,13 @@ namespace Robust.Shared.Maths
/// </remarks>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector4 : IEquatable<Vector4>, ISpanFormattable
public struct Vector4 :
IEquatable<Vector4>,
ISpanFormattable,
IAdditionOperators<Vector4, Vector4, Vector4>,
ISubtractionOperators<Vector4, Vector4, Vector4>,
IMultiplyOperators<Vector4, Vector4, Vector4>,
IComparisonOperators<Vector4, Vector4, bool>
{
#region Fields
@@ -986,5 +993,30 @@ namespace Robust.Shared.Maths
}
#endregion IEquatable<Vector4> Members
public static Vector4 operator *(Vector4 left, Vector4 right)
{
return new(left.X * right.X, left.Y * right.Y, left.Z * right.Z, left.W * right.W);
}
public static bool operator >(Vector4 left, Vector4 right)
{
return left.LengthSquared > right.LengthSquared;
}
public static bool operator >=(Vector4 left, Vector4 right)
{
return left.LengthSquared >= right.LengthSquared;
}
public static bool operator <(Vector4 left, Vector4 right)
{
return left.LengthSquared < right.LengthSquared;
}
public static bool operator <=(Vector4 left, Vector4 right)
{
return left.LengthSquared <= right.LengthSquared;
}
}
}

View File

@@ -35,6 +35,9 @@ namespace Robust.Shared.Scripting
[field: Dependency] public ToolshedManager shed { get; } = default!;
public ToolshedManager Toolshed => shed;
public ToolshedEnvironment Environment => shed.DefaultEnvironment;
protected ScriptGlobalsShared(IDependencyCollection dependencies)
{
dependencies.InjectDependencies(this);

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Robust.UnitTesting", AllInternalsVisible = true)]

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

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

@@ -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);
}
@@ -606,8 +618,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 />
@@ -1422,7 +1432,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 +1527,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,18 +20,21 @@ 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 DependencyCollection _systemDependencyCollection = default!;
private ISawmill _sawmill = default!;
internal DependencyCollection SystemDependencyCollection = default!;
private readonly List<Type> _systemTypes = new();
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
@@ -63,12 +66,12 @@ namespace Robust.Shared.GameObjects
public T GetEntitySystem<T>()
where T : IEntitySystem
{
return _systemDependencyCollection.Resolve<T>();
return SystemDependencyCollection.Resolve<T>();
}
public T? GetEntitySystemOrNull<T>() where T : IEntitySystem
{
_systemDependencyCollection.TryResolveType<T>(out var system);
SystemDependencyCollection.TryResolveType<T>(out var system);
return system;
}
@@ -76,7 +79,7 @@ namespace Robust.Shared.GameObjects
public void Resolve<T>([NotNull] ref T? instance)
where T : IEntitySystem
{
_systemDependencyCollection.Resolve(ref instance);
SystemDependencyCollection.Resolve(ref instance);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -84,7 +87,7 @@ namespace Robust.Shared.GameObjects
where T1 : IEntitySystem
where T2 : IEntitySystem
{
_systemDependencyCollection.Resolve(ref instance1, ref instance2);
SystemDependencyCollection.Resolve(ref instance1, ref instance2);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -93,7 +96,7 @@ namespace Robust.Shared.GameObjects
where T2 : IEntitySystem
where T3 : IEntitySystem
{
_systemDependencyCollection.Resolve(ref instance1, ref instance2, ref instance3);
SystemDependencyCollection.Resolve(ref instance1, ref instance2, ref instance3);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -103,14 +106,14 @@ namespace Robust.Shared.GameObjects
where T3 : IEntitySystem
where T4 : IEntitySystem
{
_systemDependencyCollection.Resolve(ref instance1, ref instance2, ref instance3, ref instance4);
SystemDependencyCollection.Resolve(ref instance1, ref instance2, ref instance3, ref instance4);
}
/// <inheritdoc />
public bool TryGetEntitySystem<T>([NotNullWhen(true)] out T? entitySystem)
where T : IEntitySystem
{
return _systemDependencyCollection.TryResolveType<T>(out entitySystem);
return SystemDependencyCollection.TryResolveType<T>(out entitySystem);
}
/// <inheritdoc />
@@ -122,7 +125,7 @@ namespace Robust.Shared.GameObjects
var excludedTypes = new HashSet<Type>();
_systemDependencyCollection = new(_dependencyCollection);
SystemDependencyCollection = new(_dependencyCollection);
var subTypes = new Dictionary<Type, Type>();
_systemTypes.Clear();
IEnumerable<Type> systems;
@@ -138,9 +141,9 @@ 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);
SystemDependencyCollection.Register(type);
_systemTypes.Add(type);
excludedTypes.Add(type);
@@ -173,21 +176,21 @@ namespace Robust.Shared.GameObjects
foreach (var (baseType, type) in subTypes)
{
_systemDependencyCollection.Register(baseType, type, overwrite: true);
SystemDependencyCollection.Register(baseType, type, overwrite: true);
_systemTypes.Remove(baseType);
}
_systemDependencyCollection.BuildGraph();
SystemDependencyCollection.BuildGraph();
foreach (var systemType in _systemTypes)
{
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
var system = (IEntitySystem)SystemDependencyCollection.ResolveType(systemType);
system.Initialize();
SystemLoaded?.Invoke(this, new SystemChangedArgs(system));
}
// Create update order for entity systems.
var (fUpdate, update) = CalculateUpdateOrder(_systemTypes, subTypes, _systemDependencyCollection);
var (fUpdate, update) = CalculateUpdateOrder(_systemTypes, subTypes, SystemDependencyCollection);
_frameUpdateOrder = fUpdate.ToArray();
_updateOrder = update
@@ -263,8 +266,8 @@ namespace Robust.Shared.GameObjects
// System.Values is modified by RemoveSystem
foreach (var systemType in _systemTypes)
{
if(_systemDependencyCollection == null) continue;
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
if(SystemDependencyCollection == null) continue;
var system = (IEntitySystem)SystemDependencyCollection.ResolveType(systemType);
SystemUnloaded?.Invoke(this, new SystemChangedArgs(system));
system.Shutdown();
_entityManager.EventBus.UnsubscribeEvents(system);
@@ -280,7 +283,7 @@ namespace Robust.Shared.GameObjects
_updateOrder = Array.Empty<UpdateReg>();
_frameUpdateOrder = Array.Empty<IEntitySystem>();
_initialized = false;
_systemDependencyCollection?.Clear();
SystemDependencyCollection?.Clear();
}
/// <inheritdoc />
@@ -357,12 +360,12 @@ namespace Robust.Shared.GameObjects
public bool TryGetEntitySystem(Type sysType, [NotNullWhen(true)] out object? system)
{
return _systemDependencyCollection.TryResolveType(sysType, out system);
return SystemDependencyCollection.TryResolveType(sysType, out system);
}
public object GetEntitySystem(Type sysType)
{
return _systemDependencyCollection.ResolveType(sysType);
return SystemDependencyCollection.ResolveType(sysType);
}
private static bool NeedsUpdate(Type 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>

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

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

@@ -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()
@@ -154,23 +198,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

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

@@ -3,11 +3,17 @@
namespace Robust.Shared.Toolshed.Commands.Debug;
[ToolshedCommand]
internal sealed class FuckCommand : ToolshedCommand
public sealed class FuckCommand : ToolshedCommand
{
[CommandImplementation]
public object? Fuck([PipedArgument] object? value)
{
throw new Exception("fuck!");
}
[CommandImplementation]
public object? Fuck()
{
throw new Exception("fuck!");
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Shared.Toolshed.Commands;
[ToolshedCommand]
internal sealed class EcsCompCommand : ToolshedCommand
{
[Dependency] private readonly IComponentFactory _factory = default!;
[CommandImplementation("listty")]
public IEnumerable<Type> ListTy()
{
return _factory.AllRegisteredTypes;
}
}

View File

@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Toolshed.TypeParsers;
namespace Robust.Shared.Toolshed.Commands.Entities;
[ToolshedCommand]
internal sealed class CompCommand : ToolshedCommand
{
public override Type[] TypeParameterParsers => new[] {typeof(ComponentType)};
[CommandImplementation]
public IEnumerable<T> CompEnumerable<T>([PipedArgument] IEnumerable<EntityUid> input)
where T: IComponent
{
return input.Where(HasComp<T>).Select(Comp<T>);
}
[CommandImplementation]
public T? CompDirect<T>([PipedArgument] EntityUid input)
where T : IComponent
{
TryComp(input, out T? res);
return res;
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Toolshed.Commands.Entities.Components;
[ToolshedCommand]
internal sealed class AllCompsCommand : ToolshedCommand
{
[CommandImplementation]
public IEnumerable<IComponent> All([PipedArgument] EntityUid input)
=> EntityManager.GetComponents(input);
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Toolshed.TypeParsers;
namespace Robust.Shared.Toolshed.Commands.Entities.Components;
[ToolshedCommand]
internal sealed class CompCommand : ToolshedCommand
{
public override Type[] TypeParameterParsers => new[] {typeof(ComponentType)};
[CommandImplementation("get")]
public IEnumerable<T> CompEnumerable<T>([PipedArgument] IEnumerable<EntityUid> input)
where T: IComponent
{
return input.Where(HasComp<T>).Select(Comp<T>);
}
[CommandImplementation("get")]
public T? CompDirect<T>([PipedArgument] EntityUid input)
where T : IComponent
{
TryComp(input, out T? res);
return res;
}
[CommandImplementation("add")]
public EntityUid Add<T>([PipedArgument] EntityUid input)
where T: Component, new()
{
AddComp<T>(input);
return input;
}
[CommandImplementation("add")]
public IEnumerable<EntityUid> Add<T>([PipedArgument] IEnumerable<EntityUid> input)
where T : Component, new()
=> input.Select(Add<T>);
[CommandImplementation("rm")]
public EntityUid Rm<T>([PipedArgument] EntityUid input)
where T: Component, new()
{
RemComp<T>(input);
return input;
}
[CommandImplementation("rm")]
public IEnumerable<EntityUid> Rm<T>([PipedArgument] IEnumerable<EntityUid> input)
where T : Component, new()
=> input.Select(Rm<T>);
[CommandImplementation("ensure")]
public EntityUid Ensure<T>([PipedArgument] EntityUid input)
where T: Component, new()
{
EnsureComp<T>(input);
return input;
}
[CommandImplementation("ensure")]
public IEnumerable<EntityUid> Ensure<T>([PipedArgument] IEnumerable<EntityUid> input)
where T : Component, new()
=> input.Select(Ensure<T>);
[CommandImplementation("has")]
public bool Has<T>([PipedArgument] EntityUid input)
where T: IComponent
{
return HasComp<T>(input);
}
[CommandImplementation("has")]
public IEnumerable<bool> Has<T>([PipedArgument] IEnumerable<EntityUid> input)
where T : IComponent
=> input.Select(Has<T>);
}

View File

@@ -12,14 +12,14 @@ internal sealed class DoCommand : ToolshedCommand
private SharedTransformSystem? _xformSys;
[CommandImplementation, TakesPipedTypeAsGeneric]
public void Do<T>(
public IEnumerable<T> Do<T>(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<T> input,
[CommandArgument] string command)
{
if (ctx is not OldShellInvocationContext { } reqCtx)
{
throw new NotImplementedException();
throw new NotImplementedException("do can only be executed in a shell invocation context. Some commands like emplace provide their own context.");
}
_xformSys ??= GetSys<SharedTransformSystem>();
@@ -31,14 +31,20 @@ internal sealed class DoCommand : ToolshedCommand
if (i is EntityUid id)
{
var worldPos = _xformSys.GetWorldPosition(id, xformQ);
var localPos = xformQ.GetComponent(id).Coordinates;
cmdStr = cmdStr
.Replace("$ID", id.ToString())
.Replace("$PID", (reqCtx.Session?.AttachedEntity ?? EntityUid.Invalid).ToString())
.Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture))
.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture));
.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture))
.Replace("$LX", localPos.X.ToString(CultureInfo.InvariantCulture))
.Replace("$LY", localPos.Y.ToString(CultureInfo.InvariantCulture));
}
cmdStr = cmdStr.Replace("$SELF", i!.ToString() ?? "");
shell.ExecuteCommand(cmdStr);
yield return i;
}
}
}

View File

@@ -10,7 +10,10 @@ internal sealed class NearbyCommand : ToolshedCommand
private EntityLookupSystem? _lookup;
[CommandImplementation]
public IEnumerable<EntityUid> Nearby([PipedArgument] IEnumerable<EntityUid> input, [CommandArgument] float range)
public IEnumerable<EntityUid> Nearby(
[PipedArgument] IEnumerable<EntityUid> input,
[CommandArgument] float range
)
{
_lookup ??= GetSys<EntityLookupSystem>();
return input.SelectMany(x => _lookup.GetEntitiesInRange(x, range)).Distinct();

Some files were not shown because too many files have changed in this diff Show More