Compare commits

...

74 Commits

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

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

Added EventSource instrumentation for PVS and replay recording.
2023-08-27 02:15:15 +02:00
DrSmugleaf
01546f32da Version: 152.0.0 2023-08-26 15:31:30 -07:00
DrSmugleaf
aeeaaaefc5 Fix not running hooks when copying non-byref data definition fields without a custom serializer (#4324) 2023-08-26 15:20:46 -07:00
Leon Friedrich
b6c8060af1 Add new PVS test (#4312) 2023-08-26 22:23:32 +10:00
DrSmugleaf
99685838da Fix entity spawn tests having instance per test lifecycle with non static setup and tear downs (#4321) 2023-08-26 22:16:47 +10:00
ike709
8917b29255 Convert Tile.TypeId to an int (#4307)
Co-authored-by: ike709 <ike709@github.com>
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
2023-08-26 22:16:14 +10:00
DrSmugleaf
f0c4d7c5eb Update CI to use setup-dotnet 3.2.0 and checkout 3.6.0 (#4319) 2023-08-25 15:45:19 -07:00
Pieter-Jan Briers
6a00c62d3c Allow content to implement own logic for BUI range checks. (#4301)
They were currently inconsistent with interaction logic in SS14. Please fix and thank.
2023-08-26 09:29:43 +12:00
metalgearsloth
fc3116fca5 Remove ComponentDeleted C# event (#4317)
No one's used it for 12 years probably no reason to obs first.
2023-08-26 09:24:33 +12:00
metalgearsloth
98c1397b3a Remove EntityStarted C# event (#4318) 2023-08-25 19:57:43 +02:00
Leon Friedrich
2464bb6c2f Remove and obsolete ComponentExt functions (#4313) 2023-08-25 23:20:39 +10:00
Leon Friedrich
709142acee Fix prototype manager not being initialized in tests (#4294) 2023-08-24 19:02:02 +02:00
Kevin Zheng
af4e3e5e1c Remove personally-identifiable file paths from client logs (#4267) 2023-08-24 18:56:48 +02:00
Kevin Zheng
d51a18c6ea Fix build with USE_SYSTEM_SQLITE (#4266) 2023-08-24 18:55:54 +02:00
metalgearsloth
d5c3d4c0c9 Add system to CompNetworkGenerator (#4310)
Robust doesn't global using this but content does so any automatic comp states on engine don't work.
2023-08-24 04:26:30 -07:00
metalgearsloth
a4474d8df8 Remove IContainerManager (#4308) 2023-08-24 16:39:35 +10:00
DrSmugleaf
d66f7c7c06 Disable obsoletion and inherited member hidden warnings in serialization source generated code (#4302) 2023-08-24 13:04:32 +10:00
DrSmugleaf
b6879869d6 Add support for long values in CVars (#4299) 2023-08-24 00:57:09 +02:00
Errant
815b8e0c48 removed warning for glibc (#4296) 2023-08-24 00:56:38 +02:00
Arimah Greene
ef4e3baa7f Fix Timer drift (#4300) 2023-08-24 00:54:30 +02:00
Moony
270ddb5a53 Update CODEOWNERS 2023-08-23 16:23:38 -05:00
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
metalgearsloth
80f3aae30c Version: 150.0.0 2023-08-23 18:55:43 +10:00
Leon Friedrich
98b1862433 Add new spawn functions (#4280) 2023-08-23 18:54:55 +10:00
Leon Friedrich
d2311c193f Add AbstractDictionarySerializer (#4276) 2023-08-23 18:53:13 +10:00
metalgearsloth
f05ed96461 Remove FixtureId from fixtures (#4270) 2023-08-23 18:50:48 +10:00
DrSmugleaf
dc23dfaf4d Version: 149.0.1 2023-08-23 00:27:59 -07:00
DmitriyMX
62315f7c2e fix: crash client when window set maxsize (#4291) 2023-08-23 16:59:57 +10:00
DrSmugleaf
b2d121e780 Fix serialization sharing instances when copying data definitions and not assigning null when the source is null (#4295) 2023-08-22 23:59:03 -07:00
409 changed files with 11896 additions and 3345 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,185 @@ END TEMPLATE-->
*None yet*
## 157.1.0
### New features
* UI tooltips now use rich text labels.
## 157.0.0
### Breaking changes
* Unrevert container changes from 155.0.0.
* Added server-client EntityUid separation. A given EntityUid will no longer refer to the same entity on the server & client.
* EntityUid is no longer net-serializable, use NetEntity instead, EntityManager & entity systems have helper methods for converting between the two,
## 156.0.0
### Breaking changes
* Revert container changes from 155.0.0.
## 155.0.0
### Breaking changes
* MapInitEvent now gets raised for components that get added to entities that have already been map-initialized.
### New features
* VirtualWritableDirProvider now supports file renaming/moving.
* Added a new command for toggling the replay UI (`replay_toggleui`).
### Bugfixes
* Fixed formatting of localization file errors.
* Directed event subscriptions will no longer error if the corresponding component is queued for deletion.
## 154.2.0
### New features
* Added support for advertising to multiple hubs simultaneously.
* Added new functions to ContainerSystem that recursively look for a component on a contained entity's parents.
### Bugfixes
* Fix Direction.TurnCw/TurnCcw to South returning Invalid.
## 154.1.0
### New features
* Add MathHelper.Max for TimeSpans.
### Bugfixes
* Make joint initialisation only log under IsFirstTimePredicted on client.
### Other
* Mark the proxy Dirty(component) as obsolete in line with EntityManager (Dirty(EntityUid, Component) should be used in its place).
## 154.0.0
### Breaking changes
* Change ignored prototypes to skip prototypes even if the prototype type is found.
* Moved IPlayerData interface to shared.
### New features
* Added a multiline text submit keybind function.
### Bugfixes
* Fixed multiline edits scrollbar margins.
### Internal
* Added more event sources.
* Made Toolshed types oneOff IoC injections.
## 153.0.0
### Breaking changes
* Removed SharedUserInterfaceComponent component references.
* Removed EntityDeletedMessage.
### Other
* Performance improvements for replay recording.
* Lidgren has been updated to [v0.2.6](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.2.6/RELEASE-NOTES.md).
* Make EntityManager.AddComponent with a component instance set the owner if its default, add system proxy for it.
### Internal
* Added some `EventSource` providers for PVS and replay recording: `Robust.Pvs` and `Robust.ReplayRecording`.
* Added RecursiveMoveBenchmark.
* Removed redundant prototype resolving.
* Removed CollisionWake component removal subscription.
* Removed redundant DebugTools.AssertNotNull(netId) in ClientGameStateManager
## 152.0.0
### Breaking changes
* `Robust.Server.GameObjects.BoundUserInterface.InteractionRangeSqrd` is now a get-only property. Modify `InteractionRange` instead if you want to change it on active UIs.
* Remove IContainerManager.
* Remove and obsolete ComponentExt methods.
* Remove EntityStarted and ComponentDeleted C# events.
* Convert Tile.TypeId to an int. Old maps that were saved with TypeId being an ushort will still be properly deserialized.
### New features
* `BoundUserInterfaceCheckRangeEvent` can be used to implement custom logic for BUI range checks.
* Add support for long values in CVars.
* Allow user code to implement own logic for bound user interface range checks.
### Bugfixes
* Fix timers counting down slower than real time and drifting.
* Add missing System using statement to generated component states.
* Fix build with USE_SYSTEM_SQLITE.
* Fix prototype manager not being initialized in robust server simulation tests.
* Fix not running serialization hooks when copying non-byref data definition fields without a custom type serializer.
### Other
* Remove warning for glibc 2.37.
* Remove personally-identifiable file paths from client logs.
### Internal
* Disable obsoletion and inherited member hidden warnings in serialization source generated code.
* Update CI workflows to use setup-dotnet 3.2.0 and checkout 3.6.0.
* Fix entity spawn tests having instance per test lifecycle with a non static OneTimeTearDown method.
* Add new PVS test to check that there is no issue with entity states referencing other entities that the client is not yet aware of.
## 151.0.0
## 150.0.1
### Bugfixes
* Fix some partial datadefs.
## 150.0.0
### Breaking changes
* Remove the Id field from Fixtures as the Id is already stored on FixturesComponent.
### New features
* Add AbstractDictionarySerializer for abstract classes.
* Add many new spawn functions for entities for common operations.
## 149.0.1
### Bugfixes
* Fix serialization sharing instances when copying data definitions and not assigning null when the source is null.
* Fixed resizing a window to be bigger than its set maxsize crashing the client.
## 149.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

@@ -22,7 +22,8 @@ namespace Robust.Client.Console.Commands
return;
}
var entity = EntityUid.Parse(args[0]);
var netEntity = NetEntity.Parse(args[0]);
var entity = _entityManager.GetEntity(netEntity);
var componentName = args[1];
var component = (Component) _componentFactory.GetComponent(componentName);
@@ -49,7 +50,8 @@ namespace Robust.Client.Console.Commands
return;
}
var entityUid = EntityUid.Parse(args[0]);
var netEntity = NetEntity.Parse(args[0]);
var entityUid = _entityManager.GetEntity(netEntity);
var componentName = args[1];
var registration = _componentFactory.GetRegistration(componentName);

View File

@@ -296,6 +296,7 @@ namespace Robust.Client.Console.Commands
internal sealed class SnapGridGetCell : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "sggcell";
@@ -310,7 +311,7 @@ namespace Robust.Client.Console.Commands
string indices = args[1];
if (!EntityUid.TryParse(args[0], out var gridUid))
if (!NetEntity.TryParse(args[0], out var gridNet))
{
shell.WriteError($"{args[0]} is not a valid entity UID.");
return;
@@ -322,7 +323,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_map.TryGetGrid(gridUid, out var grid))
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
{
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
@@ -430,6 +431,7 @@ namespace Robust.Client.Console.Commands
internal sealed class GridTileCount : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "gridtc";
@@ -442,7 +444,8 @@ namespace Robust.Client.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var gridUid))
if (!NetEntity.TryParse(args[0], out var gridUidNet) ||
!_entManager.TryGetEntity(gridUidNet, out var gridUid))
{
shell.WriteLine($"{args[0]} is not a valid entity UID.");
return;

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

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects;
public sealed partial class ClientEntityManager
{
protected override NetEntity GenerateNetEntity() => new(NextNetworkId++ | NetEntity.ClientEntity);
/// <summary>
/// If the client fails to resolve a NetEntity then during component state handling or the likes we
/// flag that comp state as requiring re-running if that NetEntity comes in.
/// </summary>
/// <returns></returns>
internal readonly Dictionary<NetEntity, List<(Type type, EntityUid Owner)>> PendingNetEntityStates = new();
public override bool IsClientSide(EntityUid uid, MetaDataComponent? metadata = null)
{
// Can't log false because some content code relies on invalid UIDs.
if (!MetaQuery.Resolve(uid, ref metadata, false))
return false;
return metadata.NetEntity.IsClientSide();
}
public override EntityUid EnsureEntity<T>(NetEntity nEntity, EntityUid callerEntity)
{
if (!nEntity.Valid)
{
return EntityUid.Invalid;
}
if (NetEntityLookup.TryGetValue(nEntity, out var entity))
{
return entity;
}
// Flag the callerEntity to have their state potentially re-run later.
var pending = PendingNetEntityStates.GetOrNew(nEntity);
pending.Add((typeof(T), callerEntity));
return entity;
}
public override EntityCoordinates EnsureCoordinates<T>(NetCoordinates netCoordinates, EntityUid callerEntity)
{
var entity = EnsureEntity<T>(netCoordinates.NetEntity, callerEntity);
return new EntityCoordinates(entity, netCoordinates.Position);
}
}

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Replays;
@@ -18,7 +16,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Manager for entities -- controls things like template loading and instantiation
/// </summary>
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
public sealed partial class ClientEntityManager : EntityManager, IClientEntityManagerInternal
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
@@ -27,8 +25,6 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
protected override int NextEntityUid { get; set; } = EntityUid.ClientUid + 1;
public override void Initialize()
{
SetupNetworking();
@@ -39,13 +35,16 @@ namespace Robust.Client.GameObjects
public override void FlushEntities()
{
// Server doesn't network deletions on client shutdown so we need to
// manually clear these out or risk stale data getting used.
PendingNetEntityStates.Clear();
using var _ = _gameTiming.StartStateApplicationArea();
base.FlushEntities();
}
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid uid)
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName)
{
return base.CreateEntity(prototypeName, uid);
return base.CreateEntity(prototypeName);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
@@ -68,7 +67,7 @@ namespace Robust.Client.GameObjects
public override void QueueDeleteEntity(EntityUid uid)
{
if (uid.IsClientSide())
if (IsClientSide(uid))
{
base.QueueDeleteEntity(uid);
return;

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

@@ -128,7 +128,7 @@ namespace Robust.Client.GameObjects
return;
}
if (component.Owner.IsClientSide() || !animatedComp.NetSyncEnabled)
if (IsClientSide(component.Owner) || !animatedComp.NetSyncEnabled)
continue;
var reg = _compFact.GetRegistration(animatedComp);

View File

@@ -81,9 +81,13 @@ public sealed class AudioSystem : SharedAudioSystem
#region Event Handlers
private void PlayAudioEntityHandler(PlayAudioEntityMessage ev)
{
var stream = EntityManager.EntityExists(ev.EntityUid)
? (PlayingStream?) Play(ev.FileName, ev.EntityUid, ev.FallbackCoordinates, ev.AudioParams, false)
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams, false);
var uid = GetEntity(ev.NetEntity);
var coords = GetCoordinates(ev.Coordinates);
var fallback = GetCoordinates(ev.FallbackCoordinates);
var stream = EntityManager.EntityExists(uid)
? (PlayingStream?) Play(ev.FileName, uid, fallback, ev.AudioParams, false)
: (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false);
if (stream != null)
stream.NetIdentifier = ev.Identifier;
@@ -98,7 +102,10 @@ public sealed class AudioSystem : SharedAudioSystem
private void PlayAudioPositionalHandler(PlayAudioPositionalMessage ev)
{
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams, false);
var coords = GetCoordinates(ev.Coordinates);
var fallback = GetCoordinates(ev.FallbackCoordinates);
var stream = (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false);
if (stream != null)
stream.NetIdentifier = ev.Identifier;
}
@@ -383,8 +390,8 @@ public sealed class AudioSystem : SharedAudioSystem
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
{
FileName = filename,
EntityUid = entity,
FallbackCoordinates = fallbackCoordinates ?? default,
NetEntity = GetNetEntity(entity),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates) ?? default,
AudioParams = audioParams ?? AudioParams.Default
});
}
@@ -437,8 +444,8 @@ public sealed class AudioSystem : SharedAudioSystem
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
FallbackCoordinates = fallbackCoordinates,
Coordinates = GetNetCoordinates(coordinates),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
AudioParams = audioParams ?? AudioParams.Default
});
}

View File

@@ -1,3 +1,4 @@
using System;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
@@ -5,13 +6,11 @@ using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Physics.Components;
using Robust.Shared.Serialization;
using static Robust.Shared.Containers.ContainerManagerComponent;
namespace Robust.Client.GameObjects
@@ -23,14 +22,20 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly PointLightSystem _lightSys = default!;
private EntityQuery<PointLightComponent> _pointLightQuery;
private EntityQuery<SpriteComponent> _spriteQuery;
private readonly HashSet<EntityUid> _updateQueue = new();
public readonly Dictionary<EntityUid, IContainer> ExpectedEntities = new();
public readonly Dictionary<NetEntity, BaseContainer> ExpectedEntities = new();
public override void Initialize()
{
base.Initialize();
_pointLightQuery = GetEntityQuery<PointLightComponent>();
_spriteQuery = GetEntityQuery<SpriteComponent>();
EntityManager.EntityInitialized += HandleEntityInitialized;
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
@@ -43,20 +48,18 @@ namespace Robust.Client.GameObjects
base.Shutdown();
}
protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing)
protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing)
{
DebugTools.Assert(ExpectedEntities.TryGetValue(missing, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(missing));
var netEntity = GetNetEntity(missing);
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
}
private void HandleEntityInitialized(EntityUid uid)
{
if (!RemoveExpectedEntity(uid, out var container))
if (!RemoveExpectedEntity(GetNetEntity(uid), out var container))
return;
if (container.Deleted)
return;
container.Insert(uid);
container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid));
}
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
@@ -64,23 +67,24 @@ namespace Robust.Client.GameObjects
if (args.Current is not ContainerManagerComponentState cast)
return;
var metaQuery = GetEntityQuery<MetaDataComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
var xform = TransformQuery.GetComponent(uid);
// Delete now-gone containers.
var toDelete = new ValueList<string>();
foreach (var (id, container) in component.Containers)
{
if (cast.Containers.ContainsKey(id))
{
DebugTools.Assert(cast.Containers[id].ContainerType == container.GetType().Name);
continue;
}
foreach (var entity in container.ContainedEntities.ToArray())
{
container.Remove(entity,
EntityManager,
xformQuery.GetComponent(entity),
metaQuery.GetComponent(entity),
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
force: true,
reparent: false);
@@ -98,26 +102,32 @@ namespace Robust.Client.GameObjects
// Add new containers and update existing contents.
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.Containers.Values)
foreach (var (id, data) in cast.Containers)
{
if (!component.Containers.TryGetValue(id, out var container))
{
container = ContainerFactory(component, containerType, id);
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
container.Init(id, uid, component);
component.Containers.Add(id, container);
}
// sync show flag
container.ShowContents = showEnts;
container.OccludesLight = occludesLight;
DebugTools.Assert(container.ID == id);
container.ShowContents = data.ShowContents;
container.OccludesLight = data.OccludesLight;
// Remove gone entities.
var toRemove = new ValueList<EntityUid>();
DebugTools.Assert(!container.Contains(EntityUid.Invalid));
var stateNetEnts = data.ContainedEntities;
var stateEnts = GetEntityArray(stateNetEnts); // No need to ensure entities.
foreach (var entity in container.ContainedEntities)
{
if (!entityUids.Contains(entity))
{
if (!stateEnts.Contains(entity))
toRemove.Add(entity);
}
}
foreach (var entity in toRemove)
@@ -125,8 +135,8 @@ namespace Robust.Client.GameObjects
container.Remove(
entity,
EntityManager,
xformQuery.GetComponent(entity),
metaQuery.GetComponent(entity),
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
force: true,
reparent: false);
@@ -134,13 +144,11 @@ namespace Robust.Client.GameObjects
}
// Remove entities that were expected, but have been removed from the container.
var removedExpected = new ValueList<EntityUid>();
foreach (var entityUid in container.ExpectedEntities)
var removedExpected = new ValueList<NetEntity>();
foreach (var netEntity in container.ExpectedEntities)
{
if (!entityUids.Contains(entityUid))
{
removedExpected.Add(entityUid);
}
if (!stateNetEnts.Contains(netEntity))
removedExpected.Add(netEntity);
}
foreach (var entityUid in removedExpected)
@@ -149,14 +157,20 @@ namespace Robust.Client.GameObjects
}
// Add new entities.
foreach (var entity in entityUids)
for (var i = 0; i < stateNetEnts.Length; i++)
{
if (!EntityManager.TryGetComponent(entity, out MetaDataComponent? meta))
var entity = stateEnts[i];
var netEnt = stateNetEnts[i];
if (!entity.IsValid())
{
AddExpectedEntity(entity, container);
DebugTools.Assert(netEnt.IsValid());
AddExpectedEntity(netEnt, container);
continue;
}
var meta = MetaData(entity);
DebugTools.Assert(meta.NetEntity == netEnt);
// If an entity is currently in the shadow realm, it means we probably left PVS and are now getting
// back into range. We do not want to directly insert this entity, as IF the container and entity
// transform states did not get sent simultaneously, the entity's transform will be modified by the
@@ -166,18 +180,18 @@ namespace Robust.Client.GameObjects
// containers/players.
if ((meta.Flags & MetaDataFlags.Detached) != 0)
{
AddExpectedEntity(entity, container);
AddExpectedEntity(netEnt, container);
continue;
}
if (container.Contains(entity))
continue;
RemoveExpectedEntity(entity, out _);
RemoveExpectedEntity(netEnt, out _);
container.Insert(entity, EntityManager,
xformQuery.GetComponent(entity),
TransformQuery.GetComponent(entity),
xform,
metaQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
force: true);
DebugTools.Assert(container.Contains(entity));
@@ -198,7 +212,7 @@ namespace Robust.Client.GameObjects
if (message.OldParent != null && message.OldParent.Value.IsValid())
return;
if (!RemoveExpectedEntity(message.Entity, out var container))
if (!RemoveExpectedEntity(GetNetEntity(message.Entity), out var container))
return;
if (xform.ParentUid != container.Owner)
@@ -208,84 +222,69 @@ namespace Robust.Client.GameObjects
return;
}
if (container.Deleted)
return;
container.Insert(message.Entity, EntityManager);
}
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
{
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
#if DEBUG
var uid = GetEntity(netEntity);
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
newContainer.ID = id;
newContainer.Manager = component;
return newContainer;
}
if (TryComp<MetaDataComponent>(uid, out var meta))
{
DebugTools.Assert((meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached,
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");
}
#endif
public void AddExpectedEntity(EntityUid uid, IContainer container)
{
DebugTools.Assert(!TryComp(uid, out MetaDataComponent? meta) ||
(meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached,
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");
if (!ExpectedEntities.TryAdd(uid, container))
if (!ExpectedEntities.TryAdd(netEntity, container))
{
// It is possible that we were expecting this entity in one container, but it has now moved to another
// container, and this entity's state is just being applied before the old container is getting updated.
var oldContainer = ExpectedEntities[uid];
ExpectedEntities[uid] = container;
DebugTools.Assert(oldContainer.ExpectedEntities.Contains(uid),
$"Entity {ToPrettyString(uid)} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}");
oldContainer.ExpectedEntities.Remove(uid);
var oldContainer = ExpectedEntities[netEntity];
ExpectedEntities[netEntity] = container;
DebugTools.Assert(oldContainer.ExpectedEntities.Contains(netEntity),
$"Entity {netEntity} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}");
oldContainer.ExpectedEntities.Remove(netEntity);
}
DebugTools.Assert(!container.ExpectedEntities.Contains(uid),
$"Contained entity {ToPrettyString(uid)} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Add(uid);
DebugTools.Assert(!container.ExpectedEntities.Contains(netEntity),
$"Contained entity {netEntity} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Add(netEntity);
}
public bool RemoveExpectedEntity(EntityUid uid, [NotNullWhen(true)] out IContainer? container)
public bool RemoveExpectedEntity(NetEntity netEntity, [NotNullWhen(true)] out BaseContainer? container)
{
if (!ExpectedEntities.Remove(uid, out container))
if (!ExpectedEntities.Remove(netEntity, out container))
return false;
DebugTools.Assert(container.ExpectedEntities.Contains(uid),
$"While removing expected contained entity {ToPrettyString(uid)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Remove(uid);
DebugTools.Assert(container.ExpectedEntities.Contains(netEntity),
$"While removing expected contained entity {ToPrettyString(netEntity)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Remove(netEntity);
return true;
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
foreach (var toUpdate in _updateQueue)
{
if (Deleted(toUpdate))
continue;
UpdateEntityRecursively(toUpdate, xformQuery, pointQuery, spriteQuery);
UpdateEntityRecursively(toUpdate);
}
_updateQueue.Clear();
}
private void UpdateEntityRecursively(
EntityUid entity,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery)
private void UpdateEntityRecursively(EntityUid entity)
{
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
// occluded but this probably isn't a big perf issue.
var xform = xformQuery.GetComponent(entity);
var xform = TransformQuery.GetComponent(entity);
var parent = xform.ParentUid;
var child = entity;
var spriteOccluded = false;
@@ -293,7 +292,7 @@ namespace Robust.Client.GameObjects
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
{
var parentXform = xformQuery.GetComponent(parent);
var parentXform = TransformQuery.GetComponent(parent);
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
{
spriteOccluded = spriteOccluded || !container.ShowContents;
@@ -308,24 +307,21 @@ namespace Robust.Client.GameObjects
// This is the CBT bit.
// The issue is we need to go through the children and re-check whether they are or are not contained.
// if they are contained then the occlusion values may need updating for all those children
UpdateEntity(entity, xform, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
UpdateEntity(entity, xform, spriteOccluded, lightOccluded);
}
private void UpdateEntity(
EntityUid entity,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery,
bool spriteOccluded,
bool lightOccluded)
{
if (spriteQuery.TryGetComponent(entity, out var sprite))
if (_spriteQuery.TryGetComponent(entity, out var sprite))
{
sprite.ContainerOccluded = spriteOccluded;
}
if (pointQuery.TryGetComponent(entity, out var light))
if (_pointLightQuery.TryGetComponent(entity, out var light))
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
var childEnumerator = xform.ChildEnumerator;
@@ -346,14 +342,14 @@ namespace Robust.Client.GameObjects
childLightOccluded = childLightOccluded || container.OccludesLight;
}
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded);
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded);
}
}
else
{
while (childEnumerator.MoveNext(out var child))
{
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded);
}
}
}

View File

@@ -81,7 +81,7 @@ namespace Robust.Client.GameObjects
while (chunkEnumerator.MoveNext(out var chunk))
{
foreach (var fixture in chunk.Fixtures)
foreach (var fixture in chunk.Fixtures.Values)
{
var poly = (PolygonShape) fixture.Shape;

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
/// <param name="message">Arguments for this event.</param>
/// <param name="replay">if true, current cmd state will not be checked or updated - use this for "replaying" an
/// old input that was saved or buffered until further processing could be done</param>
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, IFullInputCmdMessage message, bool replay = false)
{
#if DEBUG
@@ -78,14 +78,27 @@ namespace Robust.Client.GameObjects
continue;
// local handlers can block sending over the network.
if (handler.HandleCmdMessage(session, message))
if (handler.HandleCmdMessage(EntityManager, session, message))
{
return true;
}
}
// send it off to the server
DispatchInputCommand(message);
var clientMsg = (ClientFullInputCmdMessage)message;
var fullMsg = new FullInputCmdMessage(
clientMsg.Tick,
clientMsg.SubTick,
(int)clientMsg.InputSequence,
clientMsg.InputFunctionId,
clientMsg.State,
GetNetCoordinates(clientMsg.Coordinates),
clientMsg.ScreenCoordinates)
{
Uid = GetNetEntity(clientMsg.Uid)
};
DispatchInputCommand(clientMsg, fullMsg);
return false;
}
@@ -93,7 +106,7 @@ namespace Robust.Client.GameObjects
/// Handle a predicted input command.
/// </summary>
/// <param name="inputCmd">Input command to handle as predicted.</param>
public void PredictInputCommand(FullInputCmdMessage inputCmd)
public void PredictInputCommand(IFullInputCmdMessage inputCmd)
{
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
@@ -103,15 +116,16 @@ namespace Robust.Client.GameObjects
var session = _playerManager.LocalPlayer!.Session;
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
{
if (handler.HandleCmdMessage(session, inputCmd)) break;
if (handler.HandleCmdMessage(EntityManager, session, inputCmd))
break;
}
Predicted = false;
}
private void DispatchInputCommand(FullInputCmdMessage message)
private void DispatchInputCommand(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message)
{
_stateManager.InputCommandDispatched(message);
_stateManager.InputCommandDispatched(clientMsg, message);
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence);
}
@@ -152,7 +166,7 @@ namespace Robust.Client.GameObjects
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
HandleInputCommand(localPlayer.Session, keyFunction, message);
}
@@ -206,8 +220,10 @@ namespace Robust.Client.GameObjects
SetEntityContextActive(_inputManager, controlled);
}
void IPostInjectInit.PostInject()
protected override void PostInject()
{
base.PostInject();
_sawmillInputContext = _logManager.GetSawmill("input.context");
}
}

View File

@@ -43,7 +43,8 @@ namespace Robust.Client.GameObjects
private void MessageReceived(BoundUIWrapMessage ev)
{
var uid = ev.Entity;
var uid = GetEntity(ev.Entity);
if (!TryComp<ClientUserInterfaceComponent>(uid, out var cmp))
return;
@@ -53,7 +54,7 @@ namespace Robust.Client.GameObjects
if(_playerManager.LocalPlayer != null)
message.Session = _playerManager.LocalPlayer.Session;
message.Entity = uid;
message.Entity = GetNetEntity(uid);
message.UiKey = uiKey;
// Raise as object so the correct type is used.
@@ -125,7 +126,7 @@ namespace Robust.Client.GameObjects
internal void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage msg)
{
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, msg, bui.UiKey));
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), msg, bui.UiKey));
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Robust.Client.GameObjects
{
// These methods are used by the Game State Manager.
EntityUid CreateEntity(string? prototypeName, EntityUid uid = default);
EntityUid CreateEntity(string? prototypeName);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);

View File

@@ -15,7 +15,7 @@ public sealed class ClientDirtySystem : EntitySystem
{
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IComponentFactory _compFact = default!;
// Entities that have removed networked components
// could pool the ushort sets, but predicted component changes are rare... soo...
internal readonly Dictionary<EntityUid, HashSet<ushort>> RemovedComponents = new();
@@ -40,11 +40,11 @@ public sealed class ClientDirtySystem : EntitySystem
private void OnTerminate(ref EntityTerminatingEvent ev)
{
if (!_timing.InPrediction || ev.Entity.IsClientSide())
if (!_timing.InPrediction || IsClientSide(ev.Entity))
return;
// Client-side entity deletion is not supported and will cause errors.
Logger.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
}
private void OnCompRemoved(RemovedComponentEventArgs args)
@@ -52,8 +52,9 @@ public sealed class ClientDirtySystem : EntitySystem
if (args.Terminating)
return;
var uid = args.BaseArgs.Owner;
var comp = args.BaseArgs.Component;
if (!_timing.InPrediction || comp.Owner.IsClientSide() || !comp.NetSyncEnabled)
if (!_timing.InPrediction || !comp.NetSyncEnabled || IsClientSide(uid))
return;
// Was this component added during prediction? If yes, then there is no need to re-add it when resetting.
@@ -62,7 +63,7 @@ public sealed class ClientDirtySystem : EntitySystem
var netId = _compFact.GetRegistration(comp).NetID;
if (netId != null)
RemovedComponents.GetOrNew(comp.Owner).Add(netId.Value);
RemovedComponents.GetOrNew(uid).Add(netId.Value);
}
public void Reset()
@@ -73,7 +74,7 @@ public sealed class ClientDirtySystem : EntitySystem
private void OnEntityDirty(EntityUid e)
{
if (_timing.InPrediction && !e.IsClientSide())
if (_timing.InPrediction && !IsClientSide(e))
DirtyEntities.Add(e);
}
}

View File

@@ -47,8 +47,10 @@ namespace Robust.Client.GameStates
= new();
// Game state dictionaries that get used every tick.
private readonly Dictionary<EntityUid, (bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState?nextState)> _toApply = new();
private readonly Dictionary<EntityUid, EntityState> _toCreate = new();
private readonly Dictionary<EntityUid, (bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
private readonly Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)> _compStateWork = new();
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
private uint _metaCompNetId;
@@ -157,7 +159,7 @@ namespace Robust.Client.GameStates
}
}
public void InputCommandDispatched(FullInputCmdMessage message)
public void InputCommandDispatched(ClientFullInputCmdMessage clientMessage, FullInputCmdMessage message)
{
if (!IsPredictionEnabled)
{
@@ -201,7 +203,7 @@ namespace Robust.Client.GameStates
public void UpdateFullRep(GameState state, bool cloneDelta = false)
=> _processor.UpdateFullRep(state, cloneDelta);
public Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
=> _processor.GetFullRep();
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
@@ -210,7 +212,7 @@ namespace Robust.Client.GameStates
PvsLeave?.Invoke(message);
}
public void QueuePvsDetach(List<EntityUid> entities, GameTick tick)
public void QueuePvsDetach(List<NetEntity> entities, GameTick tick)
{
_processor.AddLeavePvsMessage(entities, tick);
if (_replayRecording.IsRecording)
@@ -298,7 +300,7 @@ namespace Robust.Client.GameStates
_processor.UpdateFullRep(curState);
}
IEnumerable<EntityUid> createdEntities;
IEnumerable<NetEntity> createdEntities;
using (_prof.Group("ApplyGameState"))
{
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
@@ -323,7 +325,7 @@ namespace Robust.Client.GameStates
catch (MissingMetadataException e)
{
// Something has gone wrong. Probably a missing meta-data component. Perhaps a full server state will fix it.
RequestFullState(e.Uid);
RequestFullState(e.NetEntity);
throw;
}
#endif
@@ -392,10 +394,10 @@ namespace Robust.Client.GameStates
}
}
public void RequestFullState(EntityUid? missingEntity = null)
public void RequestFullState(NetEntity? missingEntity = null)
{
_sawmill.Info("Requesting full server state");
_network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? EntityUid.Invalid });
_network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? NetEntity.Invalid });
_processor.RequestFullState();
}
@@ -472,7 +474,7 @@ namespace Robust.Client.GameStates
var countReset = 0;
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
var query = _entityManager.GetEntityQuery<MetaDataComponent>();
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
RemQueue<Component> toRemove = new();
// This is terrible, and I hate it.
@@ -486,7 +488,8 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($"Entity {entity} was made dirty.");
if (!_processor.TryGetLastServerStates(entity, out var last))
if (!metaQuery.TryGetComponent(entity, out var meta) ||
!_processor.TryGetLastServerStates(meta.NetEntity, out var last))
{
// Entity was probably deleted on the server so do nothing.
continue;
@@ -500,7 +503,6 @@ namespace Robust.Client.GameStates
foreach (var (netId, comp) in netComps.Value)
{
DebugTools.AssertNotNull(netId);
if (!comp.NetSyncEnabled)
continue;
@@ -565,7 +567,6 @@ namespace Robust.Client.GameStates
}
}
var meta = query.GetComponent(entity);
DebugTools.Assert(meta.EntityLastModifiedTick > _timing.LastRealTick);
meta.EntityLastModifiedTick = _timing.LastRealTick;
}
@@ -589,15 +590,16 @@ namespace Robust.Client.GameStates
/// initial server state for any newly created entity. It does this by simply using the standard <see
/// cref="IEntityManager.GetComponentState(IEventBus, IComponent)"/>.
/// </remarks>
private void MergeImplicitData(IEnumerable<EntityUid> createdEntities)
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
{
var outputData = new Dictionary<EntityUid, Dictionary<ushort, ComponentState>>();
var outputData = new Dictionary<NetEntity, Dictionary<ushort, ComponentState>>();
var bus = _entityManager.EventBus;
foreach (var createdEntity in createdEntities)
foreach (var netEntity in createdEntities)
{
var createdEntity = _entityManager.GetEntity(netEntity);
var compData = new Dictionary<ushort, ComponentState>();
outputData.Add(createdEntity, compData);
outputData.Add(netEntity, compData);
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
{
@@ -618,7 +620,7 @@ namespace Robust.Client.GameStates
_network.ClientSendMessage(new MsgStateAck() { Sequence = sequence });
}
public IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
public IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState)
{
using var _ = _timing.StartStateApplicationArea();
@@ -638,7 +640,7 @@ namespace Robust.Client.GameStates
_config.TickProcessMessages();
}
(IEnumerable<EntityUid> Created, List<EntityUid> Detached) output;
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
using (_prof.Group("Entity"))
{
output = ApplyEntityStates(curState, nextState);
@@ -657,7 +659,7 @@ namespace Robust.Client.GameStates
return output.Created;
}
private (IEnumerable<EntityUid> Created, List<EntityUid> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -666,6 +668,7 @@ namespace Robust.Client.GameStates
var enteringPvs = 0;
_toApply.Clear();
_toCreate.Clear();
_pendingReapplyNetStates.Clear();
var curSpan = curState.EntityStates.Span;
// Create new entities
@@ -676,21 +679,40 @@ namespace Robust.Client.GameStates
foreach (var es in curSpan)
{
if (metas.HasComponent(es.Uid))
if (_entityManager.TryGetEntity(es.NetEntity, out var nUid))
{
DebugTools.Assert(_entityManager.EntityExists(nUid));
continue;
}
count++;
var uid = es.Uid;
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
if (metaState == null)
throw new MissingMetadataException(uid);
throw new MissingMetadataException(es.NetEntity);
_entities.CreateEntity(metaState.PrototypeId, uid);
_toCreate.Add(uid, es);
var uid = _entities.CreateEntity(metaState.PrototypeId);
_toCreate.Add(es.NetEntity, es);
_toApply.Add(uid, (false, GameTick.Zero, es, null));
var newMeta = metas.GetComponent(uid);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entityManager.ClearNetEntity(newMeta.NetEntity);
_entityManager.SetNetEntity(uid, es.NetEntity, newMeta);
newMeta.LastStateApplied = curState.ToSequence;
// Check if there's any component states awaiting this entity.
if (_entityManager.PendingNetEntityStates.TryGetValue(es.NetEntity, out var value))
{
foreach (var (type, owner) in value)
{
var pending = _pendingReapplyNetStates.GetOrNew(owner);
pending.Add(type);
}
}
}
_prof.WriteValue("Count", ProfData.Int32(count));
@@ -698,7 +720,9 @@ namespace Robust.Client.GameStates
foreach (var es in curSpan)
{
if (!metas.TryGetComponent(es.Uid, out var meta) || _toCreate.ContainsKey(es.Uid))
var uid = _entityManager.GetEntity(es.NetEntity);
if (!metas.TryGetComponent(uid, out var meta) || _toCreate.ContainsKey(es.NetEntity))
{
continue;
}
@@ -715,7 +739,7 @@ namespace Robust.Client.GameStates
continue;
}
_toApply.Add(es.Uid, (isEnteringPvs, meta.LastStateApplied, es, null));
_toApply.Add(uid, (isEnteringPvs, meta.LastStateApplied, es, null));
meta.LastStateApplied = curState.ToSequence;
}
@@ -729,22 +753,31 @@ namespace Robust.Client.GameStates
{
foreach (var es in nextState.EntityStates.Span)
{
var uid = es.Uid;
if (!metas.TryGetComponent(uid, out var meta))
if (!_entityManager.TryGetEntity(es.NetEntity, out var uid))
continue;
DebugTools.Assert(metas.HasComponent(uid));
// Does the next state actually have any future information about this entity that could be used for interpolation?
if (es.EntityLastModified != nextState.ToSequence)
continue;
if (_toApply.TryGetValue(uid, out var state))
_toApply[uid] = (state.EnteringPvs, state.LastApplied, state.curState, es);
if (_toApply.TryGetValue(uid.Value, out var state))
_toApply[uid.Value] = (state.EnteringPvs, state.LastApplied, state.curState, es);
else
_toApply[uid] = (false, GameTick.Zero, null, es);
_toApply[uid.Value] = (false, GameTick.Zero, null, es);
}
}
// Check pending states and see if we need to force any entities to re-run component states.
foreach (var uid in _pendingReapplyNetStates.Keys)
{
if (_toApply.ContainsKey(uid))
continue;
_toApply[uid] = (false, GameTick.Zero, null, null);
}
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
// Apply entity states.
@@ -767,6 +800,7 @@ namespace Robust.Client.GameStates
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
queuedBroadphaseUpdates.Add((entity, xform));
}
_prof.WriteValue("Count", ProfData.Int32(_toApply.Count));
}
@@ -830,37 +864,37 @@ namespace Robust.Client.GameStates
// Construct hashset for set.Contains() checks.
var entityStates = state.EntityStates.Span;
var stateEnts = new HashSet<EntityUid>(entityStates.Length);
var stateEnts = new HashSet<NetEntity>();
foreach (var entState in entityStates)
{
stateEnts.Add(entState.Uid);
stateEnts.Add(entState.NetEntity);
}
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
var currentEnts = _entities.GetEntities();
var toDelete = new List<EntityUid>(Math.Max(64, _entities.EntityCount - stateEnts.Count));
foreach (var ent in currentEnts)
// Client side entities won't need the transform, but that should always be a tiny minority of entities
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
while (metaQuery.MoveNext(out var ent, out var metadata, out var xform))
{
if (ent.IsClientSide())
var netEnt = metadata.NetEntity;
if (metadata.NetEntity.IsClientSide())
{
if (deleteClientEntities)
toDelete.Add(ent);
continue;
}
if (stateEnts.Contains(ent) && metas.TryGetComponent(ent, out var meta))
if (stateEnts.Contains(netEnt))
{
if (resetAllEntities || meta.LastStateApplied > state.ToSequence)
meta.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
if (resetAllEntities || metadata.LastStateApplied > state.ToSequence)
metadata.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
continue;
}
if (!xforms.TryGetComponent(ent, out var xform))
continue;
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
xformSys.DetachParentToNull(ent, xform);
@@ -873,7 +907,7 @@ namespace Robust.Client.GameStates
if (deleteClientChildren
&& !deleteClientEntities // don't add duplicates
&& child.Value.IsClientSide())
&& _entities.IsClientSide(child.Value))
{
toDelete.Add(child.Value);
}
@@ -888,7 +922,7 @@ namespace Robust.Client.GameStates
}
private void ProcessDeletions(
ReadOnlySpan<EntityUid> delSpan,
ReadOnlySpan<NetEntity> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
@@ -905,13 +939,19 @@ namespace Robust.Client.GameStates
using var _ = _prof.Group("Deletion");
foreach (var id in delSpan)
foreach (var netEntity in delSpan)
{
// Don't worry about this for later.
_entityManager.PendingNetEntityStates.Remove(netEntity);
if (!_entityManager.TryGetEntity(netEntity, out var id))
continue;
if (!xforms.TryGetComponent(id, out var xform))
continue; // Already deleted? or never sent to us?
// First, a single recursive map change
xformSys.DetachParentToNull(id, xform);
xformSys.DetachParentToNull(id.Value, xform);
// Then detach all children.
var childEnumerator = xform.ChildEnumerator;
@@ -921,12 +961,12 @@ namespace Robust.Client.GameStates
}
// Finally, delete the entity.
_entities.DeleteEntity(id);
_entities.DeleteEntity(id.Value);
}
_prof.WriteValue("Count", ProfData.Int32(delSpan.Length));
}
public void DetachImmediate(List<EntityUid> entities)
public void DetachImmediate(List<NetEntity> entities)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -936,7 +976,7 @@ namespace Robust.Client.GameStates
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
}
private List<EntityUid> ProcessPvsDeparture(
private List<NetEntity> ProcessPvsDeparture(
GameTick toTick,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
@@ -945,7 +985,7 @@ namespace Robust.Client.GameStates
EntityLookupSystem lookupSys)
{
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
var detached = new List<EntityUid>();
var detached = new List<NetEntity>();
if (toDetach.Count == 0)
return detached;
@@ -967,16 +1007,18 @@ namespace Robust.Client.GameStates
private void Detach(GameTick maxTick,
GameTick? lastStateApplied,
List<EntityUid> entities,
List<NetEntity> entities,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
SharedTransformSystem xformSys,
ContainerSystem containerSys,
EntityLookupSystem lookupSys,
List<EntityUid>? detached = null)
List<NetEntity>? detached = null)
{
foreach (var ent in entities)
foreach (var netEntity in entities)
{
var ent = _entityManager.GetEntity(netEntity);
if (!metas.TryGetComponent(ent, out var meta))
continue;
@@ -1001,7 +1043,7 @@ namespace Robust.Client.GameStates
// In some cursed scenarios an entity inside of a container can leave PVS without the container itself leaving PVS.
// In those situations, we need to add the entity back to the list of expected entities after detaching.
IContainer? container = null;
BaseContainer? container = null;
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
@@ -1015,35 +1057,38 @@ namespace Robust.Client.GameStates
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
containerSys.AddExpectedEntity(ent, container);
containerSys.AddExpectedEntity(netEntity, container);
}
detached?.Add(ent);
detached?.Add(netEntity);
}
}
private void InitializeAndStart(Dictionary<EntityUid, EntityState> toCreate)
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
{
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
#if EXCEPTION_TOLERANCE
HashSet<EntityUid> brokenEnts = new HashSet<EntityUid>();
var brokenEnts = new List<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
foreach (var entity in toCreate.Keys)
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.InitializeEntity(entity);
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: ent={_entityManager.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(entity);
toCreate.Remove(netEntity);
}
#endif
}
@@ -1051,8 +1096,9 @@ namespace Robust.Client.GameStates
using (_prof.Group("Start Entity"))
{
foreach (var entity in toCreate.Keys)
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
@@ -1065,7 +1111,7 @@ namespace Robust.Client.GameStates
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(entity);
toCreate.Remove(netEntity);
}
#endif
}
@@ -1082,8 +1128,9 @@ namespace Robust.Client.GameStates
private void HandleEntityState(EntityUid uid, IEventBus bus, EntityState? curState,
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
{
var size = (curState?.ComponentChanges.Span.Length ?? 0) + (nextState?.ComponentChanges.Span.Length ?? 0);
var compStateWork = new Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)>(size);
_compStateWork.Clear();
var meta = _entityManager.GetComponent<MetaDataComponent>(uid);
var netEntity = meta.NetEntity;
// First remove any deleted components
if (curState?.NetComponents != null)
@@ -1108,7 +1155,7 @@ namespace Robust.Client.GameStates
//
// as to why we need to reset: because in the process of detaching to null-space, we will have dirtied
// the entity. most notably, all entities will have been ejected from their containers.
foreach (var (id, state) in _processor.GetLastServerStates(uid))
foreach (var (id, state) in _processor.GetLastServerStates(netEntity))
{
if (!_entityManager.TryGetComponent(uid, id, out var comp))
{
@@ -1118,7 +1165,7 @@ namespace Robust.Client.GameStates
_entityManager.AddComponent(uid, newComp, true);
}
compStateWork[id] = (comp, state, null);
_compStateWork[id] = (comp, state, null);
}
}
else if (curState != null)
@@ -1135,7 +1182,7 @@ namespace Robust.Client.GameStates
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
continue;
compStateWork[compChange.NetID] = (comp, compChange.State, null);
_compStateWork[compChange.NetID] = (comp, compChange.State, null);
}
}
@@ -1153,14 +1200,38 @@ namespace Robust.Client.GameStates
continue;
}
if (compStateWork.TryGetValue(compState.NetID, out var state))
compStateWork[compState.NetID] = (comp, state.curState, compState.State);
if (_compStateWork.TryGetValue(compState.NetID, out var state))
_compStateWork[compState.NetID] = (comp, state.curState, compState.State);
else
compStateWork[compState.NetID] = (comp, null, compState.State);
_compStateWork[compState.NetID] = (comp, null, compState.State);
}
}
foreach (var (comp, cur, next) in compStateWork.Values)
// If we have a NetEntity we reference come in then apply their state.
if (_pendingReapplyNetStates.TryGetValue(uid, out var reapplyTypes))
{
var lastState = _processor.GetLastServerStates(netEntity);
foreach (var type in reapplyTypes)
{
var compRef = _compFactory.GetRegistration(type);
var netId = compRef.NetID;
if (netId == null)
continue;
if (_compStateWork.ContainsKey(netId.Value) ||
!_entityManager.TryGetComponent(uid, type, out var comp) ||
!lastState.TryGetValue(netId.Value, out var lastCompState))
{
continue;
}
_compStateWork[netId.Value] = (comp, lastCompState, null);
}
}
foreach (var (comp, cur, next) in _compStateWork.Values)
{
try
{
@@ -1181,6 +1252,7 @@ namespace Robust.Client.GameStates
}
#region Debug Commands
private bool TryParseUid(IConsoleShell shell, string[] args, out EntityUid uid, [NotNullWhen(true)] out MetaDataComponent? meta)
{
if (args.Length != 1)
@@ -1239,7 +1311,7 @@ namespace Robust.Client.GameStates
var xform = _entities.GetComponent<TransformComponent>(uid);
if (xform.ParentUid.IsValid())
{
IContainer? container = null;
BaseContainer? container = null;
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
@@ -1250,7 +1322,7 @@ namespace Robust.Client.GameStates
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
if (container != null)
containerSys.AddExpectedEntity(uid, container);
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);
}
}
@@ -1267,18 +1339,21 @@ namespace Robust.Client.GameStates
// If this is not a client-side entity, it also needs to be removed from the full-server state dictionary to
// avoid errors. This has to be done recursively for all children.
void _recursiveRemoveState(TransformComponent xform, EntityQuery<TransformComponent> query)
void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery)
{
_processor._lastStateFullRep.Remove(xform.Owner);
_processor._lastStateFullRep.Remove(netEntity);
foreach (var child in xform.ChildEntities)
{
if (query.TryGetComponent(child, out var childXform))
_recursiveRemoveState(childXform, query);
if (xformQuery.TryGetComponent(child, out var childXform) &&
metaQuery.TryGetComponent(child, out var childMeta))
{
_recursiveRemoveState(childMeta.NetEntity, childXform, metaQuery, xformQuery);
}
}
}
if (!uid.IsClientSide() && _entities.TryGetComponent(uid, out TransformComponent? xform))
_recursiveRemoveState(xform, _entities.GetEntityQuery<TransformComponent>());
if (!_entities.IsClientSide(uid) && _entities.TryGetComponent(uid, out TransformComponent? xform))
_recursiveRemoveState(meta.NetEntity, xform, _entities.GetEntityQuery<MetaDataComponent>(), _entities.GetEntityQuery<TransformComponent>());
// Set ApplyingState to true to avoid logging errors about predicting the deletion of networked entities.
using (_timing.StartStateApplicationArea())
@@ -1312,7 +1387,7 @@ namespace Robust.Client.GameStates
meta.Flags &= ~MetaDataFlags.Detached;
if (!_processor.TryGetLastServerStates(uid, out var lastState))
if (!_processor.TryGetLastServerStates(meta.NetEntity, out var lastState))
return;
foreach (var (id, state) in lastState)
@@ -1353,9 +1428,9 @@ namespace Robust.Client.GameStates
public sealed class GameStateAppliedArgs : EventArgs
{
public GameState AppliedState { get; }
public readonly List<EntityUid> Detached;
public readonly List<NetEntity> Detached;
public GameStateAppliedArgs(GameState appliedState, List<EntityUid> detached)
public GameStateAppliedArgs(GameState appliedState, List<NetEntity> detached)
{
AppliedState = appliedState;
Detached = detached;
@@ -1364,12 +1439,12 @@ namespace Robust.Client.GameStates
public sealed class MissingMetadataException : Exception
{
public readonly EntityUid Uid;
public readonly NetEntity NetEntity;
public MissingMetadataException(EntityUid uid)
: base($"Server state is missing the metadata component for a new entity: {uid}.")
public MissingMetadataException(NetEntity netEntity)
: base($"Server state is missing the metadata component for a new entity: {netEntity}.")
{
Uid = uid;
NetEntity = netEntity;
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.GameStates
private readonly List<GameState> _stateBuffer = new();
private readonly Dictionary<GameTick, List<EntityUid>> _pvsDetachMessages = new();
private readonly Dictionary<GameTick, List<NetEntity>> _pvsDetachMessages = new();
private ISawmill _logger = default!;
private ISawmill _stateLogger = default!;
@@ -44,7 +44,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
/// </summary>
internal readonly Dictionary<EntityUid, Dictionary<ushort, ComponentState>> _lastStateFullRep
internal readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _lastStateFullRep
= new();
/// <inheritdoc />
@@ -178,10 +178,10 @@ namespace Robust.Client.GameStates
foreach (var entityState in state.EntityStates.Span)
{
if (!_lastStateFullRep.TryGetValue(entityState.Uid, out var compData))
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
{
compData = new Dictionary<ushort, ComponentState>();
_lastStateFullRep.Add(entityState.Uid, compData);
_lastStateFullRep.Add(entityState.NetEntity, compData);
}
foreach (var change in entityState.ComponentChanges.Span)
@@ -263,7 +263,7 @@ namespace Robust.Client.GameStates
return false;
}
internal void AddLeavePvsMessage(List<EntityUid> entities, GameTick tick)
internal void AddLeavePvsMessage(List<NetEntity> entities, GameTick tick)
{
// Late message may still need to be processed,
DebugTools.Assert(entities.Count > 0);
@@ -272,9 +272,9 @@ namespace Robust.Client.GameStates
public void ClearDetachQueue() => _pvsDetachMessages.Clear();
public List<(GameTick Tick, List<EntityUid> Entities)> GetEntitiesToDetach(GameTick toTick, int budget)
public List<(GameTick Tick, List<NetEntity> Entities)> GetEntitiesToDetach(GameTick toTick, int budget)
{
var result = new List<(GameTick Tick, List<EntityUid> Entities)>();
var result = new List<(GameTick Tick, List<NetEntity> Entities)>();
foreach (var (tick, entities) in _pvsDetachMessages)
{
if (tick > toTick)
@@ -353,11 +353,11 @@ namespace Robust.Client.GameStates
LastFullStateRequested = _timing.LastRealTick;
}
public void MergeImplicitData(Dictionary<EntityUid, Dictionary<ushort, ComponentState>> implicitData)
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> implicitData)
{
foreach (var (uid, implicitEntState) in implicitData)
foreach (var (netEntity, implicitEntState) in implicitData)
{
var fullRep = _lastStateFullRep[uid];
var fullRep = _lastStateFullRep[netEntity];
foreach (var (netId, implicitCompState) in implicitEntState)
{
@@ -374,7 +374,7 @@ namespace Robust.Client.GameStates
// state from the entity prototype.
if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState)
{
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {uid}");
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
continue;
}
@@ -385,17 +385,17 @@ namespace Robust.Client.GameStates
}
}
public Dictionary<ushort, ComponentState> GetLastServerStates(EntityUid entity)
public Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity netEntity)
{
return _lastStateFullRep[entity];
return _lastStateFullRep[netEntity];
}
public Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
{
return _lastStateFullRep;
}
public bool TryGetLastServerStates(EntityUid entity,
public bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary)
{
return _lastStateFullRep.TryGetValue(entity, out dictionary);

View File

@@ -75,7 +75,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Applies a given set of game states.
/// </summary>
IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState);
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
/// <summary>
/// Resets any entities that have changed while predicting future ticks.
@@ -86,12 +86,12 @@ namespace Robust.Client.GameStates
/// An input command has been dispatched.
/// </summary>
/// <param name="message">Message being dispatched.</param>
void InputCommandDispatched(FullInputCmdMessage message);
void InputCommandDispatched(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message);
/// <summary>
/// Requests a full state from the server. This should override even implicit entity data.
/// </summary>
void RequestFullState(EntityUid? missingEntity = null);
void RequestFullState(NetEntity? missingEntity = null);
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
@@ -105,7 +105,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Returns the full collection of cached game states that are used to reset predicted entities.
/// </summary>
Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep();
Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep();
/// <summary>
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state
@@ -144,12 +144,12 @@ namespace Robust.Client.GameStates
/// Queue a collection of entities that are to be detached to null-space & marked as PVS-detached.
/// This store and modify the list given to it.
/// </summary>
void QueuePvsDetach(List<EntityUid> entities, GameTick tick);
void QueuePvsDetach(List<NetEntity> entities, GameTick tick);
/// <summary>
/// Immediately detach several entities.
/// </summary>
void DetachImmediate(List<EntityUid> entities);
void DetachImmediate(List<NetEntity> entities);
/// <summary>
/// Clears the PVS detach queue.

View File

@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
/// The data to merge.
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
/// </param>
void MergeImplicitData(Dictionary<EntityUid, Dictionary<ushort, ComponentState>> data);
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> data);
/// <summary>
/// Get the last state data from the server for an entity.
/// </summary>
/// <returns>Dictionary (net ID -> ComponentState)</returns>
Dictionary<ushort, ComponentState> GetLastServerStates(EntityUid entity);
Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity entity);
/// <summary>
/// Calculate the number of applicable states in the game state buffer from a given tick.
@@ -98,7 +98,7 @@ namespace Robust.Client.GameStates
/// <param name="fromTick">The tick to calculate from.</param>
int CalculateBufferSize(GameTick fromTick);
bool TryGetLastServerStates(EntityUid entity,
bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary);
}
}

