mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6133fe0808 | ||
|
|
909fd326a0 | ||
|
|
876de4065a | ||
|
|
60e159f0d0 | ||
|
|
80f3aae30c | ||
|
|
98b1862433 | ||
|
|
d2311c193f | ||
|
|
f05ed96461 | ||
|
|
dc23dfaf4d | ||
|
|
62315f7c2e | ||
|
|
b2d121e780 | ||
|
|
fb4b029122 | ||
|
|
66239d23ea | ||
|
|
dbb45f1c13 | ||
|
|
6284e16b64 | ||
|
|
f6c55085fe | ||
|
|
30eafd26e7 | ||
|
|
63423d96b4 | ||
|
|
474334aff2 | ||
|
|
be102f86bf | ||
|
|
d7962c7190 | ||
|
|
7fe9385c3b | ||
|
|
a9d9d1348a | ||
|
|
4eaf624555 | ||
|
|
e37c131fb4 | ||
|
|
9df4606492 | ||
|
|
3be8070274 | ||
|
|
8a440d705f | ||
|
|
5849474022 | ||
|
|
b7d67c0ece | ||
|
|
b04cf71bc0 | ||
|
|
ea87df649a | ||
|
|
dab7a9112f | ||
|
|
d3dc89832a | ||
|
|
3ade9ca447 | ||
|
|
149e9a2613 | ||
|
|
d164148ce2 | ||
|
|
d898b52449 | ||
|
|
dcf7a1e580 | ||
|
|
65c6bb74eb | ||
|
|
d6d88bea91 | ||
|
|
d6467f768a |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -25,4 +25,7 @@
|
||||
|
||||
<!-- analyzer -->
|
||||
<Import Project="Robust.Analyzers.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
|
||||
|
||||
<!-- serialization generator -->
|
||||
<Import Project="Robust.Serialization.Generator.targets" />
|
||||
</Project>
|
||||
|
||||
5
MSBuild/Robust.Serialization.Generator.targets
Normal file
5
MSBuild/Robust.Serialization.Generator.targets
Normal file
@@ -0,0 +1,5 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
116
RELEASE-NOTES.md
116
RELEASE-NOTES.md
@@ -54,6 +54,118 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
* Data definitions must now be partial, their data fields must not be readonly and their data field properties must have a setter.
|
||||
|
||||
### Internal
|
||||
|
||||
* Copying data definitions through the serialization manager is now faster and consumes less memory.
|
||||
|
||||
|
||||
## 148.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add recursive PVS overrides and remove IsOverride()
|
||||
|
||||
|
||||
## 148.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Happy eyeballs delay can be configured.
|
||||
* Added more colors.
|
||||
* Allow pre-startup components to be shut down.
|
||||
* Added tile texture reload command.
|
||||
* Add implementation of Random.Pick(ValueList<T> ..).
|
||||
* Add IntegrationInstance fields for common dependencies.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Prevent invalid prototypes from being spawned.
|
||||
* Change default value of EntityLastModifiedTick from zero to one.
|
||||
* Make DiscordRichPresence icon CVars server-side with replication.
|
||||
|
||||
|
||||
## 148.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* `SpinBox.LineEditControl` exposes the underlying `LineEdit`.
|
||||
* Add VV attributes to various fields across overlay and sessions.
|
||||
* Add IsPaused to EntityManager to check if an entity is paused.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix SetActiveTheme not updating the theme.
|
||||
|
||||
|
||||
## 148.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added IgnoreUIChecksComponent that lets entities ignore bound user interface range checks which would normally close the UI.
|
||||
* Add support for F16-F24 keybinds.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix gamestate bug where PVS is disabled.
|
||||
|
||||
### Other
|
||||
|
||||
* EntityQuery.HasComponent override for nullable entity uids.
|
||||
|
||||
|
||||
## 148.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Several NuGet dependencies are now private assets.
|
||||
* Added `IViewportControl.PixelToMap()` and `PixelToMapEvent`. These are variants of the existing screen-to-map functions that should account for distortion effects.
|
||||
|
||||
### New features
|
||||
|
||||
* Added several new rich-text tags, including italic and bold-italic.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed log messages for unknown components not working due to threaded IoC issues.
|
||||
* Replay recordings no longer record invalid prototype uploads.
|
||||
|
||||
|
||||
## 147.0.0
|
||||
|
||||
### Breaking changes
|
||||
@@ -204,7 +316,7 @@ END TEMPLATE-->
|
||||
* `IHttpClientHolder` holds a shared `HttpClient` for use by content. It has Happy Eyeballs fixed and an appropriate `User-Agent`.
|
||||
* Added `DataNode.ToString()`. Makes it easier to save yaml files and debug code.
|
||||
* Added some cvars to modify discord rich presence icons.
|
||||
* .ogg files now read the `Artist` and `Title` tags and make them available via new fields in `AudioStream`.
|
||||
* .ogg files now read the `Artist` and `Title` tags and make them available via new fields in `AudioStream`.
|
||||
* The default fragment shaders now have access to the local light level (`lowp vec3 lightSample`).
|
||||
* Added `IPrototypeManager.ValidateAllPrototypesSerializable()`, which can be used to check that all currently loaded prototypes can be serialised & deserialised.
|
||||
|
||||
@@ -213,7 +325,7 @@ END TEMPLATE-->
|
||||
* Fix certain debug commands and tools crashing on non-SS14 RobustToolbox games due to a missing font.
|
||||
* Discord rich presence strings are now truncated if they are too long.
|
||||
* Fixed a couple of broadphase/entity-lookup update bugs that were affecting containers and entities attached to other (non-grid/map) entities.
|
||||
* Fixed `INetChannel.Disconnect()` not properly disconnecting clients in integration tests.
|
||||
* Fixed `INetChannel.Disconnect()` not properly disconnecting clients in integration tests.
|
||||
|
||||
### Other
|
||||
|
||||
|
||||
@@ -558,3 +558,6 @@ cmd-vfs_ls-help = Usage: vfs_list <path>
|
||||
|
||||
cmd-vfs_ls-err-args = Need exactly 1 argument.
|
||||
cmd-vfs_ls-hint-path = <path>
|
||||
|
||||
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites
|
||||
cmd-reloadtiletextures-help = Usage: reloadtiletextures
|
||||
|
||||
@@ -18,6 +18,15 @@ input-key-F12 = F12
|
||||
input-key-F13 = F13
|
||||
input-key-F14 = F14
|
||||
input-key-F15 = F15
|
||||
input-key-F16 = F16
|
||||
input-key-F17 = F17
|
||||
input-key-F18 = F18
|
||||
input-key-F19 = F19
|
||||
input-key-F20 = F20
|
||||
input-key-F21 = F21
|
||||
input-key-F22 = F22
|
||||
input-key-F23 = F23
|
||||
input-key-F24 = F24
|
||||
input-key-Pause = Pause
|
||||
input-key-Left = Left
|
||||
input-key-Up = Up
|
||||
|
||||
@@ -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.
|
||||
|
||||
293
Robust.Analyzers/DataDefinitionAnalyzer.cs
Normal file
293
Robust.Analyzers/DataDefinitionAnalyzer.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute";
|
||||
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
|
||||
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
|
||||
Diagnostics.IdDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} is a DataDefinition but is not partial.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
"Make sure to mark any type that is a data definition as partial."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
|
||||
Diagnostics.IdNestedDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} contains nested data definition {1} but is not partial.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
"Make sure to mark any type containing a nested data definition as partial."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
Diagnostics.IdDataFieldWritable,
|
||||
"Data field must not be readonly",
|
||||
"Data field {0} in data definition {1} is readonly.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
"Make sure to remove the readonly modifier."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
Diagnostics.IdDataFieldPropertyWritable,
|
||||
"Data field property must have a setter",
|
||||
"Data field property {0} in data definition {1} does not have a setter.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
"Make sure to add a setter."
|
||||
);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
|
||||
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
|
||||
);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
|
||||
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
|
||||
}
|
||||
|
||||
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not TypeDeclarationSyntax declaration)
|
||||
return;
|
||||
|
||||
var type = context.SemanticModel.GetDeclaredSymbol(declaration)!;
|
||||
if (!IsDataDefinition(type))
|
||||
return;
|
||||
|
||||
if (!IsPartial(declaration))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataDefinitionPartialRule, declaration.Keyword.GetLocation(), type.Name));
|
||||
}
|
||||
|
||||
var containingType = type.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
var containingTypeDeclaration = (TypeDeclarationSyntax) containingType.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (!IsPartial(containingTypeDeclaration))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(NestedDataDefinitionPartialRule, containingTypeDeclaration.Keyword.GetLocation(), containingType.Name, type.Name));
|
||||
}
|
||||
|
||||
containingType = containingType.ContainingType;
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not FieldDeclarationSyntax field)
|
||||
return;
|
||||
|
||||
var typeDeclaration = field.FirstAncestorOrSelf<TypeDeclarationSyntax>();
|
||||
if (typeDeclaration == null)
|
||||
return;
|
||||
|
||||
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
|
||||
if (!IsDataDefinition(type))
|
||||
return;
|
||||
|
||||
foreach (var variable in field.Declaration.Variables)
|
||||
{
|
||||
var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
|
||||
if (fieldSymbol == null)
|
||||
continue;
|
||||
|
||||
if (IsReadOnlyDataField(type, fieldSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not PropertyDeclarationSyntax property)
|
||||
return;
|
||||
|
||||
var typeDeclaration = property.FirstAncestorOrSelf<TypeDeclarationSyntax>();
|
||||
if (typeDeclaration == null)
|
||||
return;
|
||||
|
||||
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
|
||||
if (!IsDataDefinition(type) || type.IsRecord || type.IsValueType)
|
||||
return;
|
||||
|
||||
var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property);
|
||||
if (propertySymbol == null)
|
||||
return;
|
||||
|
||||
if (IsReadOnlyDataField(type, propertySymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
|
||||
{
|
||||
if (!IsDataField(field, out _, out _))
|
||||
return false;
|
||||
|
||||
return IsReadOnlyMember(type, field);
|
||||
}
|
||||
|
||||
private static bool IsPartial(TypeDeclarationSyntax type)
|
||||
{
|
||||
return type.Modifiers.IndexOf(SyntaxKind.PartialKeyword) != -1;
|
||||
}
|
||||
|
||||
private static bool IsDataDefinition(ITypeSymbol? type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return HasAttribute(type, DataDefinitionNamespace) ||
|
||||
IsImplicitDataDefinition(type);
|
||||
}
|
||||
|
||||
private static bool IsDataField(ISymbol member, out ITypeSymbol type, out AttributeData attribute)
|
||||
{
|
||||
// TODO data records and other attributes
|
||||
if (member is IFieldSymbol field)
|
||||
{
|
||||
foreach (var attr in field.GetAttributes())
|
||||
{
|
||||
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
|
||||
{
|
||||
type = field.Type;
|
||||
attribute = attr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (member is IPropertySymbol property)
|
||||
{
|
||||
foreach (var attr in property.GetAttributes())
|
||||
{
|
||||
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
|
||||
{
|
||||
type = property.Type;
|
||||
attribute = attr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type = null!;
|
||||
attribute = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool Inherits(ITypeSymbol type, string parent)
|
||||
{
|
||||
foreach (var baseType in GetBaseTypes(type))
|
||||
{
|
||||
if (baseType.ToDisplayString() == parent)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
|
||||
{
|
||||
if (member is IFieldSymbol field)
|
||||
{
|
||||
return field.IsReadOnly;
|
||||
}
|
||||
else if (member is IPropertySymbol property)
|
||||
{
|
||||
if (property.SetMethod == null)
|
||||
return true;
|
||||
|
||||
if (property.SetMethod.IsInitOnly)
|
||||
return type.IsReferenceType;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasAttribute(ITypeSymbol type, string attributeName)
|
||||
{
|
||||
foreach (var attribute in type.GetAttributes())
|
||||
{
|
||||
if (attribute.AttributeClass?.ToDisplayString() == attributeName)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsImplicitDataDefinition(ITypeSymbol type)
|
||||
{
|
||||
if (HasAttribute(type, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
|
||||
foreach (var baseType in GetBaseTypes(type))
|
||||
{
|
||||
if (HasAttribute(baseType, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var @interface in type.AllInterfaces)
|
||||
{
|
||||
if (IsImplicitDataDefinitionInterface(@interface))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsImplicitDataDefinitionInterface(ITypeSymbol @interface)
|
||||
{
|
||||
if (HasAttribute(@interface, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
|
||||
foreach (var subInterface in @interface.AllInterfaces)
|
||||
{
|
||||
if (HasAttribute(subInterface, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerable<ITypeSymbol> GetBaseTypes(ITypeSymbol type)
|
||||
{
|
||||
var baseType = type.BaseType;
|
||||
while (baseType != null)
|
||||
{
|
||||
yield return baseType;
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
168
Robust.Analyzers/DataDefinitionFixer.cs
Normal file
168
Robust.Analyzers/DataDefinitionFixer.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
|
||||
using static Robust.Analyzers.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
||||
public sealed class DefinitionFixer : CodeFixProvider
|
||||
{
|
||||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
|
||||
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
|
||||
);
|
||||
|
||||
public override Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
{
|
||||
foreach (var diagnostic in context.Diagnostics)
|
||||
{
|
||||
switch (diagnostic.Id)
|
||||
{
|
||||
case IdDataDefinitionPartial:
|
||||
return RegisterPartialTypeFix(context, diagnostic);
|
||||
case IdNestedDataDefinitionPartial:
|
||||
return RegisterPartialTypeFix(context, diagnostic);
|
||||
case IdDataFieldWritable:
|
||||
return RegisterDataFieldFix(context, diagnostic);
|
||||
case IdDataFieldPropertyWritable:
|
||||
return RegisterDataFieldPropertyFix(context, diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override FixAllProvider GetFixAllProvider()
|
||||
{
|
||||
return WellKnownFixAllProviders.BatchFixer;
|
||||
}
|
||||
|
||||
private static async Task RegisterPartialTypeFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().First();
|
||||
|
||||
if (token == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Make type partial",
|
||||
c => MakeDataDefinitionPartial(context.Document, token, c),
|
||||
"Make type partial"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task<Document> MakeDataDefinitionPartial(Document document, TypeDeclarationSyntax declaration, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
var token = SyntaxFactory.Token(PartialKeyword);
|
||||
var newDeclaration = declaration.AddModifiers(token);
|
||||
|
||||
root = root!.ReplaceNode(declaration, newDeclaration);
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var field = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<FieldDeclarationSyntax>().FirstOrDefault();
|
||||
|
||||
if (field == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Make data field writable",
|
||||
c => MakeFieldWritable(context.Document, field, c),
|
||||
"Make data field writable"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task RegisterDataFieldPropertyFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var property = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
|
||||
|
||||
if (property == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Make data field writable",
|
||||
c => MakePropertyWritable(context.Document, property, c),
|
||||
"Make data field writable"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task<Document> MakeFieldWritable(Document document, FieldDeclarationSyntax declaration, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
var token = declaration.Modifiers.First(t => t.IsKind(ReadOnlyKeyword));
|
||||
var newDeclaration = declaration.WithModifiers(declaration.Modifiers.Remove(token));
|
||||
|
||||
root = root!.ReplaceNode(declaration, newDeclaration);
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task<Document> MakePropertyWritable(Document document, PropertyDeclarationSyntax declaration, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
var newDeclaration = declaration;
|
||||
var privateSet = newDeclaration
|
||||
.AccessorList?
|
||||
.Accessors
|
||||
.FirstOrDefault(s => s.IsKind(SetAccessorDeclaration) || s.IsKind(InitAccessorDeclaration));
|
||||
|
||||
if (newDeclaration.AccessorList != null && privateSet != null)
|
||||
{
|
||||
newDeclaration = newDeclaration.WithAccessorList(
|
||||
newDeclaration.AccessorList.WithAccessors(
|
||||
newDeclaration.AccessorList.Accessors.Remove(privateSet)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
AccessorDeclarationSyntax setter;
|
||||
if (declaration.Modifiers.Any(m => m.IsKind(PrivateKeyword)))
|
||||
{
|
||||
setter = SyntaxFactory.AccessorDeclaration(
|
||||
SetAccessorDeclaration,
|
||||
default,
|
||||
default,
|
||||
SyntaxFactory.Token(SetKeyword),
|
||||
default,
|
||||
default,
|
||||
SyntaxFactory.Token(SemicolonToken)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
setter = SyntaxFactory.AccessorDeclaration(
|
||||
SetAccessorDeclaration,
|
||||
default,
|
||||
SyntaxFactory.TokenList(SyntaxFactory.Token(PrivateKeyword)),
|
||||
SyntaxFactory.Token(SetKeyword),
|
||||
default,
|
||||
default,
|
||||
SyntaxFactory.Token(SemicolonToken)
|
||||
);
|
||||
}
|
||||
|
||||
newDeclaration = newDeclaration.AddAccessorListAccessors(setter);
|
||||
|
||||
root = root!.ReplaceNode(declaration, newDeclaration);
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@ public static class Diagnostics
|
||||
public const string IdValueEventSubscribedByRef = "RA0014";
|
||||
public const string IdByRefEventRaisedByValue = "RA0015";
|
||||
public const string IdValueEventRaisedByRef = "RA0016";
|
||||
public const string IdDataDefinitionPartial = "RA0017";
|
||||
public const string IdNestedDataDefinitionPartial = "RA0018";
|
||||
public const string IdDataFieldWritable = "RA0019";
|
||||
public const string IdDataFieldPropertyWritable = "RA0020";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.UnitTesting.Server;
|
||||
namespace Robust.Benchmarks.EntityManager;
|
||||
|
||||
[Virtual]
|
||||
public class AddRemoveComponentBenchmark
|
||||
public partial class AddRemoveComponentBenchmark
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
@@ -48,7 +48,7 @@ public class AddRemoveComponentBenchmark
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed class A : Component
|
||||
public sealed partial class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.EntityManager;
|
||||
|
||||
public class ComponentIteratorBenchmark
|
||||
public partial class ComponentIteratorBenchmark
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
@@ -69,7 +69,7 @@ public class ComponentIteratorBenchmark
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed class A : Component
|
||||
public sealed partial class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Analyzers;
|
||||
@@ -9,7 +8,7 @@ using Robust.UnitTesting.Server;
|
||||
namespace Robust.Benchmarks.EntityManager;
|
||||
|
||||
[Virtual]
|
||||
public class GetComponentBenchmark
|
||||
public partial class GetComponentBenchmark
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
@@ -55,7 +54,7 @@ public class GetComponentBenchmark
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed class A : Component
|
||||
public sealed partial class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.UnitTesting.Server;
|
||||
namespace Robust.Benchmarks.EntityManager;
|
||||
|
||||
[Virtual]
|
||||
public class SpawnDeleteEntityBenchmark
|
||||
public partial class SpawnDeleteEntityBenchmark
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
@@ -56,7 +56,7 @@ public class SpawnDeleteEntityBenchmark
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed class A : Component
|
||||
public sealed partial class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
[DataDefinition]
|
||||
[Virtual]
|
||||
public class DataDefinitionWithString
|
||||
public partial class DataDefinitionWithString
|
||||
{
|
||||
[DataField("string")]
|
||||
public string StringField { get; init; } = default!;
|
||||
public string StringField { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed class SealedDataDefinitionWithString
|
||||
public sealed partial class SealedDataDefinitionWithString
|
||||
{
|
||||
[DataField("string")]
|
||||
public string StringField { get; init; } = default!;
|
||||
public string StringField { get; private set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -10,8 +11,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
|
||||
/// Arbitrarily large data definition for benchmarks.
|
||||
/// Taken from content.
|
||||
/// </summary>
|
||||
[Prototype("seed")]
|
||||
public sealed class SeedDataDefinition : IPrototype
|
||||
public sealed partial class SeedDataDefinition : Component
|
||||
{
|
||||
public const string Prototype = @"
|
||||
- type: seed
|
||||
@@ -106,7 +106,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public struct SeedChemQuantity
|
||||
public partial struct SeedChemQuantity
|
||||
{
|
||||
[DataField("Min")]
|
||||
public int Min;
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace Robust.Client
|
||||
deps.Register<IComponentFactory, ComponentFactory>();
|
||||
deps.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
deps.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
deps.Register<ClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
deps.Register<GameController, GameController>();
|
||||
deps.Register<IGameController, GameController>();
|
||||
deps.Register<IGameControllerInternal, GameController>();
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Physics;
|
||||
namespace Robust.Client.ComponentTrees;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class LightTreeComponent: Component, IComponentTreeComponent<PointLightComponent>
|
||||
public sealed partial class LightTreeComponent: Component, IComponentTreeComponent<PointLightComponent>
|
||||
{
|
||||
public DynamicTree<ComponentTreeEntry<PointLightComponent>> Tree { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Physics;
|
||||
namespace Robust.Client.ComponentTrees;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SpriteTreeComponent: Component, IComponentTreeComponent<SpriteComponent>
|
||||
public sealed partial class SpriteTreeComponent: Component, IComponentTreeComponent<SpriteComponent>
|
||||
{
|
||||
public DynamicTree<ComponentTreeEntry<SpriteComponent>> Tree { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mousePos = _eye.ScreenToMap(_input.MouseScreenPosition);
|
||||
var mousePos = _eye.PixelToMap(_input.MouseScreenPosition);
|
||||
|
||||
if (!_map.TryFindGridAt(mousePos, out var gridUid, out var grid))
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
var mouseSpot = _inputManager.MouseScreenPosition;
|
||||
var spot = _eyeManager.ScreenToMap(mouseSpot);
|
||||
var spot = _eyeManager.PixelToMap(mouseSpot);
|
||||
|
||||
if (!_mapManager.TryFindGridAt(spot, out var gridUid, out var grid))
|
||||
{
|
||||
|
||||
@@ -371,7 +371,7 @@ namespace Robust.Client.Debugging
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
|
||||
{
|
||||
var hoverBodies = new List<PhysicsComponent>();
|
||||
var bounds = Box2.UnitCentered.Translated(_eyeManager.ScreenToMap(mousePos.Position).Position);
|
||||
var bounds = Box2.UnitCentered.Translated(_eyeManager.PixelToMap(mousePos.Position).Position);
|
||||
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
|
||||
{
|
||||
@@ -404,7 +404,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Distance) != 0x0)
|
||||
{
|
||||
var mapPos = _eyeManager.ScreenToMap(mousePos);
|
||||
var mapPos = _eyeManager.PixelToMap(mousePos);
|
||||
|
||||
if (mapPos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
|
||||
/// Plays back <see cref="Animation"/>s on entities.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AnimationPlayerComponent : Component
|
||||
public sealed partial class AnimationPlayerComponent : Component
|
||||
{
|
||||
// TODO: Give this component a friend someday. Way too much content shit to change atm ._.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects;
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(GenericVisualizerSystem))]
|
||||
public sealed class GenericVisualizerComponent : Component
|
||||
public sealed partial class GenericVisualizerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a nested dictionary that maps appearance data keys -> sprite layer keys -> appearance data values -> layer data.
|
||||
|
||||
@@ -10,7 +10,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent, ComponentReference(typeof(SharedEyeComponent))]
|
||||
public sealed class EyeComponent : SharedEyeComponent
|
||||
public sealed partial class EyeComponent : SharedEyeComponent
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace Robust.Client.GameObjects;
|
||||
/// updated.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed class IconComponent : Component
|
||||
public sealed partial class IconComponent : Component
|
||||
{
|
||||
[IncludeDataField]
|
||||
public readonly SpriteSpecifier.Rsi Icon = default!;
|
||||
public SpriteSpecifier.Rsi Icon = default!;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Client.GameObjects
|
||||
/// Defines data fields used in the <see cref="InputSystem"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class InputComponent : Component
|
||||
public sealed partial class InputComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The context that will be made active for a client that attaches to this entity.
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedPointLightComponent))]
|
||||
public sealed class PointLightComponent : SharedPointLightComponent, IComponentTreeEntry<PointLightComponent>
|
||||
public sealed partial class PointLightComponent : SharedPointLightComponent, IComponentTreeEntry<PointLightComponent>
|
||||
{
|
||||
public EntityUid? TreeUid { get; set; }
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public interface IRenderableComponent : IComponent
|
||||
public partial interface IRenderableComponent : IComponent
|
||||
{
|
||||
int DrawDepth { get; set; }
|
||||
float Bottom { get; }
|
||||
|
||||
@@ -32,7 +32,7 @@ using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteS
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
|
||||
public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
|
||||
{
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
|
||||
public sealed class ClientUserInterfaceComponent : SharedUserInterfaceComponent
|
||||
public sealed partial class ClientUserInterfaceComponent : SharedUserInterfaceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
internal readonly Dictionary<Enum, PrototypeData> _interfaces = new();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Robust.Client.Graphics
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
private ISawmill _logMill = default!;
|
||||
|
||||
// We default to this when we get set to a null eye.
|
||||
private readonly FixedEye _defaultEye = new();
|
||||
@@ -53,6 +54,7 @@ namespace Robust.Client.Graphics
|
||||
void IEyeManager.Initialize()
|
||||
{
|
||||
MainViewport = _uiManager.MainViewport;
|
||||
_logMill = IoCManager.Resolve<ILogManager>().RootSawmill;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -129,14 +131,14 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public ScreenCoordinates CoordinatesToScreen(EntityCoordinates point)
|
||||
{
|
||||
return MapToScreen(point.ToMap(_entityManager));
|
||||
return MapToScreen(point.ToMap(_entityManager, _entityManager.System<SharedTransformSystem>()));
|
||||
}
|
||||
|
||||
public ScreenCoordinates MapToScreen(MapCoordinates point)
|
||||
{
|
||||
if (CurrentEye.Position.MapId != point.MapId)
|
||||
{
|
||||
Logger.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})");
|
||||
_logMill.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})");
|
||||
return new(default, WindowId.Invalid);
|
||||
}
|
||||
|
||||
@@ -146,12 +148,10 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates ScreenToMap(ScreenCoordinates point)
|
||||
{
|
||||
var (pos, window) = point;
|
||||
|
||||
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
|
||||
return default;
|
||||
|
||||
return viewport.ScreenToMap(pos);
|
||||
return viewport.ScreenToMap(point.Position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -159,6 +159,21 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
return MainViewport.ScreenToMap(point);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates PixelToMap(ScreenCoordinates point)
|
||||
{
|
||||
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
|
||||
return default;
|
||||
|
||||
return viewport.PixelToMap(point.Position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates PixelToMap(Vector2 point)
|
||||
{
|
||||
return MainViewport.PixelToMap(point);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CurrentEyeChangedEvent : EntityEventArgs
|
||||
|
||||
@@ -79,6 +79,16 @@ namespace Robust.Client.Graphics
|
||||
/// <returns>Corresponding point in the world.</returns>
|
||||
MapCoordinates ScreenToMap(Vector2 point);
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="ScreenToMap(ScreenCoordinates)"/>, except it should compensate for the effects of shaders on viewports.
|
||||
/// </summary>
|
||||
MapCoordinates PixelToMap(ScreenCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="ScreenToMap(Vector2)"/>, except it should compensate for the effects of shaders on viewports.
|
||||
/// </summary>
|
||||
MapCoordinates PixelToMap(Vector2 point);
|
||||
|
||||
void ClearCurrentEye();
|
||||
void Initialize();
|
||||
}
|
||||
|
||||
@@ -207,6 +207,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{GlfwKey.F13, Key.F13},
|
||||
{GlfwKey.F14, Key.F14},
|
||||
{GlfwKey.F15, Key.F15},
|
||||
{GlfwKey.F16, Key.F16},
|
||||
{GlfwKey.F17, Key.F17},
|
||||
{GlfwKey.F18, Key.F18},
|
||||
{GlfwKey.F19, Key.F19},
|
||||
{GlfwKey.F20, Key.F20},
|
||||
{GlfwKey.F21, Key.F21},
|
||||
{GlfwKey.F22, Key.F22},
|
||||
{GlfwKey.F23, Key.F23},
|
||||
{GlfwKey.F24, Key.F24},
|
||||
{GlfwKey.Pause, Key.Pause},
|
||||
{GlfwKey.World1, Key.World1},
|
||||
};
|
||||
|
||||
@@ -191,6 +191,15 @@ internal partial class Clyde
|
||||
MapKey(SDL_SCANCODE_F13, Key.F13);
|
||||
MapKey(SDL_SCANCODE_F14, Key.F14);
|
||||
MapKey(SDL_SCANCODE_F15, Key.F15);
|
||||
MapKey(SDL_SCANCODE_F16, Key.F16);
|
||||
MapKey(SDL_SCANCODE_F17, Key.F17);
|
||||
MapKey(SDL_SCANCODE_F18, Key.F18);
|
||||
MapKey(SDL_SCANCODE_F19, Key.F19);
|
||||
MapKey(SDL_SCANCODE_F20, Key.F20);
|
||||
MapKey(SDL_SCANCODE_F21, Key.F21);
|
||||
MapKey(SDL_SCANCODE_F22, Key.F22);
|
||||
MapKey(SDL_SCANCODE_F23, Key.F23);
|
||||
MapKey(SDL_SCANCODE_F24, Key.F24);
|
||||
MapKey(SDL_SCANCODE_PAUSE, Key.Pause);
|
||||
|
||||
KeyMapReverse = new Dictionary<Key, SDL_Scancode>();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -11,6 +12,7 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public struct StencilParameters
|
||||
public partial struct StencilParameters
|
||||
{
|
||||
public StencilParameters()
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[ViewVariables] private ShaderKind Kind;
|
||||
[ViewVariables] private Dictionary<string, object>? _params;
|
||||
@@ -95,19 +95,19 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
[DataField("kind", required: true)]
|
||||
private readonly string _rawKind = default!;
|
||||
private string _rawKind = default!;
|
||||
|
||||
[DataField("path")]
|
||||
private readonly ResPath? _path;
|
||||
private ResPath? _path;
|
||||
|
||||
[DataField("params")]
|
||||
private readonly Dictionary<string, string>? _paramMapping;
|
||||
private Dictionary<string, string>? _paramMapping;
|
||||
|
||||
[DataField("light_mode")]
|
||||
private readonly string? _rawMode;
|
||||
private string? _rawMode;
|
||||
|
||||
[DataField("blend_mode")]
|
||||
private readonly string? _rawBlendMode;
|
||||
private string? _rawBlendMode;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
|
||||
@@ -161,6 +161,15 @@ namespace Robust.Client.Input
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
Pause,
|
||||
World1,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
|
||||
namespace Robust.Client.Input
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed class KeyBindingRegistration
|
||||
public sealed partial class KeyBindingRegistration
|
||||
{
|
||||
[DataField("function")]
|
||||
public BoundKeyFunction Function;
|
||||
|
||||
@@ -3,13 +3,16 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
@@ -52,8 +55,11 @@ namespace Robust.Client.Map
|
||||
_genTextureAtlas();
|
||||
}
|
||||
|
||||
private void _genTextureAtlas()
|
||||
internal void _genTextureAtlas()
|
||||
{
|
||||
_tileRegions.Clear();
|
||||
_tileTextureAtlas = null;
|
||||
|
||||
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
|
||||
|
||||
// If there are no tile definitions, we do nothing.
|
||||
@@ -144,4 +150,17 @@ namespace Robust.Client.Map
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReloadTileTexturesCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly ClydeTileDefinitionManager _tile = default!;
|
||||
|
||||
public override string Command => "reloadtiletextures";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_tile._genTextureAtlas();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@ namespace Robust.Client.Physics;
|
||||
/// Simple component used to tag entities that have physics prediction enabled.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class PredictedPhysicsComponent : Component
|
||||
public sealed partial class PredictedPhysicsComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -532,7 +532,7 @@ namespace Robust.Client.Placement
|
||||
return false;
|
||||
}
|
||||
coordinates = EntityCoordinates.FromMap(MapManager,
|
||||
EyeManager.ScreenToMap(InputManager.MouseScreenPosition));
|
||||
EyeManager.PixelToMap(InputManager.MouseScreenPosition));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Robust.Client.Placement
|
||||
public IEnumerable<EntityCoordinates> LineCoordinates()
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.ScreenToMap(mouseScreen);
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
@@ -165,7 +165,7 @@ namespace Robust.Client.Placement
|
||||
public IEnumerable<EntityCoordinates> GridCoordinates()
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.ScreenToMap(mouseScreen);
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
@@ -254,7 +254,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
protected EntityCoordinates ScreenToCursorGrid(ScreenCoordinates coords)
|
||||
{
|
||||
var mapCoords = pManager.EyeManager.ScreenToMap(coords.Position);
|
||||
var mapCoords = pManager.EyeManager.PixelToMap(coords.Position);
|
||||
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid))
|
||||
{
|
||||
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
|
||||
|
||||
@@ -3,14 +3,18 @@ using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
|
||||
{
|
||||
new IEnumerable<ICommonSession> Sessions { get; }
|
||||
|
||||
[ViewVariables]
|
||||
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
|
||||
|
||||
[ViewVariables]
|
||||
LocalPlayer? LocalPlayer { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
@@ -17,11 +18,14 @@ namespace Robust.Client.Player
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public EntityUid? AttachedEntity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public NetUserId UserId { get; }
|
||||
|
||||
[ViewVariables]
|
||||
internal string Name { get; set; } = "<Unknown>";
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -31,9 +35,11 @@ namespace Robust.Client.Player
|
||||
set => this.Name = value;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
internal short Ping { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public INetChannel ConnectedClient { get; internal set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Upload.Commands;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Replays;
|
||||
@@ -250,36 +251,56 @@ public sealed partial class ReplayLoadManager
|
||||
continue;
|
||||
|
||||
message.Messages.RemoveSwap(i);
|
||||
var changed = new Dictionary<Type, HashSet<string>>();
|
||||
_protoMan.LoadString(protoUpload.PrototypeData, true, changed);
|
||||
|
||||
foreach (var (kind, ids) in changed)
|
||||
try
|
||||
{
|
||||
var protos = prototypes[kind];
|
||||
var count = protos.Count;
|
||||
protos.UnionWith(ids);
|
||||
if (!ignoreDuplicates && ids.Count + count != protos.Count)
|
||||
{
|
||||
// An existing prototype was overwritten. Much like for resource uploading, supporting this
|
||||
// requires tracking the last-modified time of prototypes and either resetting or applying
|
||||
// prototype changes when jumping around in time. This also requires reworking how the initial
|
||||
// implicit state data is generated, because we can't simply cache it anymore.
|
||||
// Also, does reloading prototypes in release mode modify existing entities?
|
||||
|
||||
var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
|
||||
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
_sawmill.Error(msg);
|
||||
else
|
||||
throw new NotSupportedException(msg);
|
||||
}
|
||||
LoadPrototype(protoUpload.PrototypeData, prototypes, ignoreDuplicates);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is NotSupportedException || !_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw;
|
||||
|
||||
_protoMan.ResolveResults();
|
||||
_protoMan.ReloadPrototypes(changed);
|
||||
_locMan.ReloadLocalizations();
|
||||
var msg = $"Caught exception while parsing uploaded prototypes in a replay. Exception: {e}";
|
||||
_sawmill.Error(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPrototype(
|
||||
string data,
|
||||
Dictionary<Type, HashSet<string>> prototypes,
|
||||
bool ignoreDuplicates)
|
||||
{
|
||||
var changed = new Dictionary<Type, HashSet<string>>();
|
||||
_protoMan.LoadString(data, true, changed);
|
||||
|
||||
foreach (var (kind, ids) in changed)
|
||||
{
|
||||
var protos = prototypes[kind];
|
||||
var count = protos.Count;
|
||||
protos.UnionWith(ids);
|
||||
if (!ignoreDuplicates && ids.Count + count != protos.Count)
|
||||
{
|
||||
// An existing prototype was overwritten. Much like for resource uploading, supporting this
|
||||
// requires tracking the last-modified time of prototypes and either resetting or applying
|
||||
// prototype changes when jumping around in time. This also requires reworking how the initial
|
||||
// implicit state data is generated, because we can't simply cache it anymore.
|
||||
// Also, does reloading prototypes in release mode modify existing entities?
|
||||
|
||||
var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
|
||||
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
_sawmill.Error(msg);
|
||||
else
|
||||
throw new NotSupportedException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
_protoMan.ResolveResults();
|
||||
_protoMan.ReloadPrototypes(changed);
|
||||
_locMan.ReloadLocalizations();
|
||||
}
|
||||
|
||||
private void UpdateDeletions(NetListAsArray<EntityUid> entityDeletions,
|
||||
Dictionary<EntityUid, EntityState> entStates, HashSet<EntityUid> detached)
|
||||
{
|
||||
|
||||
@@ -10,25 +10,26 @@
|
||||
<RobustILLink>true</RobustILLink>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" PrivateAssets="compile" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Robust.Natives" Version="0.1.1" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" PrivateAssets="compile" />
|
||||
|
||||
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void AddMessage(FormattedMessage message)
|
||||
{
|
||||
var entry = new RichTextEntry(message, this, _tagManager);
|
||||
var entry = new RichTextEntry(message, this, _tagManager, null);
|
||||
|
||||
entry.Update(_getFont(), _getContentBox().Width, UIScale);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
@@ -21,18 +22,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void SetMessage(FormattedMessage message)
|
||||
public void SetMessage(FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
_message = message;
|
||||
_entry = new RichTextEntry(_message, this, _tagManager);
|
||||
_entry = new RichTextEntry(_message, this, _tagManager, tagsAllowed, defaultColor);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
public void SetMessage(string message)
|
||||
public void SetMessage(string message, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(message);
|
||||
SetMessage(msg);
|
||||
SetMessage(msg, tagsAllowed, defaultColor);
|
||||
}
|
||||
|
||||
public string? GetMessage() => _message?.ToMarkup();
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string LeftButtonStyle = "spinbox-left";
|
||||
public const string RightButtonStyle = "spinbox-right";
|
||||
public const string MiddleButtonStyle = "spinbox-middle";
|
||||
private LineEdit _lineEdit;
|
||||
public LineEdit LineEditControl { get; }
|
||||
private List<Button> _leftButtons = new();
|
||||
private List<Button> _rightButtons = new();
|
||||
private int _stepSize = 1;
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
_value = value;
|
||||
_lineEdit.Text = value.ToString();
|
||||
LineEditControl.Text = value.ToString();
|
||||
ValueChanged?.Invoke(new ValueChangedEventArgs(value));
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
_value = value;
|
||||
_lineEdit.Text = value.ToString();
|
||||
LineEditControl.Text = value.ToString();
|
||||
}
|
||||
|
||||
public event Action<ValueChangedEventArgs>? ValueChanged;
|
||||
@@ -62,17 +62,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Orientation = LayoutOrientation.Horizontal;
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
_lineEdit = new LineEdit
|
||||
LineEditControl = new LineEdit
|
||||
{
|
||||
MinSize = new Vector2(40, 0),
|
||||
HorizontalExpand = true
|
||||
};
|
||||
AddChild(_lineEdit);
|
||||
AddChild(LineEditControl);
|
||||
|
||||
Value = 0;
|
||||
|
||||
_lineEdit.IsValid = (str) => int.TryParse(str, out var i) && (IsValid == null || IsValid(i));
|
||||
_lineEdit.OnTextChanged += (args) =>
|
||||
LineEditControl.IsValid = (str) => int.TryParse(str, out var i) && (IsValid == null || IsValid(i));
|
||||
LineEditControl.OnTextChanged += (args) =>
|
||||
{
|
||||
if (int.TryParse(args.Text, out int i))
|
||||
Value = i;
|
||||
@@ -143,8 +143,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public bool LineEditDisabled
|
||||
{
|
||||
get => !_lineEdit.Editable;
|
||||
set => _lineEdit.Editable = !value;
|
||||
get => !LineEditControl.Editable;
|
||||
set => LineEditControl.Editable = !value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -185,7 +185,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (!_lineEdit.HasKeyboardFocus())
|
||||
if (!LineEditControl.HasKeyboardFocus())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
EntityCoordinates mouseGridPos;
|
||||
TileRef tile;
|
||||
|
||||
var mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
|
||||
var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
|
||||
if (mouseWorldMap == MapCoordinates.Nullspace)
|
||||
return;
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
/// </param>
|
||||
MapCoordinates ScreenToMap(Vector2 coords);
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="ScreenToMap(Vector2)"/>, except it should compensate for the effects of shaders on viewports.
|
||||
/// </summary>
|
||||
MapCoordinates PixelToMap(Vector2 point);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a point on the map to screen coordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
/// <summary>
|
||||
/// An event used to reverse distortion effects applied by shaders.
|
||||
/// Used to find the map position that visible pixels originate from so that severe distortion shaders do not make interaction nigh-impossible.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct PixelToMapEvent(Vector2 LocalPosition, IViewportControl Control, IClydeViewport Viewport)
|
||||
{
|
||||
/// <summary>
|
||||
/// The local position of the pixel within the <see cref="Control"/> that we are trying to convert to a map position.
|
||||
/// </summary>
|
||||
public readonly Vector2 LocalPosition = LocalPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The original (or WIP) location of the pixel within the <see cref="Control"/> that we are trying to convert to a map position.
|
||||
/// Used as the output of the event.
|
||||
/// </summary>
|
||||
public Vector2 VisiblePosition = LocalPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The control the pixel we are considering is located within.
|
||||
/// </summary>
|
||||
public readonly IViewportControl Control = Control;
|
||||
|
||||
/// <summary>
|
||||
/// The viewport being displayed by the control we are considering.
|
||||
/// </summary>
|
||||
public readonly IClydeViewport Viewport = Viewport;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -13,8 +14,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
[Virtual]
|
||||
public class ViewportContainer : Control, IViewportControl
|
||||
{
|
||||
private readonly IClyde _displayManager;
|
||||
private readonly IInputManager _inputManager;
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
public IClydeViewport? Viewport { get; set; }
|
||||
|
||||
@@ -36,8 +38,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
public ViewportContainer()
|
||||
{
|
||||
_displayManager = IoCManager.Resolve<IClyde>();
|
||||
_inputManager = IoCManager.Resolve<IInputManager>();
|
||||
IoCManager.InjectDependencies(this);
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
Resized();
|
||||
}
|
||||
@@ -99,8 +100,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
// -- Handlers: In --
|
||||
|
||||
// -- Utils / S2M-M2S Base --
|
||||
|
||||
public MapCoordinates LocalPixelToMap(Vector2 point)
|
||||
public MapCoordinates LocalCoordsToMap(Vector2 point)
|
||||
{
|
||||
if (Viewport == null)
|
||||
return default;
|
||||
@@ -111,6 +111,19 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
return Viewport.LocalToWorld(point);
|
||||
}
|
||||
|
||||
public MapCoordinates LocalPixelToMap(Vector2 point)
|
||||
{
|
||||
if (Viewport == null)
|
||||
return default;
|
||||
|
||||
// pre-scaler
|
||||
point *= _viewportResolution;
|
||||
var ev = new PixelToMapEvent(point, this, Viewport);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, ref ev);
|
||||
|
||||
return Viewport.LocalToWorld(ev.VisiblePosition);
|
||||
}
|
||||
|
||||
public Vector2 WorldToLocalPixel(Vector2 point)
|
||||
{
|
||||
if (Viewport?.Eye == null)
|
||||
@@ -127,6 +140,12 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
// -- Utils / S2M-M2S Extended --
|
||||
|
||||
public MapCoordinates ScreenToMap(Vector2 point)
|
||||
{
|
||||
return LocalCoordsToMap(point - GlobalPixelPosition);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MapCoordinates PixelToMap(Vector2 point)
|
||||
{
|
||||
return LocalPixelToMap(point - GlobalPixelPosition);
|
||||
}
|
||||
|
||||
29
Robust.Client/UserInterface/RichText/BoldItalicTag.cs
Normal file
29
Robust.Client/UserInterface/RichText/BoldItalicTag.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class BoldItalicTag : IMarkupTag
|
||||
{
|
||||
public const string BoldItalicFont = "DefaultBoldItalic";
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "bolditalic";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldItalicFont);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -14,12 +15,18 @@ public sealed class BoldTag : IMarkupTag
|
||||
|
||||
public string Name => "bold";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldFont);
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager,
|
||||
context.Tags.Any(static x => x is ItalicTag)
|
||||
? BoldItalicTag.BoldItalicFont
|
||||
: BoldFont
|
||||
);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
|
||||
11
Robust.Client/UserInterface/RichText/BulletTag.cs
Normal file
11
Robust.Client/UserInterface/RichText/BulletTag.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class BulletTag : IMarkupTag
|
||||
{
|
||||
public string Name => "bullet";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string TextBefore(MarkupNode _) => " · ";
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
/// </summary>
|
||||
public sealed class ColorTag : IMarkupTag
|
||||
{
|
||||
private static readonly Color DefaultColor = new(200, 200, 200);
|
||||
public static readonly Color DefaultColor = new(200, 200, 200);
|
||||
|
||||
public string Name => "color";
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
public sealed class FontPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("path", required: true)]
|
||||
public ResPath Path { get; } = default!;
|
||||
public ResPath Path { get; private set; } = default!;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
public sealed class FontTag : IMarkupTag
|
||||
{
|
||||
public const string DefaultFont = "Default";
|
||||
public const int DefaultSize = 12;
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "font";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
string fontId = node.Value.StringValue ?? DefaultFont;
|
||||
@@ -28,6 +30,7 @@ public sealed class FontTag : IMarkupTag
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
@@ -44,7 +47,7 @@ public sealed class FontTag : IMarkupTag
|
||||
IPrototypeManager prototypeManager,
|
||||
string fontId)
|
||||
{
|
||||
var size = 12;
|
||||
var size = DefaultSize;
|
||||
|
||||
if (contextFontStack.TryPeek(out var previousFont))
|
||||
{
|
||||
|
||||
36
Robust.Client/UserInterface/RichText/HeadingTag.cs
Normal file
36
Robust.Client/UserInterface/RichText/HeadingTag.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class HeadingTag : IMarkupTag
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "head";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
if (!node.Value.TryGetLong(out var levelParam))
|
||||
return;
|
||||
|
||||
var level = Math.Min(Math.Max((int)levelParam, 1), 3);
|
||||
node.Attributes["size"] = new MarkupParameter(
|
||||
(int)Math.Ceiling(FontTag.DefaultSize * 2 / Math.Sqrt(level))
|
||||
);
|
||||
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldTag.BoldFont);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -11,14 +12,21 @@ public sealed class ItalicTag : IMarkupTag
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "italic";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, ItalicFont);
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager,
|
||||
context.Tags.Any(static x => x is BoldTag)
|
||||
? BoldItalicTag.BoldItalicFont
|
||||
: ItalicFont
|
||||
);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
|
||||
@@ -8,22 +8,26 @@ public sealed class MarkupDrawingContext
|
||||
{
|
||||
public readonly Stack<Color> Color;
|
||||
public readonly Stack<Font> Font;
|
||||
public readonly List<IMarkupTag> Tags;
|
||||
|
||||
public MarkupDrawingContext()
|
||||
{
|
||||
Color = new Stack<Color>();
|
||||
Font = new Stack<Font>();
|
||||
Tags = new List<IMarkupTag>();
|
||||
}
|
||||
|
||||
public MarkupDrawingContext(int capacity)
|
||||
{
|
||||
Color = new Stack<Color>(capacity);
|
||||
Font = new Stack<Font>(capacity);
|
||||
Tags = new List<IMarkupTag>();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Color.Clear();
|
||||
Font.Clear();
|
||||
Tags.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Sandboxing;
|
||||
@@ -15,24 +16,29 @@ public sealed class MarkupTagManager
|
||||
/// <summary>
|
||||
/// Tags defined in engine need to be instantiated here because of sandboxing
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, IMarkupTag> _markupTagTypes = new()
|
||||
{
|
||||
{"color", new ColorTag()},
|
||||
{"cmdlink", new CommandLinkTag()},
|
||||
{"font", new FontTag()},
|
||||
{"bold", new BoldTag()},
|
||||
{"italic", new ItalicTag()}
|
||||
};
|
||||
private readonly Dictionary<string, IMarkupTag> _markupTagTypes = new IMarkupTag[] {
|
||||
new BoldItalicTag(),
|
||||
new BoldTag(),
|
||||
new BulletTag(),
|
||||
new ColorTag(),
|
||||
new CommandLinkTag(),
|
||||
new FontTag(),
|
||||
new HeadingTag(),
|
||||
new ItalicTag()
|
||||
}.ToDictionary(x => x.Name.ToLower(), x => x);
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="IMarkupTag"/> types that shouldn't be instantiated through reflection
|
||||
/// </summary>
|
||||
private readonly List<Type> _engineTypes = new()
|
||||
{
|
||||
typeof(BoldItalicTag),
|
||||
typeof(BoldTag),
|
||||
typeof(BulletTag),
|
||||
typeof(ColorTag),
|
||||
typeof(CommandLinkTag),
|
||||
typeof(FontTag),
|
||||
typeof(BoldTag),
|
||||
typeof(HeadingTag),
|
||||
typeof(ItalicTag)
|
||||
};
|
||||
|
||||
@@ -59,9 +65,11 @@ public sealed class MarkupTagManager
|
||||
return _markupTagTypes.GetValueOrDefault(name);
|
||||
}
|
||||
|
||||
public bool TryGetMarkupTag(string name, [NotNullWhen(true)] out IMarkupTag? tag)
|
||||
public bool TryGetMarkupTag(string name, Type[]? tagsAllowed, [NotNullWhen(true)] out IMarkupTag? tag)
|
||||
{
|
||||
if (_markupTagTypes.TryGetValue(name, out var markupTag))
|
||||
if (_markupTagTypes.TryGetValue(name, out var markupTag)
|
||||
// Using a whitelist prevents new tags from sneaking in.
|
||||
&& (tagsAllowed == null || Array.IndexOf(tagsAllowed, markupTag.GetType()) != -1))
|
||||
{
|
||||
tag = markupTag;
|
||||
return true;
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
internal struct RichTextEntry
|
||||
{
|
||||
private static readonly Color DefaultColor = new(200, 200, 200);
|
||||
|
||||
private readonly Color _defaultColor;
|
||||
private readonly MarkupTagManager _tagManager;
|
||||
private readonly Type[]? _tagsAllowed;
|
||||
|
||||
public readonly FormattedMessage Message;
|
||||
|
||||
@@ -39,13 +39,15 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
private readonly Dictionary<int, Control> _tagControls = new();
|
||||
|
||||
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager)
|
||||
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
Message = message;
|
||||
Height = 0;
|
||||
Width = 0;
|
||||
LineBreaks = default;
|
||||
_defaultColor = defaultColor ?? new(200, 200, 200);
|
||||
_tagManager = tagManager;
|
||||
_tagsAllowed = tagsAllowed;
|
||||
|
||||
var nodeIndex = -1;
|
||||
foreach (var node in Message.Nodes)
|
||||
@@ -55,7 +57,7 @@ namespace Robust.Client.UserInterface
|
||||
if (node.Name == null)
|
||||
continue;
|
||||
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, out var tag) || !tag.TryGetControl(node, out var control))
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
|
||||
continue;
|
||||
|
||||
parent.Children.Add(control);
|
||||
@@ -82,7 +84,7 @@ namespace Robust.Client.UserInterface
|
||||
var wordWrap = new WordWrap(maxSizeX);
|
||||
var context = new MarkupDrawingContext();
|
||||
context.Font.Push(defaultFont);
|
||||
context.Color.Push(DefaultColor);
|
||||
context.Color.Push(_defaultColor);
|
||||
|
||||
// Go over every node.
|
||||
// Nodes can change the markup drawing context and return additional text.
|
||||
@@ -171,7 +173,7 @@ namespace Robust.Client.UserInterface
|
||||
float uiScale)
|
||||
{
|
||||
context.Clear();
|
||||
context.Color.Push(DefaultColor);
|
||||
context.Color.Push(_defaultColor);
|
||||
context.Font.Push(defaultFont);
|
||||
|
||||
var globalBreakCounter = 0;
|
||||
@@ -186,7 +188,7 @@ namespace Robust.Client.UserInterface
|
||||
var text = ProcessNode(node, context);
|
||||
if (!context.Color.TryPeek(out var color) || !context.Font.TryPeek(out var font))
|
||||
{
|
||||
color = DefaultColor;
|
||||
color = _defaultColor;
|
||||
font = defaultFont;
|
||||
}
|
||||
|
||||
@@ -226,15 +228,17 @@ namespace Robust.Client.UserInterface
|
||||
return node.Value.StringValue ?? "";
|
||||
|
||||
//Skip the node if there is no markup tag for it.
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, out var tag))
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
|
||||
return "";
|
||||
|
||||
if (!node.Closing)
|
||||
{
|
||||
context.Tags.Add(tag);
|
||||
tag.PushDrawContext(node, context);
|
||||
return tag.TextBefore(node);
|
||||
}
|
||||
|
||||
context.Tags.Remove(tag);
|
||||
tag.PopDrawContext(node, context);
|
||||
return tag.TextAfter(node);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class UITheme : IPrototype
|
||||
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("path")]
|
||||
private ResPath _path;
|
||||
@@ -70,7 +70,7 @@ public sealed class UITheme : IPrototype
|
||||
|
||||
if (!texturePath.EndsWith(".png"))
|
||||
texturePath = $"{texturePath}.png";
|
||||
|
||||
|
||||
var resPath = new ResPath(texturePath);
|
||||
if (resPath.IsRelative)
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ internal partial class UserInterfaceManager
|
||||
public void SetActiveTheme(string themeName)
|
||||
{
|
||||
if (!_themes.TryGetValue(themeName, out var theme) || (theme == CurrentTheme)) return;
|
||||
CurrentTheme = theme;
|
||||
UpdateTheme(theme);
|
||||
}
|
||||
|
||||
public void SetDefaultTheme(string themeId)
|
||||
|
||||
6
Robust.Serialization.Generator/DataDefinition.cs
Normal file
6
Robust.Serialization.Generator/DataDefinition.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Serialization.Generator;
|
||||
|
||||
public sealed record DataDefinition(ITypeSymbol Type, string GenericTypeName, List<DataField> Fields, bool HasHooks, bool InvalidFields);
|
||||
17
Robust.Serialization.Generator/DataDefinitionComparer.cs
Normal file
17
Robust.Serialization.Generator/DataDefinitionComparer.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Robust.Serialization.Generator;
|
||||
|
||||
public sealed class DataDefinitionComparer : IEqualityComparer<TypeDeclarationSyntax>
|
||||
{
|
||||
public bool Equals(TypeDeclarationSyntax x, TypeDeclarationSyntax y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public int GetHashCode(TypeDeclarationSyntax type)
|
||||
{
|
||||
return type.GetHashCode();
|
||||
}
|
||||
}
|
||||
14
Robust.Serialization.Generator/DataField.cs
Normal file
14
Robust.Serialization.Generator/DataField.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Serialization.Generator;
|
||||
|
||||
public sealed record DataField(
|
||||
ISymbol Symbol,
|
||||
ITypeSymbol Type,
|
||||
(INamedTypeSymbol Serializer, CustomSerializerType Type)? CustomSerializer);
|
||||
|
||||
public enum CustomSerializerType
|
||||
{
|
||||
Copier,
|
||||
CopyCreator
|
||||
}
|
||||
564
Robust.Serialization.Generator/Generator.cs
Normal file
564
Robust.Serialization.Generator/Generator.cs
Normal file
@@ -0,0 +1,564 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using static Robust.Serialization.Generator.CustomSerializerType;
|
||||
using static Robust.Serialization.Generator.Types;
|
||||
|
||||
namespace Robust.Serialization.Generator;
|
||||
|
||||
[Generator]
|
||||
public class Generator : IIncrementalGenerator
|
||||
{
|
||||
private const string TypeCopierInterfaceNamespace = "Robust.Shared.Serialization.TypeSerializers.Interfaces.ITypeCopier";
|
||||
private const string TypeCopyCreatorInterfaceNamespace = "Robust.Shared.Serialization.TypeSerializers.Interfaces.ITypeCopyCreator";
|
||||
private const string SerializationHooksNamespace = "Robust.Shared.Serialization.ISerializationHooks";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext initContext)
|
||||
{
|
||||
IncrementalValuesProvider<TypeDeclarationSyntax> dataDefinitions = initContext.SyntaxProvider.CreateSyntaxProvider(
|
||||
static (node, _) => node is TypeDeclarationSyntax,
|
||||
static (context, _) =>
|
||||
{
|
||||
var type = (TypeDeclarationSyntax) context.Node;
|
||||
var symbol = (ITypeSymbol) context.SemanticModel.GetDeclaredSymbol(type)!;
|
||||
return IsDataDefinition(symbol) ? type : null;
|
||||
}
|
||||
).Where(static type => type != null)!;
|
||||
|
||||
var comparer = new DataDefinitionComparer();
|
||||
initContext.RegisterSourceOutput(
|
||||
initContext.CompilationProvider.Combine(dataDefinitions.WithComparer(comparer).Collect()),
|
||||
static (sourceContext, source) =>
|
||||
{
|
||||
var (compilation, declarations) = source;
|
||||
var builder = new StringBuilder();
|
||||
var containingTypes = new Stack<INamedTypeSymbol>();
|
||||
|
||||
foreach (var declaration in declarations)
|
||||
{
|
||||
builder.Clear();
|
||||
containingTypes.Clear();
|
||||
|
||||
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
|
||||
|
||||
var nonPartial = !IsPartial(declaration);
|
||||
|
||||
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty
|
||||
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
|
||||
|
||||
var containingType = type.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
containingTypes.Push(containingType);
|
||||
containingType = containingType.ContainingType;
|
||||
}
|
||||
|
||||
var containingTypesStart = new StringBuilder();
|
||||
var containingTypesEnd = new StringBuilder();
|
||||
foreach (var parent in containingTypes)
|
||||
{
|
||||
var syntax = (ClassDeclarationSyntax) parent.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (!IsPartial(syntax))
|
||||
{
|
||||
nonPartial = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
|
||||
containingTypesEnd.AppendLine("}");
|
||||
}
|
||||
|
||||
var definition = GetDataDefinition(type);
|
||||
if (nonPartial || definition.InvalidFields)
|
||||
continue;
|
||||
|
||||
builder.AppendLine($$"""
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.Analyzers;
|
||||
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
|
||||
|
||||
{{namespaceString}}
|
||||
|
||||
{{containingTypesStart}}
|
||||
|
||||
[RobustAutoGenerated]
|
||||
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
|
||||
{
|
||||
{{GetConstructor(definition)}}
|
||||
|
||||
{{GetCopyMethods(definition)}}
|
||||
|
||||
{{GetInstantiators(definition)}}
|
||||
}
|
||||
|
||||
{{containingTypesEnd}}
|
||||
""");
|
||||
|
||||
var symbolName = type
|
||||
.ToDisplayString()
|
||||
.Replace('<', '{')
|
||||
.Replace('>', '}');
|
||||
|
||||
var sourceText = CSharpSyntaxTree
|
||||
.ParseText(builder.ToString())
|
||||
.GetRoot()
|
||||
.NormalizeWhitespace()
|
||||
.ToFullString();
|
||||
|
||||
sourceContext.AddSource($"{symbolName}.g.cs", sourceText);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static DataDefinition GetDataDefinition(ITypeSymbol definition)
|
||||
{
|
||||
var fields = new List<DataField>();
|
||||
var invalidFields = false;
|
||||
|
||||
foreach (var member in definition.GetMembers())
|
||||
{
|
||||
if (member is not IFieldSymbol && member is not IPropertySymbol)
|
||||
continue;
|
||||
|
||||
if (member.IsStatic)
|
||||
continue;
|
||||
|
||||
if (IsDataField(member, out var type, out var attribute))
|
||||
{
|
||||
if (attribute.ConstructorArguments.FirstOrDefault(arg => arg.Kind == TypedConstantKind.Type).Value is INamedTypeSymbol customSerializer)
|
||||
{
|
||||
if (ImplementsInterface(customSerializer, TypeCopierInterfaceNamespace))
|
||||
{
|
||||
fields.Add(new DataField(member, type, (customSerializer, Copier)));
|
||||
continue;
|
||||
}
|
||||
else if (ImplementsInterface(customSerializer, TypeCopyCreatorInterfaceNamespace))
|
||||
{
|
||||
fields.Add(new DataField(member, type, (customSerializer, CopyCreator)));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fields.Add(new DataField(member, type, null));
|
||||
|
||||
if (IsReadOnlyMember(definition, type))
|
||||
{
|
||||
invalidFields = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var typeName = GetGenericTypeName(definition);
|
||||
var hasHooks = ImplementsInterface(definition, SerializationHooksNamespace);
|
||||
|
||||
return new DataDefinition(definition, typeName, fields, hasHooks, invalidFields);
|
||||
}
|
||||
|
||||
private static string GetConstructor(DataDefinition definition)
|
||||
{
|
||||
if (definition.Type.TypeKind == TypeKind.Interface)
|
||||
return string.Empty;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (NeedsEmptyConstructor(definition.Type))
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
// Implicit constructor
|
||||
#pragma warning disable CS8618
|
||||
public {{definition.Type.Name}}()
|
||||
#pragma warning enable CS8618
|
||||
{
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetCopyMethods(DataDefinition definition)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
var modifiers = IsVirtualClass(definition.Type) ? "virtual " : string.Empty;
|
||||
var baseCall = string.Empty;
|
||||
string baseCopy;
|
||||
var baseType = definition.Type.BaseType;
|
||||
|
||||
if (baseType != null && IsDataDefinition(definition.Type.BaseType))
|
||||
{
|
||||
var baseName = baseType.ToDisplayString();
|
||||
baseCall = $"""
|
||||
var definitionCast = ({baseName}) target;
|
||||
base.InternalCopy(ref definitionCast, serialization, hookCtx, context);
|
||||
target = ({definition.GenericTypeName}) definitionCast;
|
||||
""";
|
||||
|
||||
baseCopy = $$"""
|
||||
public override void Copy(ref {{baseName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var cast = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref cast, serialization, hookCtx, context);
|
||||
target = cast!;
|
||||
}
|
||||
|
||||
public override void Copy(ref object target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var cast = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref cast, serialization, hookCtx, context);
|
||||
target = cast!;
|
||||
}
|
||||
""";
|
||||
}
|
||||
else
|
||||
{
|
||||
baseCopy = $$"""
|
||||
public {{modifiers}} void Copy(ref object target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var cast = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref cast, serialization, hookCtx, context);
|
||||
target = cast!;
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
builder.AppendLine($$"""
|
||||
public {{modifiers}} void InternalCopy(ref {{definition.GenericTypeName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
{{baseCall}}
|
||||
{{CopyDataFields(definition)}}
|
||||
}
|
||||
|
||||
public {{modifiers}} void Copy(ref {{definition.GenericTypeName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
InternalCopy(ref target, serialization, hookCtx, context);
|
||||
}
|
||||
|
||||
{{baseCopy}}
|
||||
""");
|
||||
|
||||
foreach (var @interface in GetImplicitDataDefinitionInterfaces(definition.Type, true))
|
||||
{
|
||||
var interfaceModifiers = baseType != null && baseType.AllInterfaces.Contains(@interface, SymbolEqualityComparer.Default)
|
||||
? "override "
|
||||
: modifiers;
|
||||
var interfaceName = @interface.ToDisplayString();
|
||||
|
||||
builder.AppendLine($$"""
|
||||
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var def = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref def, serialization, hookCtx, context);
|
||||
target = def;
|
||||
}
|
||||
|
||||
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
InternalCopy(ref target, serialization, hookCtx, context);
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetInstantiators(DataDefinition definition)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var modifiers = string.Empty;
|
||||
|
||||
if (definition.Type.BaseType is { } baseType && IsDataDefinition(baseType))
|
||||
modifiers = "override ";
|
||||
else if (IsVirtualClass(definition.Type))
|
||||
modifiers = "virtual ";
|
||||
|
||||
if (definition.Type.IsAbstract)
|
||||
{
|
||||
// TODO make abstract once data definitions are forced to be partial
|
||||
builder.AppendLine($$"""
|
||||
public {{modifiers}} {{definition.GenericTypeName}} Instantiate()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
""");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
public {{modifiers}} {{definition.GenericTypeName}} Instantiate()
|
||||
{
|
||||
return new {{definition.GenericTypeName}}();
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
foreach (var @interface in GetImplicitDataDefinitionInterfaces(definition.Type, false))
|
||||
{
|
||||
var interfaceName = @interface.ToDisplayString();
|
||||
builder.AppendLine($$"""
|
||||
{{interfaceName}} {{interfaceName}}.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
|
||||
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
// TODO serveronly? do we care? who knows!!
|
||||
private static StringBuilder CopyDataFields(DataDefinition definition)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine($"""
|
||||
if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.ToString().ToLower()}, context))
|
||||
return;
|
||||
""");
|
||||
|
||||
var structCopier = new StringBuilder();
|
||||
foreach (var field in definition.Fields)
|
||||
{
|
||||
var type = field.Type;
|
||||
var typeName = type.ToDisplayString();
|
||||
if (IsMultidimensionalArray(type))
|
||||
{
|
||||
typeName = typeName.Replace("*", "");
|
||||
}
|
||||
|
||||
var isNullableValueType = IsNullableValueType(type);
|
||||
var nonNullableTypeName = type.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString();
|
||||
if (isNullableValueType)
|
||||
{
|
||||
nonNullableTypeName = typeName.Substring(0, typeName.Length - 1);
|
||||
}
|
||||
|
||||
var isClass = type.IsReferenceType || type.SpecialType == SpecialType.System_String;
|
||||
var isNullable = type.NullableAnnotation == NullableAnnotation.Annotated;
|
||||
var nullableOverride = isClass && !isNullable ? ", true" : string.Empty;
|
||||
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 (nullNotAllowed)
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
if ({{name}} == null)
|
||||
{
|
||||
throw new NullNotAllowedException();
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
builder.AppendLine($$"""
|
||||
{{typeName}} {{tempVarName}} = default!;
|
||||
""");
|
||||
|
||||
if (isNullable || isNullableValueType)
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
if ({{name}} == null)
|
||||
{
|
||||
{{tempVarName}} = null!;
|
||||
}
|
||||
else
|
||||
{
|
||||
""");
|
||||
}
|
||||
|
||||
var serializerName = serializer.ToDisplayString();
|
||||
switch (serializerType)
|
||||
{
|
||||
case Copier:
|
||||
CopyToCustom(
|
||||
builder,
|
||||
nonNullableTypeName,
|
||||
serializerName,
|
||||
tempVarName,
|
||||
name,
|
||||
isNullable,
|
||||
isClass,
|
||||
isNullableValueType
|
||||
);
|
||||
break;
|
||||
case CopyCreator:
|
||||
CreateCopyCustom(
|
||||
builder,
|
||||
name,
|
||||
tempVarName,
|
||||
nonNullableTypeName,
|
||||
serializerName,
|
||||
nullableValue,
|
||||
nullableOverride
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isNullable || isNullableValueType)
|
||||
{
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
|
||||
if (definition.Type.IsValueType)
|
||||
{
|
||||
structCopier.AppendLine($"{name} = {tempVarName}!,");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"target.{name} = {tempVarName}!;");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
{{typeName}} {{tempVarName}} = default!;
|
||||
""");
|
||||
|
||||
if (nullNotAllowed)
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
if ({{name}} == null)
|
||||
{
|
||||
throw new NullNotAllowedException();
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
var instantiator = string.Empty;
|
||||
if (IsDataDefinition(type))
|
||||
{
|
||||
instantiator = $"{tempVarName} = {name}.Instantiate();";
|
||||
}
|
||||
else if (!type.IsAbstract &&
|
||||
HasEmptyPublicConstructor(type) &&
|
||||
(type.IsReferenceType || IsNullableType(type)))
|
||||
{
|
||||
instantiator = $"{tempVarName} = new();";
|
||||
}
|
||||
|
||||
var hasHooks = ImplementsInterface(type, SerializationHooksNamespace) || !type.IsSealed;
|
||||
builder.AppendLine($$"""
|
||||
if (!serialization.TryCustomCopy(this.{{name}}, ref {{tempVarName}}, hookCtx, {{hasHooks.ToString().ToLower()}}, context))
|
||||
{
|
||||
""");
|
||||
|
||||
if (CanBeCopiedByValue(field.Symbol, field.Type))
|
||||
{
|
||||
builder.AppendLine($"{tempVarName} = {name};");
|
||||
}
|
||||
else if (IsDataDefinition(type) && !type.IsAbstract &&
|
||||
type is not INamedTypeSymbol { TypeKind: TypeKind.Interface })
|
||||
{
|
||||
var nullability = type.IsValueType ? string.Empty : "?";
|
||||
var nullable = !type.IsValueType || IsNullableType(type);
|
||||
|
||||
if (nullable)
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
if ({{name}} == null)
|
||||
{
|
||||
{{tempVarName}} = null!;
|
||||
}
|
||||
else
|
||||
{
|
||||
""");
|
||||
}
|
||||
|
||||
builder.AppendLine($$"""
|
||||
{{instantiator}}
|
||||
{{name}}{{nullability}}.Copy(ref {{tempVarName}}, serialization, hookCtx, context);
|
||||
""");
|
||||
|
||||
if (nullable)
|
||||
{
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"{tempVarName} = serialization.CreateCopy({name}, hookCtx, context);");
|
||||
}
|
||||
|
||||
builder.AppendLine("}");
|
||||
|
||||
if (definition.Type.IsValueType)
|
||||
{
|
||||
structCopier.AppendLine($"{name} = {tempVarName}!,");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"target.{name} = {tempVarName}!;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.Type.IsValueType)
|
||||
{
|
||||
builder.AppendLine($$"""
|
||||
target = target with
|
||||
{
|
||||
{{structCopier}}
|
||||
};
|
||||
""");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void CopyToCustom(
|
||||
StringBuilder builder,
|
||||
string typeName,
|
||||
string serializerName,
|
||||
string tempVarName,
|
||||
string varName,
|
||||
bool isNullable,
|
||||
bool isClass,
|
||||
bool isNullableValueType)
|
||||
{
|
||||
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($$"""
|
||||
{{nonNullableTypeName}} {{tempVarName}}CopyTo = default!;
|
||||
{{newTemp}}
|
||||
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($$"""
|
||||
{{tempVarName}} = serialization.CreateCopy<{{nonNullableTypeName}}, {{serializerName}}>(this.{{varName}}{{nullableValue}}, hookCtx, context{{nullableOverride}});
|
||||
""");
|
||||
}
|
||||
}
|
||||
8
Robust.Serialization.Generator/IsExternalInit.cs
Normal file
8
Robust.Serialization.Generator/IsExternalInit.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices;
|
||||
|
||||
// Need this to be able to define records in the project
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
331
Robust.Serialization.Generator/Types.cs
Normal file
331
Robust.Serialization.Generator/Types.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Robust.Serialization.Generator;
|
||||
|
||||
internal static class Types
|
||||
{
|
||||
private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute";
|
||||
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
|
||||
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
|
||||
private const string CopyByRefNamespace = "Robust.Shared.Serialization.Manager.Attributes.CopyByRefAttribute";
|
||||
|
||||
internal static bool IsPartial(TypeDeclarationSyntax type)
|
||||
{
|
||||
return type.Modifiers.IndexOf(SyntaxKind.PartialKeyword) != -1;
|
||||
}
|
||||
|
||||
internal static bool IsDataDefinition(ITypeSymbol? type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return HasAttribute(type, DataDefinitionNamespace) ||
|
||||
IsImplicitDataDefinition(type);
|
||||
}
|
||||
|
||||
internal static bool IsDataField(ISymbol member, out ITypeSymbol type, out AttributeData attribute)
|
||||
{
|
||||
// TODO data records and other attributes
|
||||
if (member is IFieldSymbol field)
|
||||
{
|
||||
foreach (var attr in field.GetAttributes())
|
||||
{
|
||||
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
|
||||
{
|
||||
type = field.Type;
|
||||
attribute = attr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (member is IPropertySymbol property)
|
||||
{
|
||||
foreach (var attr in property.GetAttributes())
|
||||
{
|
||||
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
|
||||
{
|
||||
type = property.Type;
|
||||
attribute = attr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type = null!;
|
||||
attribute = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsImplicitDataDefinition(ITypeSymbol type)
|
||||
{
|
||||
if (HasAttribute(type, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
|
||||
foreach (var baseType in GetBaseTypes(type))
|
||||
{
|
||||
if (HasAttribute(baseType, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var @interface in type.AllInterfaces)
|
||||
{
|
||||
if (IsImplicitDataDefinitionInterface(@interface))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsImplicitDataDefinitionInterface(ITypeSymbol @interface)
|
||||
{
|
||||
if (HasAttribute(@interface, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
|
||||
foreach (var subInterface in @interface.AllInterfaces)
|
||||
{
|
||||
if (HasAttribute(subInterface, ImplicitDataDefinitionNamespace))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static IEnumerable<ITypeSymbol> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
|
||||
{
|
||||
var interfaces = all ? type.AllInterfaces : type.Interfaces;
|
||||
foreach (var @interface in interfaces)
|
||||
{
|
||||
if (IsImplicitDataDefinitionInterface(@interface))
|
||||
yield return @interface;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsNullableType(ITypeSymbol type)
|
||||
{
|
||||
if (type.NullableAnnotation == NullableAnnotation.Annotated)
|
||||
return true;
|
||||
|
||||
if (type.OriginalDefinition.ToDisplayString() == "System.Nullable<T>")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsNullableValueType(ITypeSymbol type)
|
||||
{
|
||||
return type.IsValueType && IsNullableType(type);
|
||||
}
|
||||
|
||||
internal static bool IsMultidimensionalArray(ITypeSymbol type)
|
||||
{
|
||||
return type is IArrayTypeSymbol { Rank: > 1 };
|
||||
}
|
||||
|
||||
internal static bool CanBeCopiedByValue(ISymbol member, ITypeSymbol type)
|
||||
{
|
||||
if (type.OriginalDefinition.ToDisplayString() == "System.Nullable<T>")
|
||||
return CanBeCopiedByValue(member, ((INamedTypeSymbol) type).TypeArguments[0]);
|
||||
|
||||
if (type.TypeKind == TypeKind.Enum)
|
||||
return true;
|
||||
|
||||
switch (type.SpecialType)
|
||||
{
|
||||
case SpecialType.System_Enum:
|
||||
case SpecialType.System_Boolean:
|
||||
case SpecialType.System_Char:
|
||||
case SpecialType.System_SByte:
|
||||
case SpecialType.System_Byte:
|
||||
case SpecialType.System_Int16:
|
||||
case SpecialType.System_UInt16:
|
||||
case SpecialType.System_Int32:
|
||||
case SpecialType.System_UInt32:
|
||||
case SpecialType.System_Int64:
|
||||
case SpecialType.System_UInt64:
|
||||
case SpecialType.System_Decimal:
|
||||
case SpecialType.System_Single:
|
||||
case SpecialType.System_Double:
|
||||
case SpecialType.System_String:
|
||||
case SpecialType.System_DateTime:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HasAttribute(member, CopyByRefNamespace))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static string GetGenericTypeName(ITypeSymbol symbol)
|
||||
{
|
||||
var name = symbol.Name;
|
||||
|
||||
if (symbol is INamedTypeSymbol { TypeParameters: { Length: > 0 } parameters })
|
||||
{
|
||||
name += "<";
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
name += parameter.Name;
|
||||
|
||||
if (i < parameters.Length - 1)
|
||||
{
|
||||
name += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
name += ">";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
internal static string GetPartialTypeDefinitionLine(ITypeSymbol symbol)
|
||||
{
|
||||
var access = symbol.DeclaredAccessibility switch
|
||||
{
|
||||
Accessibility.Private => "private",
|
||||
Accessibility.ProtectedAndInternal => "protected internal",
|
||||
Accessibility.Protected => "protected",
|
||||
Accessibility.Internal => "internal",
|
||||
Accessibility.Public => "public",
|
||||
_ => "public"
|
||||
};
|
||||
|
||||
var typeKeyword = "partial ";
|
||||
if (symbol.TypeKind == TypeKind.Interface)
|
||||
{
|
||||
typeKeyword += "interface";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (symbol.IsRecord)
|
||||
{
|
||||
typeKeyword += symbol.IsValueType ? "record struct" : "record";
|
||||
}
|
||||
else
|
||||
{
|
||||
typeKeyword += symbol.IsValueType ? "struct" : "class";
|
||||
}
|
||||
|
||||
if (symbol.IsAbstract)
|
||||
{
|
||||
typeKeyword = $"abstract {typeKeyword}";
|
||||
}
|
||||
}
|
||||
|
||||
var typeName = GetGenericTypeName(symbol);
|
||||
return $"{access} {typeKeyword} {typeName}";
|
||||
}
|
||||
|
||||
internal static bool Inherits(ITypeSymbol type, string parent)
|
||||
{
|
||||
foreach (var baseType in GetBaseTypes(type))
|
||||
{
|
||||
if (baseType.ToDisplayString() == parent)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool ImplementsInterface(ITypeSymbol type, string interfaceName)
|
||||
{
|
||||
foreach (var interfaceType in type.AllInterfaces)
|
||||
{
|
||||
if (interfaceType.ToDisplayString().Contains(interfaceName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
|
||||
{
|
||||
if (member is IFieldSymbol field)
|
||||
{
|
||||
return field.IsReadOnly;
|
||||
}
|
||||
else if (member is IPropertySymbol property)
|
||||
{
|
||||
if (property.SetMethod == null)
|
||||
return true;
|
||||
|
||||
if (property.SetMethod.IsInitOnly)
|
||||
return type.IsReferenceType;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool NeedsEmptyConstructor(ITypeSymbol type)
|
||||
{
|
||||
if (type is not INamedTypeSymbol named)
|
||||
return false;
|
||||
|
||||
if (named.InstanceConstructors.Length == 0)
|
||||
return true;
|
||||
|
||||
foreach (var constructor in named.InstanceConstructors)
|
||||
{
|
||||
if (constructor.Parameters.Length == 0 &&
|
||||
!constructor.IsImplicitlyDeclared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
internal static bool HasAttribute(ISymbol symbol, string attributeName)
|
||||
{
|
||||
foreach (var attribute in symbol.GetAttributes())
|
||||
{
|
||||
if (attribute.AttributeClass?.ToDisplayString() == attributeName)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static IEnumerable<ITypeSymbol> GetBaseTypes(ITypeSymbol type)
|
||||
{
|
||||
var baseType = type.BaseType;
|
||||
while (baseType != null)
|
||||
{
|
||||
yield return baseType;
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,19 +59,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 +82,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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class ActorComponent : Component
|
||||
public sealed partial class ActorComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public IPlayerSession PlayerSession { get; internal set; } = default!;
|
||||
|
||||
@@ -11,7 +11,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent, ComponentReference(typeof(SharedEyeComponent))]
|
||||
public sealed class EyeComponent : SharedEyeComponent
|
||||
public sealed partial class EyeComponent : SharedEyeComponent
|
||||
{
|
||||
public const int DefaultVisibilityMask = 1;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.GameObjects;
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
internal sealed class ViewSubscriberComponent : Component
|
||||
internal sealed partial class ViewSubscriberComponent : Component
|
||||
{
|
||||
internal readonly HashSet<IPlayerSession> SubscribedSessions = new();
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedPointLightComponent))]
|
||||
public sealed class PointLightComponent : SharedPointLightComponent {}
|
||||
public sealed partial class PointLightComponent : SharedPointLightComponent {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Lets any entities with this component ignore user interface range checks that would normally
|
||||
/// close the UI automatically.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class IgnoreUIRangeComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -15,14 +15,14 @@ namespace Robust.Server.GameObjects
|
||||
/// <seealso cref="BoundUserInterface"/>
|
||||
[PublicAPI]
|
||||
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
|
||||
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent
|
||||
public sealed partial class ServerUserInterfaceComponent : SharedUserInterfaceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<Enum, BoundUserInterface> Interfaces = new();
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveUserInterfaceComponent : Component
|
||||
public sealed partial class ActiveUserInterfaceComponent : Component
|
||||
{
|
||||
public HashSet<BoundUserInterface> Interfaces = new();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[Access(typeof(VisibilitySystem))]
|
||||
public sealed class VisibilityComponent : Component
|
||||
public sealed partial class VisibilityComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The visibility layer for the entity.
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Robust.Server.GameObjects
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
[Dependency] private readonly TransformSystem _xformSys = default!;
|
||||
|
||||
private EntityQuery<IgnoreUIRangeComponent> _ignoreUIRangeQuery;
|
||||
|
||||
private readonly List<IPlayerSession> _sessionCache = new();
|
||||
|
||||
private readonly Dictionary<IPlayerSession, List<BoundUserInterface>> _openInterfaces = new();
|
||||
@@ -30,6 +32,8 @@ namespace Robust.Server.GameObjects
|
||||
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentInit>(OnUserInterfaceInit);
|
||||
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
|
||||
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -174,6 +178,9 @@ namespace Robust.Server.GameObjects
|
||||
if (!query.TryGetComponent(session.AttachedEntity, out var xform))
|
||||
continue;
|
||||
|
||||
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
|
||||
continue;
|
||||
|
||||
if (uiMap != xform.MapID)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Server.GameObjects
|
||||
/// This can then be used to re-serialize the entity with the same UID for the merge driver to recognize.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed class MapSaveIdComponent : Component
|
||||
public sealed partial class MapSaveIdComponent : Component
|
||||
{
|
||||
public int Uid { get; set; }
|
||||
}
|
||||
|
||||
@@ -81,30 +81,35 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private protected override EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null)
|
||||
{
|
||||
var entity = base.CreateEntity(prototypeName, uid, context);
|
||||
if (prototypeName == null)
|
||||
return base.CreateEntity(prototypeName, uid, context);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(prototypeName))
|
||||
{
|
||||
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
|
||||
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
|
||||
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
|
||||
|
||||
// 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,
|
||||
// which indicates "not different from client's own deserialization".
|
||||
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
|
||||
foreach (var (netId, component) in GetNetComponents(entity))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
var compName = ComponentFactory.GetComponentName(component.GetType());
|
||||
if (prototype.Components.ContainsKey(compName))
|
||||
component.ClearTicks();
|
||||
}
|
||||
}
|
||||
var entity = base.CreateEntity(prototype, uid, 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,
|
||||
// which indicates "not different from client's own deserialization".
|
||||
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
|
||||
ClearTicks(entity, prototype);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void ClearTicks(EntityUid entity, EntityPrototype prototype)
|
||||
{
|
||||
foreach (var (netId, component) in GetNetComponents(entity))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
var compName = ComponentFactory.GetComponentName(netId);
|
||||
if (prototype.Components.ContainsKey(compName))
|
||||
component.ClearTicks();
|
||||
}
|
||||
}
|
||||
|
||||
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
|
||||
{
|
||||
TryGetComponent(uid, out ActorComponent? actor);
|
||||
|
||||
@@ -77,11 +77,21 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// </summary>
|
||||
private readonly HashSet<TIndex> _globalOverrides = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent along with all of their children.
|
||||
/// </summary>
|
||||
private readonly HashSet<TIndex> _globalRecursiveOverrides = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent.
|
||||
/// </summary>
|
||||
public HashSet<TIndex>.Enumerator GlobalOverridesEnumerator => _globalOverrides.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent along with all of their children.
|
||||
/// </summary>
|
||||
public HashSet<TIndex>.Enumerator GlobalRecursiveOverridesEnumerator => _globalRecursiveOverrides.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent to a certain <see cref="ICommonSession"/>.
|
||||
/// </summary>
|
||||
@@ -203,8 +213,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
case GlobalOverride _:
|
||||
_globalOverrides.Add(index);
|
||||
case GlobalOverride global:
|
||||
if (global.Recursive)
|
||||
_globalRecursiveOverrides.Add(index);
|
||||
else
|
||||
_globalOverrides.Add(index);
|
||||
break;
|
||||
case GridChunkLocation gridChunkLocation:
|
||||
// might be gone due to grid-deletions
|
||||
@@ -239,8 +252,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
// since we can find the index, we can assume the dicts will be there too & dont need to do any checks. gaming.
|
||||
switch (location)
|
||||
{
|
||||
case GlobalOverride _:
|
||||
_globalOverrides.Remove(index);
|
||||
case GlobalOverride global:
|
||||
if (global.Recursive)
|
||||
_globalRecursiveOverrides.Remove(index);
|
||||
else
|
||||
_globalOverrides.Remove(index);
|
||||
break;
|
||||
case GridChunkLocation gridChunkLocation:
|
||||
_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices].Remove(index);
|
||||
@@ -376,15 +392,10 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
|
||||
#region UpdateIndex
|
||||
|
||||
private bool IsOverride(TIndex index)
|
||||
private bool TryGetLocation(TIndex index, out IIndexLocation? location)
|
||||
{
|
||||
if (_locationChangeBuffer.TryGetValue(index, out var change) &&
|
||||
change is GlobalOverride or LocalOverride) return true;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var indexLoc) &&
|
||||
indexLoc is GlobalOverride or LocalOverride) return true;
|
||||
|
||||
return false;
|
||||
return _locationChangeBuffer.TryGetValue(index, out location)
|
||||
|| _indexLocations.TryGetValue(index, out location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -392,15 +403,25 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// </summary>
|
||||
/// <param name="index">The <see cref="TIndex"/> to update.</param>
|
||||
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
|
||||
public void UpdateIndex(TIndex index, bool removeFromOverride = false)
|
||||
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
|
||||
public void AddGlobalOverride(TIndex index, bool removeFromOverride, bool recursive)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
if (!TryGetLocation(index, out var oldLocation))
|
||||
{
|
||||
RegisterUpdate(index, new GlobalOverride(recursive));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeFromOverride && oldLocation is LocalOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is GlobalOverride) return;
|
||||
if (oldLocation is GlobalOverride global &&
|
||||
(!removeFromOverride || global.Recursive == recursive))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterUpdate(index, new GlobalOverride());
|
||||
RegisterUpdate(index, new GlobalOverride(recursive));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -411,12 +432,20 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
|
||||
public void UpdateIndex(TIndex index, ICommonSession session, bool removeFromOverride = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
if (!TryGetLocation(index, out var oldLocation))
|
||||
{
|
||||
RegisterUpdate(index, new LocalOverride(session));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeFromOverride || oldLocation is GlobalOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is LocalOverride local &&
|
||||
local.Session == session) return;
|
||||
if (oldLocation is LocalOverride local &&
|
||||
(!removeFromOverride || local.Session == session))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterUpdate(index, new LocalOverride(session));
|
||||
}
|
||||
@@ -429,34 +458,42 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
|
||||
public void UpdateIndex(TIndex index, EntityCoordinates coordinates, bool removeFromOverride = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
if (!removeFromOverride
|
||||
&& TryGetLocation(index, out var oldLocation)
|
||||
&& oldLocation is GlobalOverride or LocalOverride)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entityManager.TryGetComponent(coordinates.EntityId, out TransformComponent? xform))
|
||||
return;
|
||||
|
||||
var gridIdOpt = coordinates.GetGridUid(_entityManager);
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
if (xform.GridUid is { } gridId && gridId.IsValid())
|
||||
{
|
||||
var gridIndices = GetChunkIndices(coordinates.Position);
|
||||
UpdateIndex(index, gridId, gridIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
return;
|
||||
}
|
||||
|
||||
var mapCoordinates = coordinates.ToMap(_entityManager, _transformSystem);
|
||||
var mapIndices = GetChunkIndices(coordinates.Position);
|
||||
UpdateIndex(index, mapCoordinates.MapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
var worldPos = _transformSystem.GetWorldMatrix(xform).Transform(coordinates.Position);
|
||||
var mapIndices = GetChunkIndices(worldPos);
|
||||
UpdateIndex(index, xform.MapID, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
}
|
||||
|
||||
public IChunkIndexLocation GetChunkIndex(EntityCoordinates coordinates)
|
||||
{
|
||||
var gridIdOpt = coordinates.GetGridUid(_entityManager);
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
if (!_entityManager.TryGetComponent(coordinates.EntityId, out TransformComponent? xform))
|
||||
return new MapChunkLocation(default, default);
|
||||
|
||||
if (xform.GridUid is { } gridId && gridId.IsValid())
|
||||
{
|
||||
var gridIndices = GetChunkIndices(coordinates.Position);
|
||||
return new GridChunkLocation(gridId, gridIndices);
|
||||
}
|
||||
|
||||
var mapCoordinates = coordinates.ToMap(_entityManager, _transformSystem);
|
||||
var mapIndices = GetChunkIndices(coordinates.Position);
|
||||
return new MapChunkLocation(mapCoordinates.MapId, mapIndices);
|
||||
var worldPos = _transformSystem.GetWorldMatrix(xform).Transform(coordinates.Position);
|
||||
var mapIndices = GetChunkIndices(worldPos);
|
||||
return new MapChunkLocation(xform.MapID, mapIndices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -469,11 +506,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="forceDirty">If true, this will mark the previous chunk as dirty even if the entity did not move from that chunk.</param>
|
||||
public void UpdateIndex(TIndex index, EntityUid gridId, Vector2i chunkIndices, bool removeFromOverride = false, bool forceDirty = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
_locationChangeBuffer.TryGetValue(index, out var bufferedLocation);
|
||||
_indexLocations.TryGetValue(index, out var oldLocation);
|
||||
|
||||
//removeFromOverride is false 99% of the time.
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is GridChunkLocation oldGrid &&
|
||||
if (oldLocation is GridChunkLocation oldGrid &&
|
||||
oldGrid.ChunkIndices == chunkIndices &&
|
||||
oldGrid.GridId == gridId)
|
||||
{
|
||||
@@ -497,22 +537,26 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="forceDirty">If true, this will mark the previous chunk as dirty even if the entity did not move from that chunk.</param>
|
||||
public void UpdateIndex(TIndex index, MapId mapId, Vector2i chunkIndices, bool removeFromOverride = false, bool forceDirty = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
_locationChangeBuffer.TryGetValue(index, out var bufferedLocation);
|
||||
_indexLocations.TryGetValue(index, out var oldLocation);
|
||||
|
||||
//removeFromOverride is false 99% of the time.
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is MapChunkLocation oldMap &&
|
||||
// Is this entity just returning to its old location?
|
||||
if (oldLocation is MapChunkLocation oldMap &&
|
||||
oldMap.ChunkIndices == chunkIndices &&
|
||||
oldMap.MapId == mapId)
|
||||
{
|
||||
_locationChangeBuffer.Remove(index);
|
||||
if (bufferedLocation != null)
|
||||
_locationChangeBuffer.Remove(index);
|
||||
|
||||
if (forceDirty)
|
||||
_dirtyChunks.Add(oldMap);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
|
||||
}
|
||||
|
||||
@@ -584,7 +628,18 @@ public struct GridChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatabl
|
||||
}
|
||||
}
|
||||
|
||||
public struct GlobalOverride : IIndexLocation { }
|
||||
public struct GlobalOverride : IIndexLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, this will also send all children of the override.
|
||||
/// </summary>
|
||||
public readonly bool Recursive;
|
||||
|
||||
public GlobalOverride(bool recursive)
|
||||
{
|
||||
Recursive = recursive;
|
||||
}
|
||||
}
|
||||
|
||||
public struct LocalOverride : IIndexLocation
|
||||
{
|
||||
|
||||
@@ -12,20 +12,23 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
[Shared.IoC.Dependency] private readonly PvsSystem _pvs = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to every client. Overrides any client-specific overrides.
|
||||
/// Used to ensure that an entity is always sent to every client. By default this overrides any client-specific overrides.
|
||||
/// </summary>
|
||||
public void AddGlobalOverride(EntityUid uid)
|
||||
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
|
||||
/// <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.UpdateIndex(uid, true);
|
||||
_pvs.EntityPVSCollection.AddGlobalOverride(uid, removeExistingOverride, recursive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing
|
||||
/// client-specific overrides.
|
||||
/// </summary>
|
||||
public void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
/// <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, true);
|
||||
_pvs.EntityPVSCollection.UpdateIndex(uid, session, removeExistingOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.Configuration;
|
||||
@@ -13,11 +12,9 @@ using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -107,6 +104,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
private EntityQuery<EyeComponent> _eyeQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -114,6 +112,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
_eyeQuery = GetEntityQuery<EyeComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
|
||||
|
||||
@@ -387,7 +386,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
pvsCollection.AddGrid(gridId);
|
||||
}
|
||||
|
||||
_entityPvsCollection.UpdateIndex(gridId);
|
||||
_entityPvsCollection.AddGlobalOverride(gridId, true, false);
|
||||
}
|
||||
|
||||
private void OnMapDestroyed(MapChangedEvent e)
|
||||
@@ -407,7 +406,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
if(e.Map == MapId.Nullspace) return;
|
||||
var uid = _mapManager.GetMapEntityId(e.Map);
|
||||
_entityPvsCollection.UpdateIndex(uid);
|
||||
_entityPvsCollection.AddGlobalOverride(uid, true, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -749,6 +748,16 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
globalEnumerator.Dispose();
|
||||
|
||||
|
||||
var globalRecursiveEnumerator = _entityPvsCollection.GlobalRecursiveOverridesEnumerator;
|
||||
while (globalRecursiveEnumerator.MoveNext())
|
||||
{
|
||||
var uid = globalRecursiveEnumerator.Current;
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
|
||||
}
|
||||
globalRecursiveEnumerator.Dispose();
|
||||
|
||||
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
|
||||
while (localEnumerator.MoveNext())
|
||||
{
|
||||
@@ -764,7 +773,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
var expandEvent = new ExpandPvsEvent(session, new List<EntityUid>());
|
||||
var expandEvent = new ExpandPvsEvent(session);
|
||||
RaiseLocalEvent(ref expandEvent);
|
||||
foreach (var entityUid in expandEvent.Entities)
|
||||
{
|
||||
@@ -772,6 +781,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
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,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
|
||||
}
|
||||
|
||||
var entityStates = new List<EntityState>(entStateCount);
|
||||
|
||||
foreach (var (uid, visiblity) in visibleEnts)
|
||||
@@ -898,8 +913,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
public bool RecursivelyAddOverride(
|
||||
in EntityUid uid,
|
||||
public bool RecursivelyAddOverride(in EntityUid uid,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<EntityUid, PvsEntityVisibility> toSend,
|
||||
@@ -911,34 +925,74 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref int enteredEntityCount,
|
||||
ref int entStateCount,
|
||||
in int newEntityBudget,
|
||||
in int enteredEntityBudget)
|
||||
in int enteredEntityBudget,
|
||||
bool addChildren = false)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
if (!uid.IsValid()) return false;
|
||||
if (!uid.IsValid())
|
||||
return false;
|
||||
|
||||
var parent = transQuery.GetComponent(uid).ParentUid;
|
||||
var xform = transQuery.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))
|
||||
return false;
|
||||
|
||||
//did we already get added?
|
||||
if (toSend.ContainsKey(uid)) return true;
|
||||
// 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))
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (addChildren)
|
||||
{
|
||||
RecursivelyAddChildren(xform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(TransformComponent xform,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<EntityUid, PvsEntityVisibility> toSend,
|
||||
Dictionary<EntityUid, GameTick> lastSeen,
|
||||
in GameTick fromTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
ref int entStateCount,
|
||||
in int newEntityBudget,
|
||||
in int enteredEntityBudget)
|
||||
{
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(child, out var childXform))
|
||||
continue;
|
||||
|
||||
if (!toSend.ContainsKey(child))
|
||||
{
|
||||
var (entered, _) = ProcessEntry(in child, lastAcked, lastSent, lastSeen, ref newEntityCount,
|
||||
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
AddToSendSet(in child, _metaQuery.GetComponent(child), toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
|
||||
private (bool Entered, bool ShouldAdd) ProcessEntry(in EntityUid uid,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
|
||||
@@ -1034,26 +1088,20 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
while (query.MoveNext(out var uid, out var md))
|
||||
{
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
if (md.EntityLastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
// Temporary debugging code.
|
||||
// TODO REMOVE TEMPORARY CODE
|
||||
if (state.Empty)
|
||||
{
|
||||
var msg = $"{nameof(GetEntityState)} returned an empty state while enumerating all. Entity {ToPrettyString(uid)}. Net component Data:";
|
||||
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
|
||||
{
|
||||
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
|
||||
$"Enabled: {cmp.NetSyncEnabled}, " +
|
||||
$"Lifestage: {cmp.LifeStage}, " +
|
||||
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
|
||||
$"SessionSpecific: {cmp.SessionSpecific}, " +
|
||||
$"LastModified: {cmp.LastModifiedTick}";
|
||||
}
|
||||
Log.Error(msg);
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
|
||||
Tick: {fromTick}--{_gameTiming.CurTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
@@ -1077,26 +1125,21 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
// Temporary debugging code.
|
||||
// TODO REMOVE TEMPORARY CODE
|
||||
if (state.Empty)
|
||||
{
|
||||
var msg = $"{nameof(GetEntityState)} returned an empty state for new entity {ToPrettyString(uid)}. Net component Data:";
|
||||
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
|
||||
{
|
||||
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
|
||||
$"Enabled: {cmp.NetSyncEnabled}, " +
|
||||
$"Lifestage: {cmp.LifeStage}, " +
|
||||
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
|
||||
$"SessionSpecific: {cmp.SessionSpecific}, " +
|
||||
$"LastModified: {cmp.LastModifiedTick}";
|
||||
}
|
||||
Log.Error(msg);
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state for a new entity.
|
||||
Tick: {fromTick}--{_gameTiming.CurTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
continue;
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
@@ -1109,8 +1152,9 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
if (!state.Empty)
|
||||
@@ -1330,11 +1374,20 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
public readonly struct ExpandPvsEvent
|
||||
{
|
||||
public readonly IPlayerSession Session;
|
||||
public readonly List<EntityUid> Entities;
|
||||
|
||||
public ExpandPvsEvent(IPlayerSession session, List<EntityUid> entities)
|
||||
/// <summary>
|
||||
/// List of entities that will get added to this session's PVS set.
|
||||
/// </summary>
|
||||
public readonly List<EntityUid> Entities = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of entities that will get added to this session's PVS set. Unlike <see cref="Entities"/> this will also
|
||||
/// recursively add all children of the given entity.
|
||||
/// </summary>
|
||||
public readonly List<EntityUid> RecursiveEntities = new();
|
||||
|
||||
public ExpandPvsEvent(IPlayerSession session)
|
||||
{
|
||||
Session = session;
|
||||
Entities = entities;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace Robust.Server.GameStates
|
||||
|
||||
using (_usageHistogram.WithLabels("Clean Dirty").NewTimer())
|
||||
{
|
||||
_pvs.CleanupDirty(_playerManager.ServerSessions);
|
||||
_pvs.CleanupDirty(players);
|
||||
}
|
||||
|
||||
// keep the deletion history buffers clean
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Server.Maps;
|
||||
/// Added to Maps that were loaded by MapLoaderSystem. If not present then this map was created externally.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class LoadedMapComponent : Component
|
||||
public sealed partial class LoadedMapComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -13,15 +13,18 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<!-- -->
|
||||
<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'" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' != 'True'" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
|
||||
<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" />
|
||||
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="6.0.0" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.2" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -29,7 +29,6 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
using SysVector3 = System.Numerics.Vector3;
|
||||
@@ -1638,6 +1637,11 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
public static Color RoyalBlue => new(65, 105, 225, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system color with (R, G, B, A) = (204, 71, 120, 255).
|
||||
/// </summary>
|
||||
public static Color Ruber => new(204, 71, 120, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system color with (R, G, B, A) = (139, 69, 19, 255).
|
||||
/// </summary>
|
||||
@@ -1653,6 +1657,11 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
public static Color SandyBrown => new(244, 164, 96, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system color with (R, G, B, A) = (0, 66, 153, 255).
|
||||
/// </summary>
|
||||
public static Color SeaBlue => new(0, 66, 153, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system color with (R, G, B, A) = (46, 139, 87, 255).
|
||||
/// </summary>
|
||||
@@ -1733,6 +1742,16 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
public static Color Violet => new(238, 130, 238, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system color with (R, G, B, A) = (126, 3, 168, 255).
|
||||
/// </summary>
|
||||
public static Color BetterViolet => new(126, 3, 168, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system color with (R, G, B, A) = (255, 153, 0, 255).
|
||||
/// </summary>
|
||||
public static Color VividGamboge => new(255, 153, 0, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system color with (R, G, B, A) = (245, 222, 179, 255).
|
||||
/// </summary>
|
||||
@@ -1767,6 +1786,7 @@ namespace Robust.Shared.Maths
|
||||
["aquamarine"] = Aquamarine,
|
||||
["azure"] = Azure,
|
||||
["beige"] = Beige,
|
||||
["betterviolet"] = BetterViolet,
|
||||
["bisque"] = Bisque,
|
||||
["black"] = Black,
|
||||
["blanchedalmond"] = BlanchedAlmond,
|
||||
@@ -1877,9 +1897,11 @@ namespace Robust.Shared.Maths
|
||||
["red"] = Red,
|
||||
["rosybrown"] = RosyBrown,
|
||||
["royalblue"] = RoyalBlue,
|
||||
["ruber"] = Ruber,
|
||||
["saddlebrown"] = SaddleBrown,
|
||||
["salmon"] = Salmon,
|
||||
["sandybrown"] = SandyBrown,
|
||||
["seablue"] = SeaBlue,
|
||||
["seagreen"] = SeaGreen,
|
||||
["seashell"] = SeaShell,
|
||||
["sienna"] = Sienna,
|
||||
@@ -1896,6 +1918,7 @@ namespace Robust.Shared.Maths
|
||||
["tomato"] = Tomato,
|
||||
["turquoise"] = Turquoise,
|
||||
["violet"] = Violet,
|
||||
["vividgamboge"] = VividGamboge,
|
||||
["wheat"] = Wheat,
|
||||
["white"] = White,
|
||||
["whitesmoke"] = WhiteSmoke,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
@@ -10,7 +10,14 @@ namespace Robust.Shared.Maths
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public struct Vector2i : IEquatable<Vector2i>, ISpanFormattable
|
||||
public struct Vector2i :
|
||||
IEquatable<Vector2i>,
|
||||
ISpanFormattable,
|
||||
IAdditionOperators<Vector2i, Vector2i, Vector2i>,
|
||||
ISubtractionOperators<Vector2i, Vector2i, Vector2i>,
|
||||
IMultiplyOperators<Vector2i, Vector2i, Vector2i>,
|
||||
IMultiplyOperators<Vector2i, int, Vector2i>,
|
||||
IComparisonOperators<Vector2i, Vector2i, bool>
|
||||
{
|
||||
public static readonly Vector2i Zero = (0, 0);
|
||||
public static readonly Vector2i One = (1, 1);
|
||||
@@ -213,5 +220,25 @@ namespace Robust.Shared.Maths
|
||||
out charsWritten,
|
||||
$"({X}, {Y})");
|
||||
}
|
||||
|
||||
public static bool operator >(Vector2i left, Vector2i right)
|
||||
{
|
||||
return left.LengthSquared > right.LengthSquared;
|
||||
}
|
||||
|
||||
public static bool operator >=(Vector2i left, Vector2i right)
|
||||
{
|
||||
return left.LengthSquared >= right.LengthSquared;
|
||||
}
|
||||
|
||||
public static bool operator <(Vector2i left, Vector2i right)
|
||||
{
|
||||
return left.LengthSquared < right.LengthSquared;
|
||||
}
|
||||
|
||||
public static bool operator <=(Vector2i left, Vector2i right)
|
||||
{
|
||||
return left.LengthSquared <= right.LengthSquared;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user