Compare commits

...

88 Commits

Author SHA1 Message Date
PJB3005
d2d67b8ec9 Version: 156.0.7 2025-09-19 09:17:43 +02:00
Skye
0691c5b007 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:43 +02:00
PJB3005
4b07dcb228 Version: 156.0.6 2025-09-14 15:13:21 +02:00
PJB3005
61d1ce54f2 Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
(cherry picked from commit 4413695c77fb705054c2f81fa18ec0a189b685dd)
(cherry picked from commit ff8461a6fb545dec80a77ece07a46cf1df5de830)
(cherry picked from commit 59998668c10e58c7f1fb759de6497bf9c23b450a)
2025-09-14 15:13:21 +02:00
Pieter-Jan Briers
6660471f01 Version: 156.0.5 2024-08-11 19:54:53 +02:00
Pieter-Jan Briers
007cd84384 Use absolute path for explorer.exe
frick me

(cherry picked from commit 0284eb0430)
2024-08-11 19:54:53 +02:00
Pieter-Jan Briers
1dce85a0d1 Version: 156.0.4 2024-08-11 19:33:08 +02:00
Pieter-Jan Briers
e96ef3be6d Compile compat fixes
(cherry picked from commit 025d90d281)
(cherry picked from commit 799702b814)
(cherry picked from commit 4600ee8e5788891f1b610e2d5141fb4e1228d323)
(cherry picked from commit e5bce321669ec50c05b86bf3f5fa9dbd2dfe40ef)
2024-08-11 19:33:07 +02:00
Pieter-Jan Briers
54d94f0257 Version: 156.0.3 2024-08-11 18:04:19 +02:00
Pieter-Jan Briers
f8c6d00fed Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

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

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

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

* three billion tweaks

* Rune-aware parser.

* a

* all shedded out for the night

* a

* oogh

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

* Implement parsing for all number types.

* i think i might implode

* a

* Tests.

* a

* Enum parser test.

* do u like parsers

* oopls

* ug fixes

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

* Pool toolshed's tests, also type tests.

* bwa

* tests pass :yay:

* Update Robust.Shared/CVars.cs

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

* how did this not fail tests

* awa