View File

@@ -35,7 +35,7 @@ namespace Robust.Client.GameStates
private readonly Font _font;
private readonly int _lineHeight;
private readonly Dictionary<EntityUid, NetEntData> _netEnts = new();
private readonly Dictionary<NetEntity, NetEntData> _netEnts = new();
public NetEntityOverlay()
{
@@ -77,12 +77,12 @@ namespace Robust.Client.GameStates
foreach (var entityState in gameState.EntityStates.Span)
{
if (!_netEnts.TryGetValue(entityState.Uid, out var netEnt))
if (!_netEnts.TryGetValue(entityState.NetEntity, out var netEnt))
{
if (_netEnts.Count >= _maxEnts)
continue;
_netEnts[entityState.Uid] = netEnt = new();
_netEnts[entityState.NetEntity] = netEnt = new();
}
if (!netEnt.InPVS && netEnt.LastUpdate < gameState.ToSequence)
@@ -119,11 +119,13 @@ namespace Robust.Client.GameStates
var screenHandle = args.ScreenHandle;
int i = 0;
foreach (var (uid, netEnt) in _netEnts)
foreach (var (nent, netEnt) in _netEnts)
{
var uid = _entityManager.GetEntity(nent);
if (!_entityManager.EntityExists(uid))
{
_netEnts.Remove(uid);
_netEnts.Remove(nent);
continue;
}

View File

@@ -26,6 +26,7 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
private const int HistorySize = 60 * 5; // number of ticks to keep in history.
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
@@ -73,7 +74,7 @@ namespace Robust.Client.GameStates
_history.Add((toSeq, sz, lag, buffer));
// not watching an ent
if(!WatchEntId.IsValid() || WatchEntId.IsClientSide())
if(!WatchEntId.IsValid() || _entManager.IsClientSide(WatchEntId))
return;
string? entStateString = null;
@@ -86,7 +87,9 @@ namespace Robust.Client.GameStates
var sb = new StringBuilder();
foreach (var entState in entStates.Span)
{
if (entState.Uid != WatchEntId)
var uid = _entManager.GetEntity(entState.NetEntity);
if (uid != WatchEntId)
continue;
if (!entState.ComponentChanges.HasContents)
@@ -115,7 +118,9 @@ namespace Robust.Client.GameStates
foreach (var ent in args.Detached)
{
if (ent != WatchEntId)
var uid = _entManager.GetEntity(ent);
if (uid != WatchEntId)
continue;
conShell.WriteLine($"watchEnt: Left PVS at tick {args.AppliedState.ToSequence}, eid={WatchEntId}" + "\n");
@@ -126,7 +131,9 @@ namespace Robust.Client.GameStates
{
foreach (var entDelete in entDeletes.Span)
{
if (entDelete == WatchEntId)
var uid = _entManager.GetEntity(entDelete);
if (uid == WatchEntId)
entDelString = "\n Deleted";
}
}
@@ -294,30 +301,33 @@ namespace Robust.Client.GameStates
private sealed class NetWatchEntCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override string Command => "net_watchent";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntityUid eValue;
EntityUid? entity;
if (args.Length == 0)
{
eValue = IoCManager.Resolve<IPlayerManager>().LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
entity = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
}
else if (!EntityUid.TryParse(args[0], out eValue))
else if (!NetEntity.TryParse(args[0], out var netEntity) || !_entManager.TryGetEntity(netEntity, out entity))
{
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
return;
}
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (!overlayMan.TryGetOverlay(out NetGraphOverlay? overlay))
if (!_overlayManager.TryGetOverlay(out NetGraphOverlay? overlay))
{
overlay = new();
overlayMan.AddOverlay(overlay);
overlay = new NetGraphOverlay();
_overlayManager.AddOverlay(overlay);
}
overlay.WatchEntId = eValue;
overlay.WatchEntId = entity.Value;
}
}
}

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

@@ -62,8 +62,9 @@ namespace Robust.Client.Physics
Log.Info($"Received grid fixture debug data");
if (!_enableDebug) return;
_nodes[ev.Grid] = ev.Nodes;
_connections[ev.Grid] = ev.Connections;
var grid = GetEntity(ev.Grid);
_nodes[grid] = ev.Nodes;
_connections[grid] = ev.Connections;
}
private sealed class GridSplitNodeOverlay : Overlay

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.Physics
{
if (args.Current is not JointComponentState jointState) return;
component.Relay = jointState.Relay;
component.Relay = EnsureEntity<JointComponent>(jointState.Relay, uid);
// Initial state gets applied before the entity (& entity's transform) have been initialized.
// So just let joint init code handle that.
@@ -29,7 +29,7 @@ namespace Robust.Client.Physics
component.Joints.Clear();
foreach (var (id, state) in jointState.Joints)
{
component.Joints[id] = state.GetJoint();
component.Joints[id] = state.GetJoint(EntityManager, uid);
}
return;
}
@@ -62,8 +62,8 @@ namespace Robust.Client.Physics
continue;
}
var other = state.UidA == uid ? state.UidB : state.UidA;
var uidA = GetEntity(state.UidA);
var other = uidA == uid ? GetEntity(state.UidB) : uidA;
// Add new joint (if possible).
// Need to wait for BOTH joint components to come in first before we can add it. Yay dependencies!
@@ -82,11 +82,11 @@ namespace Robust.Client.Physics
// TODO: component state handling ordering.
if (Transform(uid).MapID == MapId.Nullspace)
{
AddedJoints.Add(state.GetJoint());
AddedJoints.Add(state.GetJoint(EntityManager, uid));
continue;
}
AddJoint(state.GetJoint());
AddJoint(state.GetJoint(EntityManager, uid));
}
}
}

View File

@@ -225,7 +225,7 @@ namespace Robust.Client.Placement
}
}))
.Bind(EngineKeyFunctions.EditorPlaceObject, new PointerStateInputCmdHandler(
(session, coords, uid) =>
(session, netCoords, nent) =>
{
if (!IsActive)
return false;
@@ -239,15 +239,15 @@ namespace Robust.Client.Placement
if (Eraser)
{
if (HandleDeletion(coords))
if (HandleDeletion(netCoords))
return true;
if (uid == EntityUid.Invalid)
if (nent == EntityUid.Invalid)
{
return false;
}
HandleDeletion(uid);
HandleDeletion(nent);
}
else
{
@@ -428,7 +428,7 @@ namespace Robust.Client.Placement
var msg = new MsgPlacement();
msg.PlaceType = PlacementManagerMessage.RequestEntRemove;
msg.EntityUid = entity;
msg.EntityUid = EntityManager.GetNetEntity(entity);
_networkManager.ClientSendMessage(msg);
}
@@ -436,7 +436,7 @@ namespace Robust.Client.Placement
{
var msg = new MsgPlacement();
msg.PlaceType = PlacementManagerMessage.RequestRectRemove;
msg.EntityCoordinates = new EntityCoordinates(StartPoint.EntityId, rect.BottomLeft);
msg.NetCoordinates = new NetCoordinates(EntityManager.GetNetEntity(StartPoint.EntityId), rect.BottomLeft);
msg.RectSize = rect.Size;
_networkManager.ClientSendMessage(msg);
}
@@ -790,7 +790,7 @@ namespace Robust.Client.Placement
message.EntityTemplateName = CurrentPermission.EntityType;
// world x and y
message.EntityCoordinates = coordinates;
message.NetCoordinates = EntityManager.GetNetCoordinates(coordinates);
message.DirRcv = Direction;

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

@@ -6,6 +6,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
@@ -24,6 +25,7 @@ namespace Robust.Client.Player
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly ILogManager _logMan = default!;
/// <summary>
/// Active sessions of connected clients to the server.
@@ -65,6 +67,8 @@ namespace Robust.Client.Player
}
}
private LocalPlayer? _localPlayer;
private ISawmill _sawmill = default!;
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
/// <inheritdoc />
@@ -82,6 +86,7 @@ namespace Robust.Client.Player
{
_client.RunLevelChanged += OnRunLevelChanged;
_sawmill = _logMan.GetSawmill("player");
_network.RegisterNetMessage<MsgPlayerListReq>();
_network.RegisterNetMessage<MsgPlayerList>(HandlePlayerList);
}
@@ -122,7 +127,13 @@ namespace Robust.Client.Player
if (myState != null)
{
UpdateAttachedEntity(myState.ControlledEntity);
var uid = _entManager.GetEntity(myState.ControlledEntity);
if (myState.ControlledEntity is {Valid: true} && !_entManager.EntityExists(uid))
{
_sawmill.Error($"Received player state for local player with an unknown net entity!");
}
UpdateAttachedEntity(uid);
UpdateSessionStatus(myState.Status);
}
@@ -181,11 +192,13 @@ namespace Robust.Client.Player
if (_sessions.TryGetValue(state.UserId, out var session))
{
var local = (PlayerSession) session;
var controlled = _entManager.GetEntity(state.ControlledEntity);
// Exists, update data.
if (local.Name == state.Name
&& local.Status == state.Status
&& local.Ping == state.Ping
&& local.AttachedEntity == state.ControlledEntity)
&& local.AttachedEntity == controlled)
{
continue;
}
@@ -194,7 +207,7 @@ namespace Robust.Client.Player
local.Name = state.Name;
local.Status = state.Status;
local.Ping = state.Ping;
local.AttachedEntity = state.ControlledEntity;
local.AttachedEntity = controlled;
}
else
{
@@ -206,7 +219,7 @@ namespace Robust.Client.Player
Name = state.Name,
Status = state.Status,
Ping = state.Ping,
AttachedEntity = state.ControlledEntity,
AttachedEntity = _entManager.GetEntity(state.ControlledEntity),
};
_sessions.Add(state.UserId, newSession);
if (state.UserId == LocalPlayer!.UserId)