* many levels of silly

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
2023-08-23 16:03:34 -05:00
metalgearsloth
876de4065a Version: 150.0.1 2023-08-23 19:16:17 +10:00
metalgearsloth
60e159f0d0 Fix some merge artifacts (#4297) 2023-08-23 19:15:29 +10:00
metalgearsloth
80f3aae30c Version: 150.0.0 2023-08-23 18:55:43 +10:00
Leon Friedrich
98b1862433 Add new spawn functions (#4280) 2023-08-23 18:54:55 +10:00
Leon Friedrich
d2311c193f Add AbstractDictionarySerializer (#4276) 2023-08-23 18:53:13 +10:00
metalgearsloth
f05ed96461 Remove FixtureId from fixtures (#4270) 2023-08-23 18:50:48 +10:00
DrSmugleaf
dc23dfaf4d Version: 149.0.1 2023-08-23 00:27:59 -07:00
DmitriyMX
62315f7c2e fix: crash client when window set maxsize (#4291) 2023-08-23 16:59:57 +10:00
DrSmugleaf
b2d121e780 Fix serialization sharing instances when copying data definitions and not assigning null when the source is null (#4295) 2023-08-22 23:59:03 -07:00
DrSmugleaf
fb4b029122 Version: 149.0.0 2023-08-22 18:07:35 -07:00
DrSmugleaf
66239d23ea Refactor serialization copying to use source generators (#4286) 2023-08-22 17:37:13 -07:00
DrSmugleaf
dbb45f1c13 Version: 148.4.0 2023-08-21 23:19:25 -07:00
Leon Friedrich
6284e16b64 Add recursive PVS overrides and remove IsOverride() (#4262) 2023-08-21 23:17:53 -07:00
441 changed files with 11038 additions and 2187 deletions

5
.github/CODEOWNERS vendored
View File

@@ -16,3 +16,8 @@
# Be they Fluent translations or Freemarker templates, I know them both!
*.ftl @RemieRichards
# commands commands commands commands
**/Toolshed/** @moonheart08
*Command.cs @moonheart08
*Commands.cs @moonheart08

View File

@@ -7,12 +7,12 @@ jobs:
docfx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x

View File

@@ -15,12 +15,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Install dependencies

View File

@@ -35,12 +35,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x

View File

@@ -16,12 +16,12 @@ jobs:
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
echo ("::set-output name=version::{0}" -f $ver)
- uses: actions/checkout@v2
- uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
@@ -33,10 +33,10 @@ jobs:
mkdir "release/${{ steps.parse_version.outputs.version }}"
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
- name: Upload files to centcomm
- name: Upload files to Suns
uses: appleboy/scp-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
source: "release/${{ steps.parse_version.outputs.version }}"
@@ -46,7 +46,7 @@ jobs:
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}

View File

@@ -13,13 +13,13 @@ jobs:
steps:
- name: Check out content
uses: actions/checkout@v2
uses: actions/checkout@v3.6.0
with:
repository: space-wizards/space-station-14
submodules: recursive
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Disable submodule autoupdate

74
Directory.Packages.props Normal file
View File

@@ -0,0 +1,74 @@
<Project>
<PropertyGroup>
<!--
We actually set ManagePackageVersionsCentrally manually in another import file.
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
https://github.com/NuGet/NuGet.Client/pull/5572
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
-->
<ManagePackageVersionsCentrally />
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
<PackageVersion Include="Linguini.Bundle" Version="0.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.8.0" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
<PackageVersion Include="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.24059.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
<PackageVersion Include="Microsoft.ILVerification" Version="8.0.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="NUnit" Version="4.0.1" />
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Nett" Version="0.15.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageVersion Include="Pidgin" Version="3.2.2" />
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
<PackageVersion Include="Robust.Natives.Cef" Version="120.1.9" />
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.7" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
</ItemGroup>
</Project>

View File

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

View File

@@ -25,4 +25,7 @@
<!-- analyzer -->
<Import Project="Robust.Analyzers.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
<!-- serialization generator -->
<Import Project="Robust.Serialization.Generator.targets" />
</Project>

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

View File

@@ -54,6 +54,208 @@ END TEMPLATE-->
*None yet*
## 156.0.7
## 156.0.6
## 156.0.5
## 156.0.4
## 156.0.3
## 156.0.2
## 156.0.1
## 156.0.0
### Breaking changes
* Revert container changes from 155.0.0.
## 155.0.0
### Breaking changes
* MapInitEvent now gets raised for components that get added to entities that have already been map-initialized.
### New features
* VirtualWritableDirProvider now supports file renaming/moving.
* Added a new command for toggling the replay UI (`replay_toggleui`).
### Bugfixes
* Fixed formatting of localization file errors.
* Directed event subscriptions will no longer error if the corresponding component is queued for deletion.
## 154.2.0
### New features
* Added support for advertising to multiple hubs simultaneously.
* Added new functions to ContainerSystem that recursively look for a component on a contained entity's parents.
### Bugfixes
* Fix Direction.TurnCw/TurnCcw to South returning Invalid.
## 154.1.0
### New features
* Add MathHelper.Max for TimeSpans.
### Bugfixes
* Make joint initialisation only log under IsFirstTimePredicted on client.
### Other
* Mark the proxy Dirty(component) as obsolete in line with EntityManager (Dirty(EntityUid, Component) should be used in its place).
## 154.0.0
### Breaking changes
* Change ignored prototypes to skip prototypes even if the prototype type is found.
* Moved IPlayerData interface to shared.
### New features
* Added a multiline text submit keybind function.
### Bugfixes
* Fixed multiline edits scrollbar margins.
### Internal
* Added more event sources.
* Made Toolshed types oneOff IoC injections.
## 153.0.0
### Breaking changes
* Removed SharedUserInterfaceComponent component references.
* Removed EntityDeletedMessage.
### Other
* Performance improvements for replay recording.
* Lidgren has been updated to [v0.2.6](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.2.6/RELEASE-NOTES.md).
* Make EntityManager.AddComponent with a component instance set the owner if its default, add system proxy for it.
### Internal
* Added some `EventSource` providers for PVS and replay recording: `Robust.Pvs` and `Robust.ReplayRecording`.
* Added RecursiveMoveBenchmark.
* Removed redundant prototype resolving.
* Removed CollisionWake component removal subscription.
* Removed redundant DebugTools.AssertNotNull(netId) in ClientGameStateManager
## 152.0.0
### Breaking changes
* `Robust.Server.GameObjects.BoundUserInterface.InteractionRangeSqrd` is now a get-only property. Modify `InteractionRange` instead if you want to change it on active UIs.
* Remove IContainerManager.
* Remove and obsolete ComponentExt methods.
* Remove EntityStarted and ComponentDeleted C# events.
* Convert Tile.TypeId to an int. Old maps that were saved with TypeId being an ushort will still be properly deserialized.
### New features
* `BoundUserInterfaceCheckRangeEvent` can be used to implement custom logic for BUI range checks.
* Add support for long values in CVars.
* Allow user code to implement own logic for bound user interface range checks.
### Bugfixes
* Fix timers counting down slower than real time and drifting.
* Add missing System using statement to generated component states.
* Fix build with USE_SYSTEM_SQLITE.
* Fix prototype manager not being initialized in robust server simulation tests.
* Fix not running serialization hooks when copying non-byref data definition fields without a custom type serializer.
### Other
* Remove warning for glibc 2.37.
* Remove personally-identifiable file paths from client logs.
### Internal
* Disable obsoletion and inherited member hidden warnings in serialization source generated code.
* Update CI workflows to use setup-dotnet 3.2.0 and checkout 3.6.0.
* Fix entity spawn tests having instance per test lifecycle with a non static OneTimeTearDown method.
* Add new PVS test to check that there is no issue with entity states referencing other entities that the client is not yet aware of.
## 151.0.0
## 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

View File

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

View File

@@ -0,0 +1,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;
}
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,171 @@
using System;
using System.Linq;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Server.Containers;
using Robust.Server.GameStates;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Transform;
/// <summary>
/// This benchmark tests various transform/move related functions with an entity that has many children.
/// </summary>
[Virtual, MemoryDiagnoser]
public class RecursiveMoveBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entMan = default!;
private SharedTransformSystem _transform = default!;
private ContainerSystem _container = default!;
private PvsSystem _pvs = default!;
private EntityCoordinates _mapCoords;
private EntityCoordinates _gridCoords;
private EntityUid _ent;
private EntityUid _child;
private TransformComponent _childXform = default!;
private EntityQuery<TransformComponent> _query;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.InitializeInstance();
if (!_simulation.Resolve<IConfigurationManager>().GetCVar(CVars.NetPVS))
throw new InvalidOperationException("PVS must be enabled");
_entMan = _simulation.Resolve<IEntityManager>();
_transform = _entMan.System<SharedTransformSystem>();
_container = _entMan.System<ContainerSystem>();
_pvs = _entMan.System<PvsSystem>();
_query = _entMan.GetEntityQuery<TransformComponent>();
// Create map & grid
var mapMan = _simulation.Resolve<IMapManager>();
var mapSys = _entMan.System<SharedMapSystem>();
var mapId = mapMan.CreateMap();
var map = mapMan.GetMapEntityId(mapId);
var gridComp = mapMan.CreateGrid(mapId);
var grid = gridComp.Owner;
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
_mapCoords = new EntityCoordinates(map, 100, 100);
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
_ent = _entMan.Spawn();
// Quick check that SetCoordinates actually changes the parent as expected
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
_transform.SetCoordinates(_ent, _gridCoords);
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
throw new Exception("Grid traversal error.");
_transform.SetCoordinates(_ent, _mapCoords);
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
throw new Exception("Grid traversal error.");
// Add 5 direct children in slots to represent clothing.
for (var i = 0; i < 5; i++)
{
var id = $"inventory{i}";
_container.EnsureContainer<ContainerSlot>(_ent, id);
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
throw new Exception($"Failed to setup entity");
}
// body parts
_container.EnsureContainer<Container>(_ent, "body");
for (var i = 0; i < 5; i++)
{
// Simple organ
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
throw new Exception($"Failed to setup entity");
// body part that has another body part / limb
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
throw new Exception($"Failed to setup entity");
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
throw new Exception($"Failed to setup entity");
}
// Backpack
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
throw new Exception($"Failed to setup entity");
// Misc backpack contents.
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
for (var i = 0; i < 10; i++)
{
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
throw new Exception($"Failed to setup entity");
}
// Emergency box inside of the backpack
var box = backpackStorage.ContainedEntities.First();
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
for (var i = 0; i < 10; i++)
{
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
throw new Exception($"Failed to setup entity");
}
// Deepest child.
_child = boxContainer.ContainedEntities.First();
_childXform = _query.GetComponent(_child);
_pvs.ProcessCollections();
}
/// <summary>
/// This implicitly measures move events, including PVS and entity lookups. Though given that most of the entities
/// are in containers, this will bias the entity lookup aspect.
/// </summary>
[Benchmark]
public void MoveEntity()
{
_transform.SetCoordinates(_ent, _gridCoords);
_transform.SetCoordinates(_ent, _mapCoords);
}
/// <summary>
/// Like <see cref="MoveEntity"/>, but also processes queued PVS chunk updates.
/// </summary>
[Benchmark]
public void MoveAndUpdateChunks()
{
_transform.SetCoordinates(_ent, _gridCoords);
_pvs.ProcessCollections();
_transform.SetCoordinates(_ent, _mapCoords);
_pvs.ProcessCollections();
}
[Benchmark]
public Vector2 GetWorldPos()
{
return _transform.GetWorldPosition(_childXform);
}
[Benchmark]
public EntityUid GetRootUid()
{
var xform = _childXform;
while (xform.ParentUid.IsValid())
{
xform = _query.GetComponent(xform.ParentUid);
}
return xform.ParentUid;
}
}

View File

@@ -3,7 +3,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
public static class Program
internal static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)

View File

@@ -4,6 +4,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -23,6 +24,7 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -60,7 +62,10 @@ namespace Robust.Client.WebView.Cef
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
{
var rootDir = UserDataDir.GetUserDataDir(_gameController);
cachePath = Path.GetFullPath(Path.Combine(rootDir, "..", "cef_cache", "0"));
}
var settings = new CefSettings()
{

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var type = Type.GetType(args[0]);
var type = GetType(args[0]);
if (type == null)
{
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
shell.WriteLine(sig);
}
}
private Type? GetType(string name)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetType(name) is { } type)
return type;
}
return null;
}
}
#endif
}

View File

@@ -167,7 +167,6 @@ namespace Robust.Client
_reflectionManager.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDefaultPrototypes();
_prototypeManager.ResolveResults();
_userInterfaceManager.Initialize();
_eyeManager.Initialize();
_entityManager.Initialize();
@@ -433,7 +432,7 @@ namespace Robust.Client
_parallelMgr.Initialize();
_prof.Initialize();
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Replays;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,8 +6,8 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
public sealed class ClientUserInterfaceComponent : SharedUserInterfaceComponent
[RegisterComponent]
public sealed partial class ClientUserInterfaceComponent : SharedUserInterfaceComponent
{
[ViewVariables]
internal readonly Dictionary<Enum, PrototypeData> _interfaces = new();

View File

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

View File

@@ -206,8 +206,10 @@ namespace Robust.Client.GameObjects
SetEntityContextActive(_inputManager, controlled);
}
void IPostInjectInit.PostInject()
protected override void PostInject()
{
base.PostInject();
_sawmillInputContext = _logManager.GetSawmill("input.context");
}
}

View File

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

View File

@@ -225,7 +225,7 @@ namespace Robust.Client.Graphics
}
[DataDefinition]
public struct StencilParameters
public partial struct StencilParameters
{
public StencilParameters()
{

View File

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

View File

@@ -64,6 +64,7 @@ namespace Robust.Client.Input
common.AddFunction(EngineKeyFunctions.TextWordDelete);
common.AddFunction(EngineKeyFunctions.TextNewline);
common.AddFunction(EngineKeyFunctions.TextSubmit);
common.AddFunction(EngineKeyFunctions.MultilineTextSubmit);
common.AddFunction(EngineKeyFunctions.TextCopy);
common.AddFunction(EngineKeyFunctions.TextCut);
common.AddFunction(EngineKeyFunctions.TextPaste);

View File

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

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.Map
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
private readonly Dictionary<int, Box2[]> _tileRegions = new();
public Box2 ErrorTileRegion { get; private set; }
@@ -38,7 +38,7 @@ namespace Robust.Client.Map
}
/// <inheritdoc />
public Box2[]? TileAtlasRegion(ushort tileType)
public Box2[]? TileAtlasRegion(int tileType)
{
if (_tileRegions.TryGetValue(tileType, out var region))
{

View File

@@ -27,6 +27,6 @@ namespace Robust.Client.Map
/// Gets the region inside the texture atlas to use to draw a tile type.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2[]? TileAtlasRegion(ushort tileType);
Box2[]? TileAtlasRegion(int tileType);
}
}

View File

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

View File

@@ -7,7 +7,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
{
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
public interface IPlayerManager : ISharedPlayerManager
{
new IEnumerable<ICommonSession> Sessions { get; }

View File

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

View File

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

View File

@@ -17,7 +17,7 @@
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.9" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />

View File

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

View File

@@ -776,9 +776,11 @@ public sealed class TextEdit : Control
{
var size = base.ArrangeOverride(finalSize);
_scrollBar.Page = size.Y * UIScale;
var renderBoxSize = _renderBox.Size;
UpdateLineBreaks((int)(size.X * UIScale));
_scrollBar.Page = renderBoxSize.Y * UIScale;
UpdateLineBreaks((int)(renderBoxSize.X * UIScale));
return size;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@@ -0,0 +1,552 @@
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
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
#pragma warning disable RA0002 // Robust access analyzer
{{namespaceString}}
{{containingTypesStart}}
{{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 restore 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 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 nullable = !type.IsValueType || IsNullableType(type);
if (nullable)
{
builder.AppendLine($$"""
if ({{name}} == null)
{
{{tempVarName}} = null!;
}
else
{
""");
}
builder.AppendLine($$"""
serialization.CopyTo({{name}}, ref {{tempVarName}}, hookCtx, context{{nullableOverride}});
""");
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}});
""");
}
}

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

View File

@@ -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.4.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,314 @@
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 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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,10 +47,13 @@ internal sealed partial class MetricsManager
// Task.Run this so it gets run on another thread pool thread.
_ = Task.Run(async () =>
{
MetricsEvents.Log.RequestStart();
var resp = ctx.Response;
var req = ctx.Request;
try
{
MetricsEvents.Log.ScrapeStart();
var stream = resp.OutputStream;
// prometheus-net is a terrible library and have to do all this insanity,
@@ -74,6 +77,8 @@ internal sealed partial class MetricsManager
}), cancel);
await stream.DisposeAsync();
MetricsEvents.Log.ScrapeStop();
}
catch (ScrapeFailedException e)
{
@@ -97,6 +102,8 @@ internal sealed partial class MetricsManager
finally
{
resp.Close();
MetricsEvents.Log.RequestStop();
}
}, CancellationToken.None);
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
@@ -9,6 +10,7 @@ using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using EventSource = System.Diagnostics.Tracing.EventSource;
#nullable enable
@@ -168,6 +170,24 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
return builder;
}
[EventSource(Name = "Robust.MetricsManager")]
private sealed class MetricsEvents : EventSource
{
public static MetricsEvents Log { get; } = new();
[Event(1)]
public void ScrapeStart() => WriteEvent(1);
[Event(2)]
public void ScrapeStop() => WriteEvent(2);
[Event(3)]
public void RequestStart() => WriteEvent(3);
[Event(4)]
public void RequestStop() => WriteEvent(4);
}
}
internal interface IMetricsManager

View File

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

View File

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

View File

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

View File

@@ -4,5 +4,5 @@ namespace Robust.Server.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedPointLightComponent))]
public sealed class PointLightComponent : SharedPointLightComponent {}
public sealed partial class PointLightComponent : SharedPointLightComponent {}
}

View File

@@ -7,6 +7,6 @@ namespace Robust.Server.GameObjects;
/// close the UI automatically.
/// </summary>
[RegisterComponent]
public sealed class IgnoreUIRangeComponent : Component
public sealed partial class IgnoreUIRangeComponent : Component
{
}

View File

@@ -14,15 +14,15 @@ namespace Robust.Server.GameObjects
/// </summary>
/// <seealso cref="BoundUserInterface"/>
[PublicAPI]
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent
[RegisterComponent]
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();
}
@@ -33,7 +33,9 @@ namespace Robust.Server.GameObjects
[PublicAPI]
public sealed class BoundUserInterface
{
public float InteractionRangeSqrd;
public float InteractionRange;
public float InteractionRangeSqrd => InteractionRange * InteractionRange;
public Enum UiKey { get; }
public EntityUid Owner { get; }
@@ -58,8 +60,7 @@ namespace Robust.Server.GameObjects
UiKey = data.UiKey;
Owner = owner;
// One Abs(), because negative values imply no limit
InteractionRangeSqrd = data.InteractionRange * MathF.Abs(data.InteractionRange);
InteractionRange = data.InteractionRange;
}
}

View File

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

View File

@@ -51,7 +51,7 @@ public sealed class MapLoaderSystem : EntitySystem
private ISawmill _logWriter = default!;
private static readonly MapLoadOptions DefaultLoadOptions = new();
private const int MapFormatVersion = 5;
private const int MapFormatVersion = 6;
private const int BackwardsVersion = 2;
private MapSerializationContext _context = default!;
@@ -384,11 +384,11 @@ public sealed class MapLoaderSystem : EntitySystem
// Load tile mapping so that we can map the stored tile IDs into the ones actually used at runtime.
var tileMap = data.RootMappingNode.Get<MappingDataNode>("tilemap");
_context.TileMap = new Dictionary<ushort, string>(tileMap.Count);
_context.TileMap = new Dictionary<int, string>(tileMap.Count);
foreach (var (key, value) in tileMap.Children)
{
var tileId = (ushort) ((ValueDataNode)key).AsInt();
var tileId = ((ValueDataNode)key).AsInt();
var tileDefName = ((ValueDataNode)value).Value;
_context.TileMap.Add(tileId, tileDefName);
}
@@ -960,7 +960,7 @@ public sealed class MapLoaderSystem : EntitySystem
{
// Although we could use tiledefmanager it might write tiledata we don't need so we'll compress it
var gridQuery = GetEntityQuery<MapGridComponent>();
var tileDefs = new HashSet<ushort>();
var tileDefs = new HashSet<int>();
foreach (var ent in entities)
{
@@ -977,7 +977,7 @@ public sealed class MapLoaderSystem : EntitySystem
var tileMap = new MappingDataNode();
rootNode.Add("tilemap", tileMap);
var ordered = new List<ushort>(tileDefs);
var ordered = new List<int>(tileDefs);
ordered.Sort();
foreach (var tyleId in ordered)

View File

@@ -128,12 +128,13 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
public override void Update(float frameTime)
{
var query = GetEntityQuery<TransformComponent>();
foreach (var (activeUis, xform) in EntityQuery<ActiveUserInterfaceComponent, TransformComponent>(true))
var xformQuery = GetEntityQuery<TransformComponent>();
var query = AllEntityQuery<ActiveUserInterfaceComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var activeUis, out var xform))
{
foreach (var ui in activeUis.Interfaces)
{
CheckRange(activeUis, ui, xform, query);
CheckRange(uid, activeUis, ui, xform, xformQuery);
if (!ui.StateDirty)
continue;
@@ -160,9 +161,9 @@ namespace Robust.Server.GameObjects
/// <summary>
/// Verify that the subscribed clients are still in range of the interface.
/// </summary>
private void CheckRange(ActiveUserInterfaceComponent activeUis, BoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, BoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
{
if (ui.InteractionRangeSqrd <= 0)
if (ui.InteractionRange <= 0)
return;
// We have to cache the set of sessions because Unsubscribe modifies the original.
@@ -181,6 +182,20 @@ namespace Robust.Server.GameObjects
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
continue;
// Handle pluggable BoundUserInterfaceCheckRangeEvent
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, ui, session);
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
continue;
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
{
CloseUi(ui, session, activeUis);
continue;
}
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
if (uiMap != xform.MapID)
{
CloseUi(ui, session, activeUis);
@@ -509,4 +524,64 @@ namespace Robust.Server.GameObjects
#endregion
}
/// <summary>
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
/// </summary>
[ByRefEvent]
[PublicAPI]
public struct BoundUserInterfaceCheckRangeEvent
{
/// <summary>
/// The entity owning the UI being checked for.
/// </summary>
public readonly EntityUid Target;
/// <summary>
/// The UI itself.
/// </summary>
/// <returns></returns>
public readonly BoundUserInterface UserInterface;
/// <summary>
/// The player for which the UI is being checked.
/// </summary>
public readonly IPlayerSession Player;
/// <summary>
/// The result of the range check.
/// </summary>
public BoundUserInterfaceRangeResult Result;
public BoundUserInterfaceCheckRangeEvent(
EntityUid target,
BoundUserInterface userInterface,
IPlayerSession player)
{
Target = target;
UserInterface = userInterface;
Player = player;
}
}
/// <summary>
/// Possible results for a <see cref="BoundUserInterfaceCheckRangeEvent"/>.
/// </summary>
public enum BoundUserInterfaceRangeResult : byte
{
/// <summary>
/// Run built-in range check.
/// </summary>
Default,
/// <summary>
/// Range check passed, UI is accessible.
/// </summary>
Pass,
/// <summary>
/// Range check failed, UI is inaccessible.
/// </summary>
Fail
}
}

View File

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

View File

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

View File

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

View File

@@ -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,
@@ -1320,11 +1374,20 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
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;
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
@@ -23,8 +24,8 @@ using SharpZstd.Interop;
using Microsoft.Extensions.ObjectPool;
using Prometheus;
using Robust.Server.Replays;
using Robust.Shared.Players;
using Robust.Shared.Map.Components;
using Robust.Shared.Players;
namespace Robust.Server.GameStates
{
@@ -37,7 +38,7 @@ namespace Robust.Server.GameStates
private PvsSystem _pvs = default!;
[Dependency] private readonly IServerEntityManager _entityManager = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IServerNetManager _networkManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -219,10 +220,16 @@ namespace Robust.Server.GameStates
{
try
{
var guid = i >= 0 ? players[i].UserId.UserId : default;
PvsEventSource.Log.WorkStart(_gameTiming.CurTick.Value, i, guid);
if (i >= 0)
SendStateUpdate(i, resource, inputSystem, players[i], pvsData, mQuery, tQuery, ref oldestAckValue);
else
_replay.Update();
PvsEventSource.Log.WorkStop(_gameTiming.CurTick.Value, i, guid);
}
catch (Exception e) // Catch EVERY exception
{
@@ -373,5 +380,35 @@ namespace Robust.Server.GameStates
_networkManager.ServerSendMessage(pvsMessage, channel);
}
}
[EventSource(Name = "Robust.Pvs")]
public sealed class PvsEventSource : System.Diagnostics.Tracing.EventSource
{
public static PvsEventSource Log { get; } = new();
[Event(1)]
public void WorkStart(uint tick, int playerIdx, Guid playerGuid) => WriteEvent(1, tick, playerIdx, playerGuid);
[Event(2)]
public void WorkStop(uint tick, int playerIdx, Guid playerGuid) => WriteEvent(2, tick, playerIdx, playerGuid);
[NonEvent]
private unsafe void WriteEvent(int eventId, uint arg1, int arg2, Guid arg3)
{
if (IsEnabled())
{
var descrs = stackalloc EventData[3];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 4;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 4;
descrs[2].DataPointer = (IntPtr)(&arg3);
descrs[2].Size = 16;
WriteEventCore(eventId, 3, descrs);
}
}
}
}
}

View File

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

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using Robust.Server.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -48,7 +47,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
var chunk = instantiationDelegate != null ? instantiationDelegate() : new MapChunk(ind.X, ind.Y, size);
IReadOnlyDictionary<ushort, string>? tileMap = null;
IReadOnlyDictionary<int, string>? tileMap = null;
if (context is MapSerializationContext serContext)
{
@@ -65,11 +64,14 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
var tileDefinitionManager = dependencies.Resolve<ITileDefinitionManager>();
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = reader.ReadUInt16();
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
@@ -98,6 +100,8 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
var gridNode = new ValueDataNode();
root.Add("tiles", gridNode);
root.Add("version", new ValueDataNode("6"));
gridNode.Value = SerializeTiles(value);
return root;
@@ -106,7 +110,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
private static string SerializeTiles(MapChunk chunk)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 4;
const int structSize = 6;
var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
var barr = new byte[nTiles];

View File

@@ -82,7 +82,7 @@ namespace Robust.Server.Placement
var alignRcv = msg.Align;
var isTile = msg.IsTile;
ushort tileType = 0;
int tileType = 0;
var entityTemplateName = "";
if (isTile) tileType = msg.TileType;
@@ -177,7 +177,7 @@ namespace Robust.Server.Placement
}
}
private void PlaceNewTile(ushort tileType, EntityCoordinates coordinates, NetUserId placingUserId)
private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId placingUserId)
{
if (!coordinates.IsValid(_entityManager)) return;

View File

@@ -6,6 +6,8 @@ using Robust.Shared.GameStates;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Robust.Server.Player
@@ -13,7 +15,7 @@ namespace Robust.Server.Player
/// <summary>
/// Manages each players session when connected to the server.
/// </summary>
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
public interface IPlayerManager : ISharedPlayerManager
{
BoundKeyMap KeyMap { get; }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
namespace Robust.Server.Player

View File

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

View File

@@ -7,6 +7,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;

View File

@@ -15,7 +15,7 @@
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="SpaceWizards.HttpListener" Version="0.1.0" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' == 'True'" /> <!-- Cannot be private since Content.Server/Database/ServerDbManager.cs depends on SQLitePCL.raw -->
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" PrivateAssets="compile" />

View File

@@ -1,7 +1,3 @@
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
@@ -9,6 +5,12 @@ using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace Robust.Server.ServerHub;
@@ -21,12 +23,12 @@ internal sealed class HubManager
private ISawmill _sawmill = default!;
private string? _advertiseUrl;
private string _masterUrl = "";
private IReadOnlyList<string> _hubUrls = Array.Empty<string>();
private TimeSpan _nextPing;
private TimeSpan _interval;
private bool _active;
private bool _firstAdvertisement = true;
private readonly HashSet<string> _hubUrlsAdvertisedTo = new HashSet<string>();
private HttpClient? _httpClient;
public async void Start()
@@ -38,7 +40,10 @@ internal sealed class HubManager
return;
_cfg.OnValueChanged(CVars.HubAdvertiseInterval, UpdateInterval, true);
_cfg.OnValueChanged(CVars.HubMasterUrl, s => _masterUrl = s, true);
_cfg.OnValueChanged(CVars.HubUrls, s => _hubUrls = s.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList()
, true);
var url = _cfg.GetCVar(CVars.HubServerUrl);
if (string.IsNullOrEmpty(url))
@@ -100,32 +105,37 @@ internal sealed class HubManager
DebugTools.AssertNotNull(_advertiseUrl);
DebugTools.AssertNotNull(_httpClient);
var apiUrl = $"{_masterUrl}api/servers/advertise";
try
foreach (var hubUrl in _hubUrls)
{
using var response = await _httpClient!.PostAsJsonAsync(apiUrl, new AdvertiseRequest(_advertiseUrl!));
var apiUrl = $"{hubUrl}api/servers/advertise";
if (!response.IsSuccessStatusCode)
try
{
var errorText = await response.Content.ReadAsStringAsync();
_sawmill.Log(
LogLevel.Error,
"Error status while advertising server: [{StatusCode}] {Response}",
response.StatusCode,
errorText);
return;
}
using var response = await _httpClient!.PostAsJsonAsync(apiUrl, new AdvertiseRequest(_advertiseUrl!));
if (_firstAdvertisement)
{
_sawmill.Info("Successfully advertised to hub with address {ServerHubAddress}", _advertiseUrl);
_firstAdvertisement = false;
if (!response.IsSuccessStatusCode)
{
var errorText = await response.Content.ReadAsStringAsync();
_sawmill.Error("Error status while advertising server: [{StatusCode}] {ErrorText}, to {HubUrl}",
response.StatusCode,
errorText,
hubUrl);
continue;
}
if (!_hubUrlsAdvertisedTo.Contains(hubUrl))
{
_sawmill.Info("Successfully advertised to {HubUrl} with address {AdvertiseUrl}",
hubUrl,
_advertiseUrl);
_hubUrlsAdvertisedTo.Add(hubUrl);
}
}
catch (Exception e)
{
_sawmill.Log(LogLevel.Error, e, "Exception while trying to advertise server to {HubUrl}",
hubUrl);
}
}
catch (Exception e)
{
_sawmill.Log(LogLevel.Error, e, $"Exception while trying to advertise server to hub");
}
}

View File

@@ -1,14 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using Robust.Server.Player;
using Robust.Shared;
@@ -18,6 +7,18 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using HttpListener = SpaceWizards.HttpListener.HttpListener;
using HttpListenerContext = SpaceWizards.HttpListener.HttpListenerContext;
@@ -46,11 +47,11 @@ namespace Robust.Server.ServerStatus
private string? _serverNameCache;
private string? _serverDescCache;
private string[]? _serverTagsCache;
private IReadOnlyList<string> _serverTagsCache = Array.Empty<string>();
public async Task ProcessRequestAsync(HttpListenerContext context)
{
var apiContext = (IStatusHandlerContext) new ContextImpl(context);
var apiContext = (IStatusHandlerContext)new ContextImpl(context);
_httpSawmill.Info(
$"{apiContext.RequestMethod} {apiContext.Url.PathAndQuery} from {apiContext.RemoteEndPoint}");
@@ -110,17 +111,10 @@ namespace Robust.Server.ServerStatus
// Writes/reads of references are atomic in C# so no further synchronization necessary.
_cfg.OnValueChanged(CVars.GameHostName, n => _serverNameCache = n, true);
_cfg.OnValueChanged(CVars.GameDesc, n => _serverDescCache = n, true);
_cfg.OnValueChanged(CVars.HubTags, t =>
{
var tags = t.Split(",", StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < tags.Length; i++)
{
tags[i] = tags[i].Trim();
}
_serverTagsCache = tags;
},
true
);
_cfg.OnValueChanged(CVars.HubTags, t => _serverTagsCache = t.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList(),
true);
if (!_cfg.GetCVar(CVars.StatusEnabled))
{
@@ -287,7 +281,7 @@ namespace Robust.Server.ServerStatus
public void Respond(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
{
Respond(text, (int) code, contentType);
Respond(text, (int)code, contentType);
}
public void Respond(string text, int code = 200, string contentType = MediaTypeNames.Text.Plain)
@@ -307,7 +301,7 @@ namespace Robust.Server.ServerStatus
public void Respond(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
{
Respond(data, (int) code, contentType);
Respond(data, (int)code, contentType);
}
public void Respond(byte[] data, int code = 200, string contentType = MediaTypeNames.Text.Plain)
@@ -330,7 +324,7 @@ namespace Robust.Server.ServerStatus
{
RespondShared();
_context.Response.StatusCode = (int) HttpStatusCode.NoContent;
_context.Response.StatusCode = (int)HttpStatusCode.NoContent;
_context.Response.Close();
return Task.CompletedTask;
@@ -338,7 +332,7 @@ namespace Robust.Server.ServerStatus
public Task RespondAsync(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = "text/plain")
{
return RespondAsync(text, (int) code, contentType);
return RespondAsync(text, (int)code, contentType);
}
public async Task RespondAsync(string text, int code = 200, string contentType = "text/plain")
@@ -358,7 +352,7 @@ namespace Robust.Server.ServerStatus
public Task RespondAsync(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = "text/plain")
{
return RespondAsync(data, (int) code, contentType);
return RespondAsync(data, (int)code, contentType);
}
public async Task RespondAsync(byte[] data, int code = 200, string contentType = "text/plain")
@@ -415,7 +409,7 @@ namespace Robust.Server.ServerStatus
{
RespondShared();
_context.Response.StatusCode = (int) code;
_context.Response.StatusCode = (int)code;
return Task.FromResult(_context.Response.OutputStream);
}

View File

@@ -51,6 +51,8 @@ tags = ""
# want to use HTTPS (with a reverse proxy), or other advanced scenarios.
# Must be in the form of an ss14:// or ss14s:// URI pointing to the status API.
server_url = ""
# Comma-separated list of URLs of hub servers to advertise to.
hub_urls = "https://central.spacestation14.io/hub/"
[build]
# *Absolutely all of these can be supplied using a "build.json" file*

View File

@@ -148,6 +148,7 @@ namespace Robust.Shared.CompNetworkGenerator
}
return $@"// <auto-generated />
using System;
using Robust.Shared.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.Analyzers;
@@ -157,7 +158,7 @@ namespace {nameSpace};
public partial class {componentName}
{{
[Serializable, NetSerializable]
[System.Serializable, NetSerializable]
public class {stateName} : ComponentState
{{{stateFields}
}}

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