View File

@@ -0,0 +1,22 @@
using Robust.Client.Replays.UI;
using Robust.Client.UserInterface;
using Robust.Shared.Console;
using Robust.Shared.IoC;
namespace Robust.Client.Replays.Commands;
public sealed class ReplayToggleUiCommand : BaseReplayCommand
{
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
public override string Command => "replay_toggleui";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var screen = _userInterfaceManager.ActiveScreen;
if (screen == null || !screen.TryGetWidget(out ReplayControlWidget? replayWidget))
return;
replayWidget.Visible = !replayWidget.Visible;
}
}

View File

@@ -83,8 +83,8 @@ public sealed partial class ReplayLoadManager
}
HashSet<ResPath> uploadedFiles = new();
var detached = new HashSet<EntityUid>();
var detachQueue = new Dictionary<GameTick, List<EntityUid>>();
var detached = new HashSet<NetEntity>();
var detachQueue = new Dictionary<GameTick, List<NetEntity>>();
if (initMessages != null)
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
@@ -92,11 +92,11 @@ public sealed partial class ReplayLoadManager
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
var entSpan = state0.EntityStates.Value;
Dictionary<EntityUid, EntityState> entStates = new(entSpan.Count);
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
foreach (var entState in entSpan)
{
var modifiedState = AddImplicitData(entState);
entStates.Add(entState.Uid, modifiedState);
entStates.Add(entState.NetEntity, modifiedState);
}
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
@@ -112,11 +112,11 @@ public sealed partial class ReplayLoadManager
default,
entStates.Values.ToArray(),
playerStates.Values.ToArray(),
Array.Empty<EntityUid>());
Array.Empty<NetEntity>());
checkPoints.Add(new CheckpointState(state0, timeBase, cvars, 0, detached));
DebugTools.Assert(state0.EntityDeletions.Value.Count == 0);
var empty = Array.Empty<EntityUid>();
var empty = Array.Empty<NetEntity>();
TimeSpan GetTime(GameTick tick)
{
@@ -176,8 +176,8 @@ public sealed partial class ReplayLoadManager
private void ProcessQueue(
GameTick curTick,
Dictionary<GameTick, List<EntityUid>> detachQueue,
HashSet<EntityUid> detached)
Dictionary<GameTick, List<NetEntity>> detachQueue,
HashSet<NetEntity> detached)
{
foreach (var (tick, ents) in detachQueue)
{
@@ -192,7 +192,7 @@ public sealed partial class ReplayLoadManager
HashSet<ResPath> uploadedFiles,
Dictionary<Type, HashSet<string>> prototypes,
Dictionary<string, object> cvars,
Dictionary<GameTick, List<EntityUid>> detachQueue,
Dictionary<GameTick, List<NetEntity>> detachQueue,
ref (TimeSpan, GameTick) timeBase,
bool ignoreDuplicates = false)
{
@@ -301,8 +301,8 @@ public sealed partial class ReplayLoadManager
_locMan.ReloadLocalizations();
}
private void UpdateDeletions(NetListAsArray<EntityUid> entityDeletions,
Dictionary<EntityUid, EntityState> entStates, HashSet<EntityUid> detached)
private void UpdateDeletions(NetListAsArray<NetEntity> entityDeletions,
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached)
{
foreach (var ent in entityDeletions.Span)
{
@@ -311,16 +311,16 @@ public sealed partial class ReplayLoadManager
}
}
private void UpdateEntityStates(ReadOnlySpan<EntityState> span, Dictionary<EntityUid, EntityState> entStates,
ref int spawnedTracker, ref int stateTracker, HashSet<EntityUid> detached)
private void UpdateEntityStates(ReadOnlySpan<EntityState> span, Dictionary<NetEntity, EntityState> entStates,
ref int spawnedTracker, ref int stateTracker, HashSet<NetEntity> detached)
{
foreach (var entState in span)
{
detached.Remove(entState.Uid);
if (!entStates.TryGetValue(entState.Uid, out var oldEntState))
detached.Remove(entState.NetEntity);
if (!entStates.TryGetValue(entState.NetEntity, out var oldEntState))
{
var modifiedState = AddImplicitData(entState);
entStates[entState.Uid] = modifiedState;
entStates[entState.NetEntity] = modifiedState;
spawnedTracker++;
#if DEBUG
@@ -333,11 +333,11 @@ public sealed partial class ReplayLoadManager
}
stateTracker++;
DebugTools.Assert(oldEntState.Uid == entState.Uid);
entStates[entState.Uid] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents);
DebugTools.Assert(oldEntState.NetEntity == entState.NetEntity);
entStates[entState.NetEntity] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents);
#if DEBUG
foreach (var state in entStates[entState.Uid].ComponentChanges.Span)
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
{
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
}
@@ -388,7 +388,7 @@ public sealed partial class ReplayLoadManager
}
DebugTools.Assert(newState.NetComponents == null || newState.NetComponents.Count == combined.Count);
return new EntityState(newState.Uid, combined, newState.EntityLastModified, newState.NetComponents ?? oldNetComps);
return new EntityState(newState.NetEntity, combined, newState.EntityLastModified, newState.NetComponents ?? oldNetComps);
}
private void UpdatePlayerStates(ReadOnlySpan<PlayerState> span, Dictionary<NetUserId, PlayerState> playerStates)

View File

@@ -70,14 +70,14 @@ public sealed partial class ReplayLoadManager
{
// This shouldn't be possible, yet it has happened?
// TODO this should probably also throw an exception.
_sawmill.Error($"Encountered blank entity state? Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
_sawmill.Error($"Encountered blank entity state? Entity: {entState.NetEntity}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
return null;
}
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new MissingMetadataException(entState.Uid);
throw new MissingMetadataException(entState.NetEntity);
_sawmill.Error($"Missing metadata component. Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}.");
_sawmill.Error($"Missing metadata component. Entity: {entState.NetEntity}. Last modified: {entState.EntityLastModified}.");
return null;
}
}

View File

@@ -68,10 +68,18 @@ public sealed partial class ReplayLoadManager
var metaState = (MetaDataComponentState?)ent.ComponentChanges.Value?
.FirstOrDefault(c => c.NetID == _metaId).State;
if (metaState == null)
throw new MissingMetadataException(ent.Uid);
throw new MissingMetadataException(ent.NetEntity);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, ent.Uid);
entities.Add(ent.Uid);
var uid = _entMan.CreateEntityUninitialized(metaState.PrototypeId);
entities.Add(uid);
var metaComp = _entMan.GetComponent<MetaDataComponent>(uid);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entMan.ClearNetEntity(metaComp.NetEntity);
_entMan.SetNetEntity(uid, ent.NetEntity, metaComp);
if (i++ % 50 == 0)
{

View File

@@ -19,7 +19,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
{
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly EntityManager _entMan = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IClientNetManager _netMan = default!;
[Dependency] private readonly IComponentFactory _factory = default!;

View File

@@ -82,19 +82,28 @@ internal sealed partial class ReplayPlaybackManager
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
foreach (var es in checkpoint.DetachedStates)
{
if (metas.TryGetComponent(es.Uid, out var meta) && !meta.EntityDeleted)
var uid = _entMan.GetEntity(es.NetEntity);
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
continue;
;
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
.FirstOrDefault(c => c.NetID == _metaId).State;
if (metaState == null)
throw new MissingMetadataException(es.Uid);
throw new MissingMetadataException(es.NetEntity);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, es.Uid);
meta = metas.GetComponent(es.Uid);
_entMan.InitializeEntity(es.Uid, meta);
_entMan.StartEntity(es.Uid);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
meta = metas.GetComponent(uid);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entMan.ClearNetEntity(meta.NetEntity);
_entMan.SetNetEntity(uid, es.NetEntity, meta);
_entMan.InitializeEntity(uid, meta);
_entMan.StartEntity(uid);
meta.LastStateApplied = checkpoint.Tick;
}
}

View File

@@ -50,7 +50,7 @@ internal sealed partial class ReplayPlaybackManager
}
_timing.CurTick += 1;
_entMan.TickUpdate(args.DeltaSeconds, noPredictions: true);
_cEntManager.TickUpdate(args.DeltaSeconds, noPredictions: true);
if (!Playing || AutoPauseCountdown == null)
return;
@@ -82,7 +82,7 @@ internal sealed partial class ReplayPlaybackManager
// Maybe track our own detach queue and use _gameState.DetachImmediate()?
// That way we don't have to clone this. Downside would be that all entities will be immediately
// detached. I.e., the detach budget cvar will simply be ignored.
var clone = new List<EntityUid>(leavePvs.Entities);
var clone = new List<NetEntity>(leavePvs.Entities);
_gameState.QueuePvsDetach(clone, leavePvs.Tick);
continue;

View File

@@ -33,7 +33,8 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IGameController _controller = default!;
[Dependency] private readonly IClientEntityManager _entMan = default!;
[Dependency] private readonly IClientEntityManager _cEntManager = default!;
[Dependency] private readonly ClientEntityManager _entMan = default!;
[Dependency] private readonly IConfigurationManager _confMan = default!;
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
[Dependency] private readonly IClientGameStateManager _gameState = default!;

View File

@@ -95,12 +95,12 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
{
var tick = _timing.LastRealTick;
var players = _player.Sessions.Select(GetPlayerState).ToArray();
var deletions = Array.Empty<EntityUid>();
var deletions = Array.Empty<NetEntity>();
var fullRep = _state.GetFullRep();
var entStates = new EntityState[fullRep.Count];
var i = 0;
foreach (var (uid, dict) in fullRep)
foreach (var (netEntity, dict) in fullRep)
{
var compData = new ComponentChange[dict.Count];
var netComps = new HashSet<ushort>(dict.Keys);
@@ -110,7 +110,7 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
compData[j++] = new ComponentChange(id, compState, tick);
}
entStates[i++] = new EntityState(uid, compData, tick, netComps);
entStates[i++] = new EntityState(netEntity, compData, tick, netComps);
}
var state = new GameState(
@@ -121,16 +121,17 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
players,
deletions);
var detached = new List<EntityUid>();
var detached = new List<NetEntity>();
var query = _entMan.AllEntityQueryEnumerator<MetaDataComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (uid.IsClientSide())
if (_entMan.IsClientSide(uid))
continue;
DebugTools.Assert(fullRep.ContainsKey(uid));
var nent = comp.NetEntity;
DebugTools.Assert(fullRep.ContainsKey(nent));
if ((comp.Flags & MetaDataFlags.Detached) != 0)
detached.Add(uid);
detached.Add(nent);
}
var detachMsg = detached.Count > 0 ? new ReplayMessage.LeavePvs(detached, tick) : null;
@@ -144,7 +145,7 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
UserId = session.UserId,
Status = session.Status,
Name = session.Name,
ControlledEntity = session.AttachedEntity,
ControlledEntity = _entMan.GetNetEntity(session.AttachedEntity),
};
}
}

View File

@@ -63,10 +63,8 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
base.FrameUpdate(args);
if (_playback.Replay is not { } replay)
{
Visible = false;
return;
}
Visible = true;
TickSlider.MinValue = 0;
TickSlider.MaxValue = (float)replay.ReplayTime[^1].TotalSeconds;

View File

@@ -715,7 +715,7 @@ namespace Robust.Client.UserInterface
maxH = MathHelper.Clamp(maxConstraint, minH, maxH);
minConstraint = float.IsNaN(setH) ? 0 : setH;
minH = MathHelper.Clamp(maxH, minConstraint, minH);
minH = MathHelper.Clamp(minConstraint, minH, maxH);
return new Vector2(
Math.Clamp(avail.X, minW, maxW),

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

@@ -1,16 +1,32 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
public sealed class Tooltip : PanelContainer
{
private readonly Label _label;
private readonly RichTextLabel _label;
public string? Text
{
get => _label.Text;
set => _label.Text = value;
get => _label.GetMessage();
set
{
if (value == null)
{
_label.SetMessage(string.Empty);
}
else
{
_label.SetMessage(value);
}
}
}
public void SetMessage(FormattedMessage message)
{
_label.SetMessage(message);
}
/// <summary>
@@ -28,7 +44,7 @@ namespace Robust.Client.UserInterface.CustomControls
AddChild(vbox);
vbox.AddChild(_label = new Label());
vbox.AddChild(_label = new RichTextLabel());
}
protected override void FrameUpdate(FrameEventArgs args)

View File

@@ -38,20 +38,22 @@ namespace Robust.Client.UserInterface
/// <param name="tooltip">control to position (current size will be used to determine bounds)</param>
public static void PositionTooltip(Vector2 screenBounds, Vector2 screenPosition, Control tooltip)
{
LayoutContainer.SetPosition(tooltip, screenPosition);
tooltip.Measure(Vector2Helpers.Infinity);
var combinedMinSize = tooltip.DesiredSize;
var (right, bottom) = tooltip.Position + combinedMinSize;
LayoutContainer.SetPosition(tooltip, new Vector2(screenPosition.X, screenPosition.Y - combinedMinSize.Y));
var right = tooltip.Position.X + combinedMinSize.X;
var top = tooltip.Position.Y - combinedMinSize.Y;
if (right > screenBounds.X)
{
LayoutContainer.SetPosition(tooltip, new(screenPosition.X - combinedMinSize.X, tooltip.Position.Y));
}
if (bottom > screenBounds.Y)
if (top < 0f)
{
LayoutContainer.SetPosition(tooltip, new(tooltip.Position.X, screenPosition.Y - combinedMinSize.Y));
LayoutContainer.SetPosition(tooltip, new(tooltip.Position.X, 0f));
}
}
}

View File

@@ -39,7 +39,7 @@ internal partial class UserInterfaceManager
private float? _tooltipDelay;
private bool _showingTooltip;
private Control? _suppliedTooltip;
private const float TooltipDelay = 1;
private const float TooltipDelay = 0.25f;
private WindowRoot? _focusedRoot;

View File

@@ -103,7 +103,7 @@ namespace Robust.Client.Utility
public static Direction TurnCw(this Direction dir)
{
return (Direction)(((int)dir - 1) % 8);
return (Direction)(((int)dir + 7) % 8);
}
public static Direction TurnCcw(this Direction dir)

View File

@@ -218,7 +218,7 @@ namespace Robust.Client.ViewVariables
{
// TODO: more flexibility in allowing custom instances here.
ViewVariablesInstance instance;
if (obj is EntityUid entity && _entityManager.EntityExists(entity))
if (obj is NetEntity netEntity && _entityManager.GetEntity(netEntity).IsValid())
{
instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer, Sawmill);
}
@@ -269,7 +269,7 @@ namespace Robust.Client.ViewVariables
var type = Type.GetType(blob.ObjectType);
// TODO: more flexibility in allowing custom instances here.
ViewVariablesInstance instance;
if (type != null && typeof(EntityUid).IsAssignableFrom(type))
if (type != null && typeof(NetEntity).IsAssignableFrom(type))
{
instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer, Sawmill);
}

View File

@@ -41,7 +41,8 @@ namespace Robust.Client.ViewVariables.Instances
private const int TabServerComponents = 3;
private TabContainer _tabs = default!;
private EntityUid _entity = default!;
private EntityUid _entity;
private NetEntity _netEntity = default!;
private ViewVariablesAddWindow? _addComponentWindow;
private bool _addComponentServer;
@@ -71,7 +72,8 @@ namespace Robust.Client.ViewVariables.Instances
public override void Initialize(DefaultWindow window, object obj)
{
_entity = (EntityUid) obj;
_netEntity = (NetEntity) obj;
_entity = _entityManager.GetEntity(_netEntity);
var scrollContainer = new ScrollContainer();
//scrollContainer.SetAnchorPreset(Control.LayoutPreset.Wide, true);
@@ -163,7 +165,7 @@ namespace Robust.Client.ViewVariables.Instances
PopulateClientComponents();
if (!_entity.IsClientSide())
if (!_entityManager.IsClientSide(_entity))
{
_serverVariables = new BoxContainer
{
@@ -268,12 +270,12 @@ namespace Robust.Client.ViewVariables.Instances
button.OnPressed += _ =>
{
ViewVariablesManager.OpenVV(
new ViewVariablesComponentSelector(_entity, componentType.FullName));
new ViewVariablesComponentSelector(_entityManager.GetNetEntity(_entity), componentType.FullName));
};
removeButton.OnPressed += _ =>
{
// We send a command to remove the component.
IoCManager.Resolve<IClientConsoleHost>().RemoteExecuteCommand(null, $"rmcomp {_entity} {componentType.ComponentName}");
IoCManager.Resolve<IClientConsoleHost>().RemoteExecuteCommand(null, $"rmcomp {_netEntity} {componentType.ComponentName}");
PopulateServerComponents();
};
button.AddChild(removeButton);
@@ -417,7 +419,7 @@ namespace Robust.Client.ViewVariables.Instances
if (_addComponentServer)
{
// Attempted to add a component to the server entity... We send a command.
IoCManager.Resolve<IClientConsoleHost>().RemoteExecuteCommand(null, $"addcomp {_entity} {eventArgs.Entry}");
IoCManager.Resolve<IClientConsoleHost>().RemoteExecuteCommand(null, $"addcomp {_netEntity} {eventArgs.Entry}");
PopulateServerComponents();
_addComponentWindow?.Populate(await GetValidServerComponentsForAdding());
return;
@@ -504,7 +506,7 @@ namespace Robust.Client.ViewVariables.Instances
try
{
_entitySession =
await ViewVariablesManager.RequestSession(new ViewVariablesEntitySelector(_entity));
await ViewVariablesManager.RequestSession(new ViewVariablesEntitySelector(_netEntity));
}
catch (SessionDenyException e)
{

View File

@@ -63,20 +63,22 @@ namespace Robust.Client.ViewVariables
}
// Entity.
if (!EntityUid.TryParse(args[0], out var entity))
if (!NetEntity.TryParse(args[0], out var netEntity))
{
shell.WriteLine("Invalid specifier format.");
return;
}
var entity = _entities.GetEntity(netEntity);
if (!_entities.EntityExists(entity))
{
shell.WriteLine("That entity does not exist locally. Attempting to open remote view...");
_cvvm.OpenVV(new ViewVariablesEntitySelector(entity));
_cvvm.OpenVV(new ViewVariablesEntitySelector(netEntity));
return;
}
_cvvm.OpenVV(entity);
_cvvm.OpenVV(netEntity);
}
}
}

View File

@@ -84,14 +84,17 @@ using Robust.Shared.IoC;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
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)}}
@@ -178,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
{
}
""");
@@ -355,13 +358,32 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
var name = field.Symbol.Name;
var tempVarName = $"{name}Temp";
var nullableValue = isNullableValueType ? ".Value" : string.Empty;
var nullNotAllowed = isClass && !isNullable;
if (field.CustomSerializer is { Serializer: var serializer, Type: var serializerType })
{
if (isClass || isNullableValueType)
if (nullNotAllowed)
{
builder.AppendLine($$"""
if ({{name}} != null)
if ({{name}} == null)
{
throw new NullNotAllowedException();
}
""");
}
builder.AppendLine($$"""
{{typeName}} {{tempVarName}} = default!;
""");
if (isNullable || isNullableValueType)
{
builder.AppendLine($$"""
if ({{name}} == null)
{
{{tempVarName}} = null!;
}
else
{
""");
}
@@ -385,6 +407,7 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
CreateCopyCustom(
builder,
name,
tempVarName,
nonNullableTypeName,
serializerName,
nullableValue,
@@ -393,10 +416,19 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
break;
}
if (isClass || isNullableValueType)
if (isNullable || isNullableValueType)
{
builder.AppendLine("}");
}
if (definition.Type.IsValueType)
{
structCopier.AppendLine($"{name} = {tempVarName}!,");
}
else
{
builder.AppendLine($"target.{name} = {tempVarName}!;");
}
}
else
{
@@ -404,25 +436,18 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
{{typeName}} {{tempVarName}} = default!;
""");
if (isClass)
if (nullNotAllowed)
{
builder.AppendLine($$"""
if ({{name}} != null)
if ({{name}} == null)
{
throw new NullNotAllowedException();
}
""");
}
var instantiator = string.Empty;
if (!type.IsAbstract &&
HasEmptyPublicConstructor(type) &&
(type.IsReferenceType || IsNullableType(type)))
{
instantiator = $"{tempVarName} = new();";
}
var hasHooks = ImplementsInterface(type, SerializationHooksNamespace) || !type.IsSealed;
builder.AppendLine($$"""
{{instantiator}}
if (!serialization.TryCustomCopy(this.{{name}}, ref {{tempVarName}}, hookCtx, {{hasHooks.ToString().ToLower()}}, context))
{
""");
@@ -434,26 +459,22 @@ 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 orNew = type.IsReferenceType
? $" ?? {name}{nullability}.Instantiate()"
: string.Empty;
var nullable = !type.IsValueType || IsNullableType(type);
builder.AppendLine($"var temp = {name}{orNew};");
if (nullable)
{
builder.AppendLine("""
if (temp != null)
builder.AppendLine($$"""
if ({{name}} == null)
{
{{tempVarName}} = null!;
}
else
{
""");
}
builder.AppendLine($$"""
{{name}}{{nullability}}.Copy(ref temp, serialization, hookCtx, context);
{{tempVarName}} = temp;
serialization.CopyTo({{name}}, ref {{tempVarName}}, hookCtx, context{{nullableOverride}});
""");
if (nullable)
@@ -468,18 +489,13 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
builder.AppendLine("}");
if (isClass)
{
builder.AppendLine("}");
}
if (definition.Type.IsValueType)
{
structCopier.AppendLine($"{name} = {tempVarName},");
structCopier.AppendLine($"{name} = {tempVarName}!,");
}
else
{
builder.AppendLine($"target.{name} = {tempVarName};");
builder.AppendLine($"target.{name} = {tempVarName}!;");
}
}
}
@@ -510,25 +526,27 @@ if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.
var newTemp = isNullable && isClass ? $"{tempVarName} ??= new();" : string.Empty;
var nullableOverride = isClass ? ", true" : string.Empty;
var nullableValue = isNullableValueType ? ".Value" : string.Empty;
var nonNullableTypeName = typeName.EndsWith("?") ? typeName.Substring(0, typeName.Length - 1) : typeName;
builder.AppendLine($$"""
{{typeName}} {{tempVarName}} = default!;
{{nonNullableTypeName}} {{tempVarName}}CopyTo = default!;
{{newTemp}}
serialization.CopyTo<{{typeName}}, {{serializerName}}>(this.{{varName}}{{nullableValue}}, ref {{tempVarName}}, hookCtx, context{{nullableOverride}});
target.{{varName}} = {{tempVarName}};
serialization.CopyTo<{{typeName}}, {{serializerName}}>(this.{{varName}}{{nullableValue}}, ref {{tempVarName}}CopyTo, hookCtx, context{{nullableOverride}});
{{tempVarName}} = {{tempVarName}}CopyTo;
""");
}
private static void CreateCopyCustom(
StringBuilder builder,
string varName,
string tempVarName,
string nonNullableTypeName,
string serializerName,
string nullableValue,
string nullableOverride)
{
builder.AppendLine($$"""
target.{{varName}} = serialization.CreateCopy<{{nonNullableTypeName}}, {{serializerName}}>(this.{{varName}}{{nullableValue}}, hookCtx, context{{nullableOverride}});
{{tempVarName}} = serialization.CreateCopy<{{nonNullableTypeName}}, {{serializerName}}>(this.{{varName}}{{nullableValue}}, hookCtx, context{{nullableOverride}});
""");
}
}

View File

@@ -9,7 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.4.0" />
</ItemGroup>
</Project>

View File

@@ -286,23 +286,6 @@ internal static class Types
return true;
}
internal static bool HasEmptyPublicConstructor(ITypeSymbol type)
{
if (type is not INamedTypeSymbol named)
return false;
foreach (var constructor in named.InstanceConstructors)
{
if (constructor.DeclaredAccessibility == Accessibility.Public &&
constructor.Parameters.Length == 0)
{
return true;
}
}
return false;
}
internal static bool IsVirtualClass(ITypeSymbol type)
{
return type.IsReferenceType && !type.IsSealed && type.TypeKind != TypeKind.Interface;

View File

@@ -369,7 +369,6 @@ namespace Robust.Server
// otherwise the prototypes will be cleared
_prototype.Initialize();
_prototype.LoadDefaultPrototypes();
_prototype.ResolveResults();
_refMan.Initialize();
IoCManager.Resolve<ToolshedManager>().Initialize();
@@ -389,6 +388,11 @@ namespace Robust.Server
_protoLoadMan.Initialize();
_netResMan.Initialize();
// String serializer has to be locked before PostInit as content can depend on it (e.g., replays that start
// automatically recording on startup).
AddFinalStringsToSerializer();
_stringSerializer.LockStrings();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
_statusHost.Start();
@@ -398,9 +402,6 @@ namespace Robust.Server
_watchdogApi.Initialize();
AddFinalStringsToSerializer();
_stringSerializer.LockStrings();
if (OperatingSystem.IsWindows() && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
{
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));

View File

@@ -22,13 +22,13 @@ namespace Robust.Server.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
if (!NetEntity.TryParse(args[0], out var uidNet))
{
shell.WriteLine($"{uid} is not a valid entity uid.");
shell.WriteLine($"{args[0]} is not a valid entity.");
return;
}
if (!_entityManager.EntityExists(uid))
if (!_entityManager.TryGetEntity(uidNet, out var uid) || !_entityManager.EntityExists(uid))
{
shell.WriteLine($"No entity found with id {uid}.");
return;
@@ -44,17 +44,17 @@ namespace Robust.Server.Console.Commands
if (_entityManager.HasComponent(uid, registration.Type))
{
shell.WriteLine($"Entity {_entityManager.GetComponent<MetaDataComponent>(uid).EntityName} already has a {componentName} component.");
shell.WriteLine($"Entity {_entityManager.GetComponent<MetaDataComponent>(uid.Value).EntityName} already has a {componentName} component.");
}
var component = (Component) _componentFactory.GetComponent(registration.Type);
#pragma warning disable CS0618
component.Owner = uid;
component.Owner = uid.Value;
#pragma warning restore CS0618
_entityManager.AddComponent(uid, component);
_entityManager.AddComponent(uid.Value, component);
shell.WriteLine($"Added {componentName} component to entity {_entityManager.GetComponent<MetaDataComponent>(uid).EntityName}.");
shell.WriteLine($"Added {componentName} component to entity {_entityManager.GetComponent<MetaDataComponent>(uid.Value).EntityName}.");
}
}
}

View File

@@ -19,19 +19,19 @@ namespace Robust.Server.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var entity))
if (!NetEntity.TryParse(args[0], out var entityNet))
{
shell.WriteLine("Invalid entity UID.");
return;
}
if (!_entityManager.EntityExists(entity))
if (!_entityManager.TryGetEntity(entityNet, out var entity) || !_entityManager.EntityExists(entity))
{
shell.WriteLine("That entity does not exist.");
return;
}
_entityManager.DeleteEntity(entity);
_entityManager.DeleteEntity(entity.Value);
}
}
}

View File

@@ -27,12 +27,14 @@ namespace Robust.Server.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
if (!NetEntity.TryParse(args[0], out var uidNet))
{
shell.WriteError("Not a valid entity ID.");
return;
}
var uid = _ent.GetEntity(uidNet);
// no saving default grid
if (!_ent.EntityExists(uid))
{

View File

@@ -22,12 +22,14 @@ namespace Robust.Server.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
if (!NetEntity.TryParse(args[0], out var netEntity))
{
shell.WriteLine($"{uid} is not a valid entity uid.");
shell.WriteLine($"{netEntity} is not a valid entity uid.");
return;
}
var uid = _entityManager.GetEntity(netEntity);
if (!_entityManager.EntityExists(uid))
{
shell.WriteLine($"No entity found with id {uid}.");

View File

@@ -24,7 +24,7 @@ public sealed class ScaleCommand : LocalizedCommands
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
if (!NetEntity.TryParse(args[0], out var netEntity))
{
shell.WriteError($"Unable to find entity {args[0]}");
return;
@@ -47,6 +47,7 @@ public sealed class ScaleCommand : LocalizedCommands
var physics = _entityManager.System<SharedPhysicsSystem>();
var appearance = _entityManager.System<AppearanceSystem>();
var uid = _entityManager.GetEntity(netEntity);
_entityManager.EnsureComponent<ScaleVisualsComponent>(uid);
var @event = new ScaleEntityEvent();
_entityManager.EventBus.RaiseLocalEvent(uid, ref @event);
@@ -59,19 +60,20 @@ public sealed class ScaleCommand : LocalizedCommands
if (_entityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
foreach (var fixture in manager.Fixtures.Values)
foreach (var (id, fixture) in manager.Fixtures)
{
switch (fixture.Shape)
{
case EdgeShape edge:
physics.SetVertices(uid, fixture, edge,
physics.SetVertices(uid, id, fixture,
edge,
edge.Vertex0 * scale,
edge.Vertex1 * scale,
edge.Vertex2 * scale,
edge.Vertex3 * scale, manager);
break;
case PhysShapeCircle circle:
physics.SetPositionRadius(uid, fixture, circle, circle.Position * scale, circle.Radius * scale, manager);
physics.SetPositionRadius(uid, id, fixture, circle, circle.Position * scale, circle.Radius * scale, manager);
break;
case PolygonShape poly:
var verts = poly.Vertices;
@@ -81,7 +83,7 @@ public sealed class ScaleCommand : LocalizedCommands
verts[i] *= scale;
}
physics.SetVertices(uid, fixture, poly, verts, manager);
physics.SetVertices(uid, id, fixture, poly, verts, manager);
break;
default:
throw new NotImplementedException();

View File

@@ -29,10 +29,12 @@ public sealed class SpinCommand : LocalizedCommands
}
// get the target
EntityUid target;
EntityUid? target;
if (args.Length == 3)
{
if (!EntityUid.TryParse(args[2], out target))
if (!NetEntity.TryParse(args[2], out var targetNet) ||
!_entities.TryGetEntity(targetNet, out target))
{
shell.WriteError($"Unable to find entity {args[1]}");
return;
@@ -65,6 +67,6 @@ public sealed class SpinCommand : LocalizedCommands
var physicsSystem = _entities.System<SharedPhysicsSystem>();
physicsSystem.SetAngularDamping(physics, drag);
physicsSystem.SetAngularVelocity(target, speed, body: physics);
physicsSystem.SetAngularVelocity(target.Value, speed, body: physics);
}
}

View File

@@ -137,10 +137,10 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, new Fixture("fix1", horizontal, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
fixtures.CreateFixture(groundUid, new Fixture("fix2", vertical, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
@@ -166,7 +166,7 @@ namespace Robust.Server.Console.Commands
shape = new PolygonShape();
shape.SetAsBox(0.5f, 0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true), body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
physics.WakeBody(boxUid, body: box);
}
@@ -184,10 +184,10 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, new Fixture("fix1", horizontal, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
fixtures.CreateFixture(groundUid, new Fixture("fix2", vertical, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
@@ -213,7 +213,7 @@ namespace Robust.Server.Console.Commands
physics.SetFixedRotation(boxUid, false, body: box);
// TODO: Need to detect shape and work out if we need to use fixedrotation
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true, 5f));
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
physics.WakeBody(boxUid, body: box);
}
}
@@ -232,7 +232,7 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
fixtures.CreateFixture(groundUid, new Fixture("fix1", horizontal, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
physics.WakeBody(groundUid, body: ground);
// Setup boxes
@@ -255,7 +255,7 @@ namespace Robust.Server.Console.Commands
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true, 5f), body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
y += deltaY;
physics.WakeBody(boxUid, body: box);
@@ -275,7 +275,7 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
// Due to lookup changes fixtureless bodies are invalid, so
var cShape = new PhysShapeCircle(1f);
fixtures.CreateFixture(groundUid, new Fixture("fix1", cShape, 0, 0, false));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
var bodyUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var body = _ent.AddComponent<PhysicsComponent>(bodyUid);
@@ -288,19 +288,19 @@ namespace Robust.Server.Console.Commands
// TODO: Box2D just deref, bleh shape structs someday
var shape1 = new PolygonShape();
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix1", shape1, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
var shape2 = new PolygonShape();
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix2", shape2, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
var shape3 = new PolygonShape();
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix3", shape3, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
var shape4 = new PolygonShape();
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix4", shape4, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
physics.WakeBody(groundUid, body: ground);
physics.WakeBody(bodyUid, body: body);
@@ -328,7 +328,7 @@ namespace Robust.Server.Console.Commands
physics.SetFixedRotation(boxUid, false, body: box);
var shape = new PolygonShape();
shape.SetAsBox(0.125f, 0.125f);
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true, 0.0625f), body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
physics.WakeBody(boxUid, body: box);
});
}

View File

@@ -29,19 +29,19 @@ namespace Robust.Server.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
if (!NetEntity.TryParse(args[0], out var uidNet))
{
shell.WriteError($"Unable to parse {args[0]} as a {nameof(EntityUid)}");
return;
}
if (!_entities.EntityExists(uid))
if (!_entities.TryGetEntity(uidNet, out var uid) || !_entities.EntityExists(uid))
{
shell.WriteError($"Unable to find entity {uid}");
return;
}
_entities.EntitySysManager.GetEntitySystem<ViewSubscriberSystem>().AddViewSubscriber(uid, playerSession);
_entities.EntitySysManager.GetEntitySystem<ViewSubscriberSystem>().AddViewSubscriber(uid.Value, playerSession);
}
public sealed class RemoveViewSubscriberCommand : LocalizedCommands
@@ -66,19 +66,19 @@ namespace Robust.Server.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
if (!NetEntity.TryParse(args[0], out var uidNet))
{
shell.WriteError($"Unable to parse {args[0]} as a {nameof(EntityUid)}");
shell.WriteError($"Unable to parse {args[0]} as a {nameof(NetEntity)}");
return;
}
if (!_entities.EntityExists(uid))
if (!_entities.TryGetEntity(uidNet, out var uid) || !_entities.EntityExists(uid))
{
shell.WriteError($"Unable to find entity {uid}");
return;
}
_entities.EntitySysManager.GetEntitySystem<ViewSubscriberSystem>().RemoveViewSubscriber(uid, playerSession);
_entities.EntitySysManager.GetEntitySystem<ViewSubscriberSystem>().RemoveViewSubscriber(uid.Value, playerSession);
}
}
}

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

@@ -6,7 +6,7 @@ namespace Robust.Server.Containers
{
public sealed class ContainerSystem : SharedContainerSystem
{
protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing)
protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing)
{
Log.Error($"Missing entity for container {ToPrettyString(uid)}. Missing uid: {missing}");
//cont.InternalRemove(ent);

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

@@ -86,9 +86,9 @@ public sealed class AudioSystem : SharedAudioSystem
var msg = new PlayAudioEntityMessage
{
FileName = filename,
Coordinates = transform.Coordinates,
FallbackCoordinates = fallbackCoordinates,
EntityUid = uid,
Coordinates = GetNetCoordinates(transform.Coordinates),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
NetEntity = GetNetEntity(uid),
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id,
};
@@ -108,8 +108,8 @@ public sealed class AudioSystem : SharedAudioSystem
var msg = new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
FallbackCoordinates = fallbackCoordinates,
Coordinates = GetNetCoordinates(coordinates),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};

View File

@@ -61,7 +61,7 @@ namespace Robust.Server.GameObjects
//Client Sanitization: unbound command, just ignore
foreach (var handler in BindRegistry.GetHandlers(function))
{
if (handler.HandleCmdMessage(session, msg)) return;
if (handler.HandleCmdMessage(EntityManager, session, msg)) return;
}
}

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

@@ -83,7 +83,7 @@ namespace Robust.Server.GameObjects
/// </summary>
private void OnMessageReceived(BoundUIWrapMessage msg, EntitySessionEventArgs args)
{
var uid = msg.Entity;
var uid = GetEntity(msg.Entity);
if (!TryComp(uid, out ServerUserInterfaceComponent? uiComp) || args.SenderSession is not IPlayerSession session)
return;
@@ -118,7 +118,7 @@ namespace Robust.Server.GameObjects
// get the wrapped message and populate it with the sender & UI key information.
var message = msg.Message;
message.Session = args.SenderSession;
message.Entity = uid;
message.Entity = GetNetEntity(uid);
message.UiKey = msg.UiKey;
// Raise as object so the correct type is used.
@@ -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);
@@ -306,9 +321,9 @@ namespace Robust.Server.GameObjects
/// The player session to send this new state to.
/// Set to null for sending it to every subscribed player session.
/// </param>
public static void SetUiState(BoundUserInterface bui, BoundUserInterfaceState state, IPlayerSession? session = null, bool clearOverrides = true)
public void SetUiState(BoundUserInterface bui, BoundUserInterfaceState state, IPlayerSession? session = null, bool clearOverrides = true)
{
var msg = new BoundUIWrapMessage(bui.Owner, new UpdateBoundStateMessage(state), bui.UiKey);
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), new UpdateBoundStateMessage(state), bui.UiKey);
if (session == null)
{
bui.LastStateMsg = msg;
@@ -370,7 +385,7 @@ namespace Robust.Server.GameObjects
_openInterfaces.GetOrNew(session).Add(bui);
RaiseLocalEvent(bui.Owner, new BoundUIOpenedEvent(bui.UiKey, bui.Owner, session));
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, new OpenBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new OpenBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
// Fun fact, clients needs to have BUIs open before they can receive the state.....
if (bui.LastStateMsg != null)
@@ -399,7 +414,7 @@ namespace Robust.Server.GameObjects
if (!bui._subscribedSessions.Remove(session))
return false;
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, new CloseBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new CloseBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
CloseShared(bui, session, activeUis);
return true;
}
@@ -477,7 +492,7 @@ namespace Robust.Server.GameObjects
/// </summary>
public void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage message)
{
var msg = new BoundUIWrapMessage(bui.Owner, message, bui.UiKey);
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey);
foreach (var session in bui.SubscribedSessions)
{
RaiseNetworkEvent(msg, session.ConnectedClient);
@@ -503,10 +518,70 @@ namespace Robust.Server.GameObjects
if (!bui.SubscribedSessions.Contains(session))
return false;
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, message, bui.UiKey), session.ConnectedClient);
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.ConnectedClient);
return true;
}
#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

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

View File

@@ -42,8 +42,6 @@ namespace Robust.Server.GameObjects
private ISawmill _netEntSawmill = default!;
protected override int NextEntityUid { get; set; } = (int) EntityUid.FirstUid;
public override void Initialize()
{
_netEntSawmill = LogManager.GetSawmill("net.ent");
@@ -54,9 +52,9 @@ namespace Robust.Server.GameObjects
base.Initialize();
}
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype, EntityUid uid)
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype)
{
return AllocEntity(prototype, out _, uid);
return AllocEntity(prototype, out _);
}
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, IEntityLoadContext? context)
@@ -79,15 +77,15 @@ namespace Robust.Server.GameObjects
StartEntity(entity);
}
private protected override EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null)
private protected override EntityUid CreateEntity(string? prototypeName, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return base.CreateEntity(prototypeName, uid, context);
return base.CreateEntity(prototypeName, context);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
var entity = base.CreateEntity(prototype, uid, context);
var entity = base.CreateEntity(prototype, context);
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,

View File

@@ -24,6 +24,6 @@ namespace Robust.Server.GameStates
Action<ICommonSession, GameTick>? ClientAck { get; set; }
Action<ICommonSession, GameTick, EntityUid?>? ClientRequestFull { get; set; }
Action<ICommonSession, GameTick, NetEntity?>? ClientRequestFull { get; set; }
}
}

View File

@@ -18,7 +18,7 @@ public sealed class PvsOverrideSystem : EntitySystem
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
public void AddGlobalOverride(EntityUid uid, bool removeExistingOverride = true, bool recursive = false)
{
_pvs.EntityPVSCollection.AddGlobalOverride(uid, removeExistingOverride, recursive);
_pvs.EntityPVSCollection.AddGlobalOverride(GetNetEntity(uid), removeExistingOverride, recursive);
}
/// <summary>
@@ -28,7 +28,7 @@ public sealed class PvsOverrideSystem : EntitySystem
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
public void AddSessionOverride(EntityUid uid, ICommonSession session,bool removeExistingOverride = true)
{
_pvs.EntityPVSCollection.UpdateIndex(uid, session, removeExistingOverride);
_pvs.EntityPVSCollection.UpdateIndex(GetNetEntity(uid), session, removeExistingOverride);
}
/// <summary>
@@ -39,6 +39,6 @@ public sealed class PvsOverrideSystem : EntitySystem
if (!Resolve(uid, ref xform))
return;
_pvs.EntityPVSCollection.UpdateIndex(uid, xform.Coordinates, true);
_pvs.EntityPVSCollection.UpdateIndex(GetNetEntity(uid), xform.Coordinates, true);
}
}

View File

@@ -45,7 +45,7 @@ internal sealed partial class PvsSystem
return;
var ackedTick = sessionData.LastReceivedAck;
Dictionary<EntityUid, PvsEntityVisibility>? ackedData;
Dictionary<NetEntity, PvsEntityVisibility>? ackedData;
if (sessionData.Overflow != null && sessionData.Overflow.Value.Tick <= ackedTick)
{

View File

@@ -24,14 +24,13 @@ namespace Robust.Server.GameStates;
internal sealed partial class PvsSystem : EntitySystem
{
[Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
[Shared.IoC.Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
[Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
[Shared.IoC.Dependency] private readonly SharedTransformSystem _transform = default!;
[Shared.IoC.Dependency] private readonly IServerNetConfigurationManager _netConfigManager = default!;
[Shared.IoC.Dependency] private readonly IServerGameStateManager _serverGameStateManager = default!;
[Shared.IoC.Dependency] private readonly IParallelManager _parallelManager = default!;
[Shared.IoC.Dependency] private readonly IComponentFactory _factory = default!;
[Shared.IoC.Dependency] private readonly IServerGameStateManager _serverGameStateManager = default!;
[Shared.IoC.Dependency] private readonly IServerNetConfigurationManager _netConfigManager = default!;
[Shared.IoC.Dependency] private readonly SharedTransformSystem _transform = default!;
public const float ChunkSize = 8;
@@ -63,31 +62,28 @@ internal sealed partial class PvsSystem : EntitySystem
internal readonly Dictionary<ICommonSession, SessionPVSData> PlayerData = new();
private PVSCollection<EntityUid> _entityPvsCollection = default!;
public PVSCollection<EntityUid> EntityPVSCollection => _entityPvsCollection;
private PVSCollection<NetEntity> _entityPvsCollection = default!;
public PVSCollection<NetEntity> EntityPVSCollection => _entityPvsCollection;
private readonly List<IPVSCollection> _pvsCollections = new();
private readonly ObjectPool<Dictionary<EntityUid, PvsEntityVisibility>> _visSetPool
= new DefaultObjectPool<Dictionary<EntityUid, PvsEntityVisibility>>(
new DictPolicy<EntityUid, PvsEntityVisibility>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<NetEntity, PvsEntityVisibility>> _visSetPool
= new DefaultObjectPool<Dictionary<NetEntity, PvsEntityVisibility>>(
new DictPolicy<NetEntity, PvsEntityVisibility>(), MaxVisPoolSize);
private readonly ObjectPool<Stack<EntityUid>> _stackPool
= new DefaultObjectPool<Stack<EntityUid>>(
new StackPolicy<EntityUid>(), MaxVisPoolSize);
private readonly ObjectPool<Stack<NetEntity>> _stackPool
= new DefaultObjectPool<Stack<NetEntity>>(
new StackPolicy<NetEntity>(), MaxVisPoolSize);
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<EntityUid, MetaDataComponent>> _chunkCachePool =
new DefaultObjectPool<Dictionary<EntityUid, MetaDataComponent>>(
new DictPolicy<EntityUid, MetaDataComponent>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<NetEntity, MetaDataComponent>> _chunkCachePool =
new DefaultObjectPool<Dictionary<NetEntity, MetaDataComponent>>(
new DictPolicy<NetEntity, MetaDataComponent>(), MaxVisPoolSize);
private readonly ObjectPool<HashSet<int>> _playerChunkPool =
new DefaultObjectPool<HashSet<int>>(new SetPolicy<int>(), MaxVisPoolSize);
private readonly ObjectPool<RobustTree<EntityUid>> _treePool =
new DefaultObjectPool<RobustTree<EntityUid>>(new TreePolicy<EntityUid>(), MaxVisPoolSize);
private readonly ObjectPool<RobustTree<NetEntity>> _treePool =
new DefaultObjectPool<RobustTree<NetEntity>>(new TreePolicy<NetEntity>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<MapChunkLocation, int>> _mapChunkPool =
new DefaultObjectPool<Dictionary<MapChunkLocation, int>>(
@@ -102,19 +98,25 @@ internal sealed partial class PvsSystem : EntitySystem
private readonly List<(uint, IChunkIndexLocation)> _chunkList = new(64);
internal readonly HashSet<ICommonSession> PendingAcks = new();
private readonly Dictionary<(uint visMask, IChunkIndexLocation location), (Dictionary<NetEntity, MetaDataComponent> metadata,
RobustTree<NetEntity> tree)?> _previousTrees = new();
private readonly HashSet<(uint visMask, IChunkIndexLocation location)> _reusedTrees = new();
private EntityQuery<EyeComponent> _eyeQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<TransformComponent> _xformQuery;
public override void Initialize()
{
base.Initialize();
_eyeQuery = GetEntityQuery<EyeComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
_entityPvsCollection = RegisterPVSCollection<NetEntity>();
SubscribeLocalEvent<MapChangedEvent>(ev =>
{
@@ -159,7 +161,7 @@ internal sealed partial class PvsSystem : EntitySystem
}
// TODO rate limit this?
private void OnClientRequestFull(ICommonSession session, GameTick tick, EntityUid? missingEntity)
private void OnClientRequestFull(ICommonSession session, GameTick tick, NetEntity? missingEntity)
{
if (!PlayerData.TryGetValue(session, out var sessionData))
return;
@@ -173,7 +175,8 @@ internal sealed partial class PvsSystem : EntitySystem
if (missingEntity != null)
{
sb.Append($" Apparently they received an entity without metadata: {ToPrettyString(missingEntity.Value)}.");
var entity = GetEntity(missingEntity)!;
sb.Append($" Apparently they received an entity without metadata: {ToPrettyString(entity.Value)}.");
if (sessionData.LastSeenAt.TryGetValue(missingEntity.Value, out var lastSeenTick))
sb.Append($" Entity last sent: {lastSeenTick.Value}");
@@ -240,17 +243,17 @@ internal sealed partial class PvsSystem : EntitySystem
#region PVSCollection Event Updates
private void OnEntityDeleted(EntityUid e)
private void OnEntityDeleted(EntityUid e, MetaDataComponent metadata)
{
_entityPvsCollection.RemoveIndex(EntityManager.CurrentTick, e);
_entityPvsCollection.RemoveIndex(EntityManager.CurrentTick, metadata.NetEntity);
var previousTick = _gameTiming.CurTick - 1;
foreach (var sessionData in PlayerData.Values)
{
sessionData.LastSeenAt.Remove(e);
sessionData.LastSeenAt.Remove(metadata.NetEntity);
if (sessionData.SentEntities.TryGetValue(previousTick, out var ents))
ents.Remove(e);
ents.Remove(metadata.NetEntity);
}
}
@@ -278,7 +281,7 @@ internal sealed partial class PvsSystem : EntitySystem
DebugTools.Assert(!_mapManager.IsMap(ev.Sender));
var coordinates = _transform.GetMoverCoordinates(ev.Sender, ev.Component);
UpdateEntityRecursive(ev.Sender, ev.Component, coordinates, false, ev.ParentChanged);
UpdateEntityRecursive(ev.Sender, _metaQuery.GetComponent(ev.Sender), ev.Component, coordinates, false, ev.ParentChanged);
}
private void OnTransformStartup(EntityUid uid, TransformComponent component, ref TransformStartupEvent args)
@@ -295,10 +298,10 @@ internal sealed partial class PvsSystem : EntitySystem
DebugTools.Assert(!_mapManager.IsMap(uid));
var coordinates = _transform.GetMoverCoordinates(uid, component);
UpdateEntityRecursive(uid, component, coordinates, false, false);
UpdateEntityRecursive(uid, _metaQuery.GetComponent(uid), component, coordinates, false, false);
}
private void UpdateEntityRecursive(EntityUid uid, TransformComponent xform, EntityCoordinates coordinates, bool mover, bool forceDirty)
private void UpdateEntityRecursive(EntityUid uid, MetaDataComponent metadata, TransformComponent xform, EntityCoordinates coordinates, bool mover, bool forceDirty)
{
if (mover && !xform.LocalPosition.Equals(Vector2.Zero))
{
@@ -310,9 +313,9 @@ internal sealed partial class PvsSystem : EntitySystem
var indices = PVSCollection<EntityUid>.GetChunkIndices(coordinates.Position);
if (xform.GridUid != null)
_entityPvsCollection.UpdateIndex(uid, xform.GridUid.Value, indices, forceDirty: forceDirty);
_entityPvsCollection.UpdateIndex(metadata.NetEntity, xform.GridUid.Value, indices, forceDirty: forceDirty);
else
_entityPvsCollection.UpdateIndex(uid, xform.MapID, indices, forceDirty: forceDirty);
_entityPvsCollection.UpdateIndex(metadata.NetEntity, xform.MapID, indices, forceDirty: forceDirty);
var children = xform.ChildEnumerator;
@@ -322,7 +325,7 @@ internal sealed partial class PvsSystem : EntitySystem
// directly.
while (children.MoveNext(out var child))
{
UpdateEntityRecursive(child.Value, _xformQuery.GetComponent(child.Value), coordinates, true, forceDirty);
UpdateEntityRecursive(child.Value, _metaQuery.GetComponent(child.Value), _xformQuery.GetComponent(child.Value), coordinates, true, forceDirty);
}
}
@@ -386,7 +389,7 @@ internal sealed partial class PvsSystem : EntitySystem
pvsCollection.AddGrid(gridId);
}
_entityPvsCollection.AddGlobalOverride(gridId, true, false);
_entityPvsCollection.AddGlobalOverride(_metaQuery.GetComponent(gridId).NetEntity, true, false);
}
private void OnMapDestroyed(MapChangedEvent e)
@@ -406,7 +409,7 @@ internal sealed partial class PvsSystem : EntitySystem
if(e.Map == MapId.Nullspace) return;
var uid = _mapManager.GetMapEntityId(e.Map);
_entityPvsCollection.AddGlobalOverride(uid, true, false);
_entityPvsCollection.AddGlobalOverride(_metaQuery.GetComponent(uid).NetEntity, true, false);
}
#endregion
@@ -533,14 +536,9 @@ internal sealed partial class PvsSystem : EntitySystem
return (_chunkList, playerChunks, viewerEntities);
}
private Dictionary<(uint visMask, IChunkIndexLocation location), (Dictionary<EntityUid, MetaDataComponent> metadata,
RobustTree<EntityUid> tree)?> _previousTrees = new();
private HashSet<(uint visMask, IChunkIndexLocation location)> _reusedTrees = new();
public void RegisterNewPreviousChunkTrees(
List<(uint, IChunkIndexLocation)> chunks,
(Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] trees,
(Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] trees,
bool[] reuse)
{
// For any chunks able to re-used we'll chuck them in a dictionary for faster lookup.
@@ -584,9 +582,7 @@ internal sealed partial class PvsSystem : EntitySystem
public bool TryCalculateChunk(
IChunkIndexLocation chunkLocation,
uint visMask,
EntityQuery<TransformComponent> transform,
EntityQuery<MetaDataComponent> metadata,
out (Dictionary<EntityUid, MetaDataComponent> mData, RobustTree<EntityUid> tree)? result)
out (Dictionary<NetEntity, MetaDataComponent> mData, RobustTree<NetEntity> tree)? result)
{
if (!_entityPvsCollection.IsDirty(chunkLocation) && _previousTrees.TryGetValue((visMask, chunkLocation), out var previousTree))
{
@@ -613,11 +609,12 @@ internal sealed partial class PvsSystem : EntitySystem
}
var chunkSet = _chunkCachePool.Get();
var tree = _treePool.Get();
foreach (var uid in chunk)
foreach (var netEntity in chunk)
{
AddToChunkSetRecursively(in uid, visMask, tree, chunkSet, transform, metadata);
var uid = GetEntity(netEntity);
AddToChunkSetRecursively(in uid, in netEntity, visMask, tree, chunkSet);
#if DEBUG
var xform = transform.GetComponent(uid);
var xform = _xformQuery.GetComponent(uid);
if (chunkLocation is MapChunkLocation)
DebugTools.Assert(xform.GridUid == null || xform.GridUid == uid);
else if (chunkLocation is GridChunkLocation)
@@ -649,51 +646,55 @@ internal sealed partial class PvsSystem : EntitySystem
}
}
private bool AddToChunkSetRecursively(in EntityUid uid, uint visMask, RobustTree<EntityUid> tree, Dictionary<EntityUid, MetaDataComponent> set, EntityQuery<TransformComponent> transform,
EntityQuery<MetaDataComponent> metadata)
private bool AddToChunkSetRecursively(in EntityUid uid, in NetEntity netEntity, uint visMask, RobustTree<NetEntity> tree, Dictionary<NetEntity, MetaDataComponent> set)
{
if (set.ContainsKey(uid))
if (set.ContainsKey(netEntity))
return true;
var mComp = metadata.GetComponent(uid);
var mComp = _metaQuery.GetComponent(uid);
// TODO: Don't need to know about parents so no longer need to use bool for this method.
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
if ((visMask & mComp.VisibilityMask) != mComp.VisibilityMask)
return false;
var xform = transform.GetComponent(uid);
var xform = _xformQuery.GetComponent(uid);
// is this a map or grid?
var isRoot = !xform.ParentUid.IsValid() || uid == xform.GridUid;
if (isRoot)
{
DebugTools.Assert(_mapManager.IsGrid(uid) || _mapManager.IsMap(uid));
tree.Set(uid);
set.Add(uid, mComp);
tree.Set(netEntity);
set.Add(netEntity, mComp);
return true;
}
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
var parent = xform.ParentUid;
if (!set.ContainsKey(parent) && //was the parent not yet added to toSend?
!AddToChunkSetRecursively(in parent, visMask, tree, set, transform, metadata)) //did we just fail to add the parent?
var parentNetEntity = _metaQuery.GetComponent(parent).NetEntity;
// TODO performance
// AddToChunkSetRecursively will result in a redundant set.ContainsKey() check.
// This can probably be avoided somehow
if (!set.ContainsKey(parentNetEntity) && //was the parent not yet added to toSend?
!AddToChunkSetRecursively(in parent, in parentNetEntity, visMask, tree, set)) //did we just fail to add the parent?
{
return false; //we failed? suppose we dont get added either
}
//i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles
tree.Set(uid, parent);
set.Add(uid, mComp);
tree.Set(netEntity, parentNetEntity);
set.Add(netEntity, mComp);
return true;
}
internal (List<EntityState>? updates, List<EntityUid>? deletions, List<EntityUid>? leftPvs, GameTick fromTick)
internal (List<EntityState>? updates, List<NetEntity>? deletions, List<NetEntity>? leftPvs, GameTick fromTick)
CalculateEntityStates(IPlayerSession session,
GameTick fromTick,
GameTick toTick,
EntityQuery<MetaDataComponent> mQuery,
EntityQuery<TransformComponent> tQuery,
(Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] chunks,
(Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] chunks,
HashSet<int> visibleChunks,
EntityUid[] viewers)
{
@@ -712,7 +713,6 @@ internal sealed partial class PvsSystem : EntitySystem
throw new Exception("Encountered non-empty object inside of _visSetPool. Was the same object returned to the pool more than once?");
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
var entStateCount = 0;
var stack = _stackPool.Get();
@@ -726,14 +726,15 @@ internal sealed partial class PvsSystem : EntitySystem
// Each root nodes should simply be a map or a grid entity.
DebugTools.Assert(cache.Value.tree.RootNodes.Count == 1,
$"Root node count is {cache.Value.tree.RootNodes.Count} instead of 1. Session: {session}");
var ent = cache.Value.tree.RootNodes.FirstOrDefault();
var nent = cache.Value.tree.RootNodes.FirstOrDefault();
var ent = GetEntity(nent);
DebugTools.Assert(Exists(ent), $"Root node does not exist. Node {ent}. Session: {session}");
DebugTools.Assert(HasComp<MapComponent>(ent) || HasComp<MapGridComponent>(ent));
#endif
foreach (var rootNode in cache.Value.tree.RootNodes)
{
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, cache.Value.metadata, stack, in fromTick,
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, stack, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
}
@@ -742,8 +743,9 @@ internal sealed partial class PvsSystem : EntitySystem
var globalEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
while (globalEnumerator.MoveNext())
{
var uid = globalEnumerator.Current;
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
var netEntity = globalEnumerator.Current;
var uid = GetEntity(netEntity);
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
globalEnumerator.Dispose();
@@ -752,8 +754,9 @@ internal sealed partial class PvsSystem : EntitySystem
var globalRecursiveEnumerator = _entityPvsCollection.GlobalRecursiveOverridesEnumerator;
while (globalRecursiveEnumerator.MoveNext())
{
var uid = globalRecursiveEnumerator.Current;
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
var netEntity = globalRecursiveEnumerator.Current;
var uid = GetEntity(netEntity);
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
}
globalRecursiveEnumerator.Dispose();
@@ -761,15 +764,16 @@ internal sealed partial class PvsSystem : EntitySystem
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
while (localEnumerator.MoveNext())
{
var uid = localEnumerator.Current;
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
var netEntity = localEnumerator.Current;
var uid = GetEntity(netEntity);
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
localEnumerator.Dispose();
foreach (var viewerEntity in viewers)
{
RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
@@ -777,27 +781,32 @@ internal sealed partial class PvsSystem : EntitySystem
RaiseLocalEvent(ref expandEvent);
foreach (var entityUid in expandEvent.Entities)
{
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
foreach (var entityUid in expandEvent.RecursiveEntities)
{
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
}
var entityStates = new List<EntityState>(entStateCount);
foreach (var (uid, visiblity) in visibleEnts)
foreach (var (netEntity, visiblity) in visibleEnts)
{
var uid = GetEntity(netEntity);
#if DEBUG
// if an entity is visible, its parents should always be visible.
DebugTools.Assert((tQuery.GetComponent(uid).ParentUid is not { Valid: true } parent) || visibleEnts.ContainsKey(parent),
DebugTools.Assert((_xformQuery.GetComponent(uid).ParentUid is not { Valid: true } parent) ||
visibleEnts.ContainsKey(_metaQuery.GetComponent(parent).NetEntity),
$"Attempted to send an entity without sending it's parents. Entity: {ToPrettyString(uid)}.");
#endif
if (sessionData.RequestedFull)
{
entityStates.Add(GetFullEntityState(session, uid, mQuery.GetComponent(uid)));
entityStates.Add(GetFullEntityState(session, uid, _metaQuery.GetComponent(uid)));
continue;
}
@@ -805,8 +814,8 @@ internal sealed partial class PvsSystem : EntitySystem
continue;
var entered = visiblity == PvsEntityVisibility.Entered;
var entFromTick = entered ? lastSeen.GetValueOrDefault(uid) : fromTick;
var state = GetEntityState(session, uid, entFromTick, mQuery.GetComponent(uid));
var entFromTick = entered ? lastSeen.GetValueOrDefault(netEntity) : fromTick;
var state = GetEntityState(session, uid, entFromTick, _metaQuery.GetComponent(uid));
if (entered || !state.Empty)
entityStates.Add(state);
@@ -850,31 +859,30 @@ internal sealed partial class PvsSystem : EntitySystem
/// Figure out what entities are no longer visible to the client. These entities are sent reliably to the client
/// in a separate net message.
/// </summary>
private List<EntityUid>? ProcessLeavePVS(
Dictionary<EntityUid, PvsEntityVisibility> visibleEnts,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent)
private List<NetEntity>? ProcessLeavePVS(
Dictionary<NetEntity, PvsEntityVisibility> visibleEnts,
Dictionary<NetEntity, PvsEntityVisibility>? lastSent)
{
if (lastSent == null)
return null;
var leftView = new List<EntityUid>();
foreach (var uid in lastSent.Keys)
var leftView = new List<NetEntity>();
foreach (var netEntity in lastSent.Keys)
{
if (!visibleEnts.ContainsKey(uid))
leftView.Add(uid);
if (!visibleEnts.ContainsKey(netEntity))
leftView.Add(netEntity);
}
return leftView.Count > 0 ? leftView : null;
}
private void RecursivelyAddTreeNode(in EntityUid nodeIndex,
RobustTree<EntityUid> tree,
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
Dictionary<EntityUid, PvsEntityVisibility> toSend,
Dictionary<EntityUid, GameTick> lastSeen,
Dictionary<EntityUid, MetaDataComponent> metaDataCache,
Stack<EntityUid> stack,
private void RecursivelyAddTreeNode(in NetEntity nodeIndex,
RobustTree<NetEntity> tree,
Dictionary<NetEntity, PvsEntityVisibility>? lastAcked,
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, PvsEntityVisibility> toSend,
Dictionary<NetEntity, GameTick> lastSeen,
Stack<NetEntity> stack,
in GameTick fromTick,
ref int newEntityCount,
ref int enteredEntityCount,
@@ -899,7 +907,8 @@ internal sealed partial class PvsSystem : EntitySystem
if (!shouldAdd)
continue;
AddToSendSet(in currentNodeIndex, metaDataCache[currentNodeIndex], toSend, fromTick, in entered, ref entStateCount);
var entity = GetEntity(currentNodeIndex);
AddToSendSet(in currentNodeIndex, _metaQuery.GetComponent(entity), toSend, fromTick, in entered, ref entStateCount);
}
var node = tree[currentNodeIndex];
@@ -914,12 +923,10 @@ internal sealed partial class PvsSystem : EntitySystem
}
public bool RecursivelyAddOverride(in EntityUid uid,
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
Dictionary<EntityUid, PvsEntityVisibility> toSend,
Dictionary<EntityUid, GameTick> lastSeen,
in EntityQuery<MetaDataComponent> metaQuery,
in EntityQuery<TransformComponent> transQuery,
Dictionary<NetEntity, PvsEntityVisibility>? lastAcked,
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, PvsEntityVisibility> toSend,
Dictionary<NetEntity, GameTick> lastSeen,
in GameTick fromTick,
ref int newEntityCount,
ref int enteredEntityCount,
@@ -933,25 +940,31 @@ internal sealed partial class PvsSystem : EntitySystem
if (!uid.IsValid())
return false;
var xform = transQuery.GetComponent(uid);
var xform = _xformQuery.GetComponent(uid);
var parent = xform.ParentUid;
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in metaQuery, in transQuery, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget))
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget,
in enteredEntityBudget))
{
return false;
}
var metadata = _metaQuery.GetComponent(uid);
var netEntity = GetNetEntity(uid, metadata);
//did we already get added?
// Note that we check this AFTER adding parents. This is because while this entity may already have been added
// to the toSend set, it doesn't guarantee that its parents have been. E.g., if a player ghost just teleported
// to follow a far away entity, the player's own entity is still being sent, but we need to ensure that we also
// send the new parents, which may otherwise be delayed because of the PVS budget..
if (!toSend.ContainsKey(uid))
if (!toSend.ContainsKey(netEntity))
{
// TODO PERFORMANCE.
// ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this
// point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this
// is just the player's own entity + maybe a singularity. So currently not all that performance intensive.
var (entered, _) = ProcessEntry(in uid, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
var (entered, _) = ProcessEntry(in netEntity, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in netEntity, _metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
}
if (addChildren)
@@ -964,10 +977,10 @@ internal sealed partial class PvsSystem : EntitySystem
}
private void RecursivelyAddChildren(TransformComponent xform,
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
Dictionary<EntityUid, PvsEntityVisibility> toSend,
Dictionary<EntityUid, GameTick> lastSeen,
Dictionary<NetEntity, PvsEntityVisibility>? lastAcked,
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, PvsEntityVisibility> toSend,
Dictionary<NetEntity, GameTick> lastSeen,
in GameTick fromTick,
ref int newEntityCount,
ref int enteredEntityCount,
@@ -980,12 +993,15 @@ internal sealed partial class PvsSystem : EntitySystem
if (!_xformQuery.TryGetComponent(child, out var childXform))
continue;
if (!toSend.ContainsKey(child))
var metadata = _metaQuery.GetComponent(child);
var childNetEntity = GetNetEntity(child, metadata);
if (!toSend.ContainsKey(childNetEntity))
{
var (entered, _) = ProcessEntry(in child, lastAcked, lastSent, lastSeen, ref newEntityCount,
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, ref newEntityCount,
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in child, _metaQuery.GetComponent(child), toSend, fromTick, in entered, ref entStateCount);
AddToSendSet(in childNetEntity, metadata, toSend, fromTick, in entered, ref entStateCount);
}
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
@@ -993,19 +1009,20 @@ internal sealed partial class PvsSystem : EntitySystem
}
}
private (bool Entered, bool ShouldAdd) ProcessEntry(in EntityUid uid,
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
Dictionary<EntityUid, GameTick> lastSeen,
private (bool Entered, bool ShouldAdd) ProcessEntry(
in NetEntity netEntity,
Dictionary<NetEntity, PvsEntityVisibility>? lastAcked,
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, GameTick> lastSeen,
ref int newEntityCount,
ref int enteredEntityCount,
in int newEntityBudget,
in int enteredEntityBudget)
{
var enteredSinceLastSent = lastSent == null || !lastSent.ContainsKey(uid);
var enteredSinceLastSent = lastSent == null || !lastSent.ContainsKey(netEntity);
var entered = enteredSinceLastSent || // OR, entered since last ack:
lastAcked == null || !lastAcked.ContainsKey(uid);
lastAcked == null || !lastAcked.ContainsKey(netEntity);
// If the entity is entering, but we already sent this entering entity in the last message, we won't add it to
// the budget. Chances are the packet will arrive in a nice and orderly fashion, and the client will stick to
@@ -1020,25 +1037,25 @@ internal sealed partial class PvsSystem : EntitySystem
return (entered, false);
enteredEntityCount++;
if (!lastSeen.ContainsKey(uid))
if (!lastSeen.ContainsKey(netEntity))
newEntityCount++;
}
return (entered, true);
}
private void AddToSendSet(in EntityUid uid, MetaDataComponent metaDataComponent, Dictionary<EntityUid, PvsEntityVisibility> toSend, GameTick fromTick, in bool entered, ref int entStateCount)
private void AddToSendSet(in NetEntity netEntity, MetaDataComponent metaDataComponent, Dictionary<NetEntity, PvsEntityVisibility> toSend, GameTick fromTick, in bool entered, ref int entStateCount)
{
if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating)
{
var rep = new EntityStringRepresentation(uid, metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
var rep = new EntityStringRepresentation(GetEntity(netEntity), metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
return;
}
if (entered)
{
toSend.Add(uid, PvsEntityVisibility.Entered);
toSend.Add(netEntity, PvsEntityVisibility.Entered);
entStateCount++;
return;
}
@@ -1046,22 +1063,22 @@ internal sealed partial class PvsSystem : EntitySystem
if (metaDataComponent.EntityLastModifiedTick <= fromTick)
{
//entity has been sent before and hasnt been updated since
toSend.Add(uid, PvsEntityVisibility.StayedUnchanged);
toSend.Add(netEntity, PvsEntityVisibility.StayedUnchanged);
return;
}
//add us
toSend.Add(uid, PvsEntityVisibility.StayedChanged);
toSend.Add(netEntity, PvsEntityVisibility.StayedChanged);
entStateCount++;
}
/// <summary>
/// Gets all entity states that have been modified after and including the provided tick.
/// </summary>
public (List<EntityState>?, List<EntityUid>?, GameTick fromTick) GetAllEntityStates(ICommonSession? player, GameTick fromTick, GameTick toTick)
public (List<EntityState>?, List<NetEntity>?, GameTick fromTick) GetAllEntityStates(ICommonSession? player, GameTick fromTick, GameTick toTick)
{
List<EntityState>? stateEntities;
var toSend = _uidSetPool.Get();
var toSend = new HashSet<EntityUid>();
DebugTools.Assert(toSend.Count == 0);
bool enumerateAll = false;
@@ -1096,7 +1113,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (state.Empty)
{
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
Tick: {fromTick}--{_gameTiming.CurTick}
Entity: {ToPrettyString(uid)}
Last modified: {md.EntityLastModifiedTick}
@@ -1163,7 +1180,6 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
}
}
_uidSetPool.Return(toSend);
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
if (stateEntities.Count == 0)
@@ -1221,7 +1237,8 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
}
DebugTools.Assert(meta.EntityLastModifiedTick >= meta.LastComponentRemoved);
var entState = new EntityState(entityUid, changed, meta.EntityLastModifiedTick, netComps);
DebugTools.Assert(GetEntity(meta.NetEntity) == entityUid);
var entState = new EntityState(meta.NetEntity, changed, meta.EntityLastModifiedTick, netComps);
return entState;
}
@@ -1253,7 +1270,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
netComps.Add(netId);
}
var entState = new EntityState(entityUid, changed, meta.EntityLastModifiedTick, netComps);
var entState = new EntityState(meta.NetEntity, changed, meta.EntityLastModifiedTick, netComps);
return entState;
}
@@ -1263,14 +1280,13 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
if (session.Status != SessionStatus.InGame)
return Array.Empty<EntityUid>();
var viewers = _uidSetPool.Get();
var viewers = new HashSet<EntityUid>();
if (session.AttachedEntity != null)
{
// Fast path
if (session is IPlayerSession { ViewSubscriptionCount: 0 })
{
_uidSetPool.Return(viewers);
return new[] { session.AttachedEntity.Value };
}
@@ -1288,7 +1304,6 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
var viewerArray = viewers.ToArray();
_uidSetPool.Return(viewers);
return viewerArray;
}
@@ -1336,23 +1351,23 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
/// <summary>
/// All <see cref="EntityUid"/>s that this session saw during the last <see cref="DirtyBufferSize"/> ticks.
/// </summary>
public readonly OverflowDictionary<GameTick, Dictionary<EntityUid, PvsEntityVisibility>> SentEntities = new(DirtyBufferSize);
public readonly OverflowDictionary<GameTick, Dictionary<NetEntity, PvsEntityVisibility>> SentEntities = new(DirtyBufferSize);
/// <summary>
/// The most recently acked entities
/// </summary>
public (GameTick Tick, Dictionary<EntityUid, PvsEntityVisibility> Data)? LastAcked;
public (GameTick Tick, Dictionary<NetEntity, PvsEntityVisibility> Data)? LastAcked;
/// <summary>
/// Stores the last tick at which a given entity was acked by a player. Used to avoid re-sending the whole entity
/// state when an item re-enters PVS.
/// </summary>
public readonly Dictionary<EntityUid, GameTick> LastSeenAt = new();
public readonly Dictionary<NetEntity, GameTick> LastSeenAt = new();
/// <summary>
/// <see cref="SentEntities"/> overflow in case a player's last ack is more than <see cref="DirtyBufferSize"/> ticks behind the current tick.
/// </summary>
public (GameTick Tick, Dictionary<EntityUid, PvsEntityVisibility> SentEnts)? Overflow;
public (GameTick Tick, Dictionary<NetEntity, PvsEntityVisibility> SentEnts)? Overflow;
/// <summary>
/// If true, the client has explicitly requested a full state. Unlike the first state, we will send them

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!;
@@ -62,7 +63,7 @@ namespace Robust.Server.GameStates
public ushort TransformNetId { get; set; }
public Action<ICommonSession, GameTick>? ClientAck { get; set; }
public Action<ICommonSession, GameTick, EntityUid?>? ClientRequestFull { get; set; }
public Action<ICommonSession, GameTick, NetEntity?>? ClientRequestFull { get; set; }
public void PostInject()
{
@@ -133,7 +134,7 @@ namespace Robust.Server.GameStates
if (!_playerManager.TryGetSessionById(msg.MsgChannel.UserId, out var session))
return;
EntityUid? ent = msg.MissingEntity.IsValid() ? msg.MissingEntity : null;
NetEntity? ent = msg.MissingEntity.IsValid() ? msg.MissingEntity : null;
ClientRequestFull?.Invoke(session, msg.Tick, ent);
}
@@ -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
{
@@ -238,7 +245,7 @@ namespace Robust.Server.GameStates
{
public HashSet<int>[] PlayerChunks;
public EntityUid[][] ViewerEntities;
public (Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] ChunkCache;
public (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] ChunkCache;
}
private PvsData? GetPVSData(IPlayerSession[] players)
@@ -248,13 +255,11 @@ namespace Robust.Server.GameStates
var chunksCount = chunks.Count;
var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize);
var chunkCache =
new (Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[chunksCount];
new (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[chunksCount];
// Update the reused trees sequentially to avoid having to lock the dictionary per chunk.
var reuse = ArrayPool<bool>.Shared.Rent(chunksCount);
var transformQuery = _entityManager.GetEntityQuery<TransformComponent>();
var metadataQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
Parallel.For(0, chunkBatches,
new ParallelOptions { MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount },
i =>
@@ -265,8 +270,7 @@ namespace Robust.Server.GameStates
for (var j = start; j < end; ++j)
{
var (visMask, chunkIndexLocation) = chunks[j];
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery,
out var chunk);
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, out var chunk);
chunkCache[j] = chunk;
#if DEBUG
@@ -276,7 +280,8 @@ namespace Robust.Server.GameStates
// Each root nodes should simply be a map or a grid entity.
DebugTools.Assert(chunk.Value.tree.RootNodes.Count == 1,
$"Root node count is {chunk.Value.tree.RootNodes.Count} instead of 1.");
var ent = chunk.Value.tree.RootNodes.FirstOrDefault();
var nent = chunk.Value.tree.RootNodes.FirstOrDefault();
var ent = _entityManager.GetEntity(nent);
DebugTools.Assert(_entityManager.EntityExists(ent), $"Root node does not exist. Node {ent}.");
DebugTools.Assert(_entityManager.HasComponent<MapComponent>(ent)
|| _entityManager.HasComponent<MapGridComponent>(ent));
@@ -306,9 +311,9 @@ namespace Robust.Server.GameStates
var channel = session.ConnectedClient;
var sessionData = _pvs.PlayerData[session];
var lastAck = sessionData.LastReceivedAck;
List<EntityUid>? leftPvs = null;
List<NetEntity>? leftPvs = null;
List<EntityState>? entStates;
List<EntityUid>? deletions;
List<NetEntity>? deletions;
GameTick fromTick;
DebugTools.Assert(_pvs.CullingEnabled == (pvsData != null));
@@ -318,8 +323,6 @@ namespace Robust.Server.GameStates
session,
lastAck,
_gameTiming.CurTick,
mQuery,
tQuery,
pvsData.Value.ChunkCache,
pvsData.Value.PlayerChunks[i],
pvsData.Value.ViewerEntities[i]);
@@ -373,5 +376,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

@@ -130,7 +130,7 @@ namespace Robust.Server.Physics
var msg = new ChunkSplitDebugMessage
{
Grid = uid,
Grid = GetNetEntity(uid),
};
foreach (var (index, group) in _nodes[uid])

View File

@@ -21,9 +21,9 @@ public sealed class JointSystem : SharedJointSystem
foreach (var (id, joint) in component.Joints)
{
states.Add(id, joint.GetState());
states.Add(id, joint.GetState(EntityManager));
}
args.State = new JointComponentState(component.Relay, states);
args.State = new JointComponentState(GetNetEntity(component.Relay), states);
}
}

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;
@@ -98,8 +98,10 @@ namespace Robust.Server.Placement
return;
//TODO: Distance check, so you can't place things off of screen.
// I don't think that's this manager's biggest problem
var coordinates = msg.EntityCoordinates;
var netCoordinates = msg.NetCoordinates;
var coordinates = _entityManager.GetCoordinates(netCoordinates);
if (!coordinates.IsValid(_entityManager))
{
@@ -177,7 +179,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;
@@ -211,17 +213,19 @@ namespace Robust.Server.Placement
private void HandleEntRemoveReq(MsgPlacement msg)
{
//TODO: Some form of admin check
if (!_entityManager.EntityExists(msg.EntityUid))
var entity = _entityManager.GetEntity(msg.EntityUid);
if (!_entityManager.EntityExists(entity))
return;
var placementEraseEvent = new PlacementEntityEvent(msg.EntityUid, _entityManager.GetComponent<TransformComponent>(msg.EntityUid).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
var placementEraseEvent = new PlacementEntityEvent(entity, _entityManager.GetComponent<TransformComponent>(entity).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
_entityManager.DeleteEntity(msg.EntityUid);
_entityManager.DeleteEntity(entity);
}
private void HandleRectRemoveReq(MsgPlacement msg)
{
EntityCoordinates start = msg.EntityCoordinates;
EntityCoordinates start = _entityManager.GetCoordinates(msg.NetCoordinates);
Vector2 rectSize = msg.RectSize;
foreach (EntityUid entity in EntitySystem.Get<EntityLookupSystem>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
new Box2(start.Position, start.Position + rectSize)))

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;
@@ -209,7 +210,7 @@ namespace Robust.Server.Player
{
PlayerState.Status = Status;
PlayerState.Name = Name;
PlayerState.ControlledEntity = AttachedEntity;
PlayerState.ControlledEntity = IoCManager.Resolve<IEntityManager>().GetNetEntity(AttachedEntity);
_playerManager.Dirty();
}

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

@@ -125,8 +125,10 @@ namespace Robust.Server.ViewVariables
case ViewVariablesComponentSelector componentSelector:
{
var compType = _reflectionManager.GetType(componentSelector.ComponentType);
var entity = _entityManager.GetEntity(componentSelector.Entity);
if (compType == null ||
!_entityManager.TryGetComponent(componentSelector.Entity, compType, out var component))
!_entityManager.TryGetComponent(entity, compType, out var component))
{
Deny(ViewVariablesResponseCode.NoObject);
return;
@@ -137,7 +139,9 @@ namespace Robust.Server.ViewVariables
}
case ViewVariablesEntitySelector entitySelector:
{
if (!_entityManager.EntityExists(entitySelector.Entity))
var entity = _entityManager.GetEntity(entitySelector.Entity);
if (!_entityManager.EntityExists(entity))
{
Deny(ViewVariablesResponseCode.NoObject);
return;

View File

@@ -14,7 +14,8 @@ namespace Robust.Server.ViewVariables.Traits
public ViewVariablesTraitEntity(IViewVariablesSession session) : base(session)
{
_entity = (EntityUid) Session.Object;
var netEntity = (NetEntity) Session.Object;
_entity = IoCManager.Resolve<IEntityManager>().GetEntity(netEntity);
}
public override ViewVariablesBlob? DataRequest(ViewVariablesRequest viewVariablesRequest)

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
@@ -27,10 +28,11 @@ namespace Robust.Server.ViewVariables.Traits
if (messageRequestMeta is ViewVariablesRequestMembers)
{
var members = new List<(MemberData mData, MemberInfo mInfo)>();
var obj = Session.Object;
var objType = Session.ObjectType;
foreach (var property in Session.ObjectType.GetAllProperties())
foreach (var property in objType.GetAllProperties())
{
if (!ViewVariablesUtility.TryGetViewVariablesAccess(property, out var access))
{
continue;
@@ -53,7 +55,7 @@ namespace Robust.Server.ViewVariables.Traits
_members.Add(property);
}
foreach (var field in Session.ObjectType.GetAllFields())
foreach (var field in objType.GetAllFields())
{
if (!ViewVariablesUtility.TryGetViewVariablesAccess(field, out var access))
{
@@ -66,7 +68,7 @@ namespace Robust.Server.ViewVariables.Traits
Name = field.Name,
Type = field.FieldType.AssemblyQualifiedName,
TypePretty = PrettyPrint.PrintUserFacingTypeShort(field.FieldType, 2),
Value = field.GetValue(Session.Object),
Value = field.GetValue(obj),
PropertyIndex = _members.Count
}, field));

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