Compare commits

..

8 Commits

Author SHA1 Message Date
Pieter-Jan Briers
0081bfe540 Version: 174.0.4 2024-08-11 19:33:07 +02:00
Pieter-Jan Briers
6a9792223d 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
d49f27a3bb Version: 174.0.3 2024-08-11 18:04:18 +02:00
Pieter-Jan Briers
7ebd4e39be 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:18 +02:00
Pieter-Jan Briers
ef1fd698a0 Version: 174.0.2 2024-03-10 21:28:11 +01:00
Pieter-Jan Briers
a253c18e01 global.json force .NET 7
(cherry picked from commit 6211cf2e03)
2024-03-10 21:28:04 +01:00
Pieter-Jan Briers
4641634011 Version: 174.0.1 2024-03-10 20:50:30 +01:00
Pieter-Jan Briers
5d93618442 Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 688efac67b634c613539b783a9fb6e679948cd53)
2024-03-10 20:50:30 +01:00
160 changed files with 2590 additions and 4158 deletions

View File

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

3
.gitmodules vendored
View File

@@ -13,6 +13,3 @@
[submodule "cefglue"]
path = cefglue
url = https://github.com/space-wizards/cefglue.git
[submodule "Arch/Arch"]
path = Arch/Arch
url = https://github.com/space-wizards/Arch.git

Submodule Arch/Arch deleted from c76d18feb7

View File

@@ -1,94 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<Nullable>enable</Nullable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<PackageId>Arch</PackageId>
<Title>Arch</Title>
<Version>1.2.7.1-alpha</Version>
<Authors>genaray</Authors>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Description>A high performance c# net.6 and net.7 archetype based ECS ( Entity component system ).</Description>
<PackageReleaseNotes>Updated LowLevel which fixes bugs. </PackageReleaseNotes>
<PackageTags>c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;stride;unity;godot;</PackageTags>
<PackageProjectUrl>https://github.com/genaray/Arch</PackageProjectUrl>
<RepositoryUrl>https://github.com/genaray/Arch.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<IsPackable>true</IsPackable>
<LangVersion>11</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Copyright>Apache2.0</Copyright>
<NoWarn>1701;1702;1591</NoWarn>
<Configurations>Debug;Debug-PureECS;Debug-Events;Release;Release-PureECS;Release-Events;</Configurations>
<AssemblyName>Arch</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DefaultItemExcludes>src/Arch/**/*</DefaultItemExcludes>
<DefineConstants>$(DefineConstants);PURE_ECS;CONTRACTS_FULL</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>$(DefineConstants);PURE_ECS;CONTRACTS_FULL;TRACE;</DefineConstants>
<Optimize>false</Optimize>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Arch.Benchmarks" />
<InternalsVisibleTo Include="Arch.Tests" />
</ItemGroup>
<ItemGroup>
<Using Include="System" />
<Using Include="System.Collections" />
<Using Include="System.Collections.Generic" />
<Using Include="System.Diagnostics" />
<Using Include="System.Diagnostics.CodeAnalysis" />
<Using Include="System.IO" />
<Using Include="System.Linq" />
<Using Include="System.Runtime.CompilerServices" />
<Using Include="System.Runtime.InteropServices" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include=".\Arch\src\Arch.SourceGen\Arch.SourceGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Arch.LowLevel" Version="1.1.0" />
<PackageReference Include="Collections.Pooled" Version="2.0.0-preview.27" />
<PackageReference Include="CommunityToolkit.HighPerformance" Version="7.1.2" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="ZeroAllocJobScheduler" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<Compile Include="Arch\src\Arch\**\*.cs">
<Link>Arch\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="Arch\src\Arch\obj\**\*.cs" />
<InternalsVisibleTo Include="Arch.Benchmarks" />
<InternalsVisibleTo Include="Arch.Tests" />
</ItemGroup>
<Import Project="../MSBuild/Robust.Properties.targets" />
</Project>

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.1.1" />
<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

@@ -54,192 +54,16 @@ END TEMPLATE-->
*None yet*
## 182.0.0
## 174.0.4
### Breaking changes
* Add EntityUid's generation / version to the hashcode.
## 174.0.3
## 181.0.2
## 174.0.2
### Bugfixes
* Fix exceptions from having too many lights on screen and causing the game to go black.
* Fix components having events raised in ClientGameStateManager before fully set and causing nullable reference exceptions.
* Replace tile intersection IEnumerables with TileEnumerator internally. Also made it public for external callers that wish to avoid IEnumerable.
## 181.0.1
### Bugfixes
* Fix the non-generic HasComp and add a test for good measure.
## 181.0.0
### Breaking changes
- Arch is merged refactoring how components are stored on engine. There's minimal changes on the API end to facilitate component nullability with much internal refactoring.
## 180.2.1
## 180.2.0
### New features
* Add EnsureEntity variants that take in collections.
* Add more MapSystem helper methods.
### Internal
* Cache some more PVS data to avoid re-allocating every tick.
## 180.1.0
### New features
* Add the map name to lsmap.
* Add net.pool_size to CVars to control the message data pool size in Lidgren and to also toggle pooling.
### Bugfixes
* Fix physics contraints causing enormous heap allocations.
* Fix potential error when writing a runtime log.
* Fix shape lookups for non-hard fixtures in EntityLookupSystem from 180.0.0
## 180.0.0
### Breaking changes
* Removed some obsolete methods from EntityLookupSystem.
### New features
* PhysicsSystem.TryGetNearest now supports chain shapes.
* Add IPhysShape methods to EntityLookupSystem rather than relying on AABB checks.
* Add some more helper methods to SharedTransformSystem.
* Add GetOrNew dictionary extension that also returns a bool on whether the key existed.
* Add a GetAnchoredEntities overload that takes in a list.
### Other
* Use NetEntities for the F3 debug panel to align with command usage.
## 179.0.0
### Breaking changes
* EyeComponent.Eye is no longer nullable
### New features
* Light rendering can now be enabled or disable per eye.
### Bugfixes
* Deserializing old maps with empty grid chunks should now just ignore those chunks.
### Other
* UnknownPrototypeException now also tells you the prototype kind instead of just the unkown ID.
* Adding or removing networked components while resetting predicted entities now results in a more informative exception.
## 178.0.0
### Breaking changes
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
* Several actor/player related components and events have been moved to shared.
### New features
* Added `NetListAsArray<T>.Value` to the sandbox whitelist
## 177.0.0
### Breaking changes
* Removed toInsertXform and added containerXform in SharedContainerSystem.CanInsert.
* Removed EntityQuery parameters from SharedContainerSystem.IsEntityOrParentInContainer.
* Changed the signature of ContainsEntity in SharedTransformSystem to use Entity<T>.
* Removed one obsoleted SharedTransformSystem.AnchorEntity method.
* Changed signature of SharedTransformSystem.SetCoordinates to use Entity<T>.
### New features
* Added more Entity<T> query methods.
* Added BeforeApplyState event to replay playback.
### Bugfixes
* Fixed inverted GetAllMapGrids map id check.
* Fixed transform test warnings.
* Fixed PlacementManager warnings.
* Fixed reparenting bug for entities that are being deleted.
### Other
* Changed VerticalAlignment of RichTextLabel to Center to be consistent with Label.
* Changed PVS error log to be a warning instead.
* Marked insert and remove container methods as obsolete, added container system methods to replace them.
* Marked TransformComponent.MapPosition as obsolete, added GetMapCoordinates system method to replace it.
### Internal
* Moved TryGetUi/TryToggleUi/ToggleUi/TryOpen/OpenUi/TryClose/CloseUi methods from UserInterfaceSystem to SharedUserInterfaceSystem.
## 176.0.0
### Breaking changes
* Reverted audio rework temporarily until packaging is fixed.
* Changes to Robust.Packaging to facilitate Content.Packaging ports from the python packaging scripts.
### New features
* Add a cvar for max game state buffer size.
* Add an overload for GetEntitiesInRange that takes in a set.
### Bugfixes
* Fix PVS initial list capacity always being 0.
* Fix replay lerp error spam.
## 175.0.0
### Breaking changes
* Removed static SoundSystem.Play methods.
* Moved IPlayingAudioStream onto AudioComponent and entities instead of an abstract stream.
* IResourceCache is in shared and IClientResourceCache is the client version to use for textures.
* Default audio attenuation changed from InverseDistanceClamped to LinearDistanceClamped.
* Removed per-source audio attenuation.
### New features
* Add preliminary support for EFX Reverb presets + auxiliary slots; these are also entities.
* Audio on grid entities is now attached to the grid.
### Bugfixes
* If an audio entity comes into PVS range its track will start at the relevant offset and not the beginning.
* Z-Axis offset is considered for ReferenceDistance / MaxDistance for audio.
* Audio will now pause if the attached entity is paused.
### Other
* Changed audio Z-Axis offset from -5m to -1m.
## 174.0.1
## 174.0.0

View File

@@ -2203,207 +2203,3 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- name: Arch
license: |
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Lars Matthäus/genaray
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,177 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using Arch.Core;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using Robust.Shared.Analyzers;
using static Robust.Benchmarks.EntityManager.ArchetypeComponentAccessBenchmark;
namespace Robust.Benchmarks.Arch;
[MemoryDiagnoser]
[Virtual]
public class ArchComponentAccessBenchmark
{
private const int N = 10000;
private static readonly Consumer Consumer = new();
private Entity _entity;
private World _world = default!;
private QueryDescription _singleQuery;
private QueryDescription _tenQuery;
[GlobalSetup]
public void GlobalSetup()
{
var _ = new JobScheduler.JobScheduler("ArchBenchmark");
_world = World.Create();
for (var i = 0; i < N; i++)
{
var entity = _world.Create();
// Randomly chosen id
if (entity.Id == 1584)
_entity = entity;
_world.Add(
entity,
new Struct1(),
new Struct2(),
new Struct3(),
new Struct4(),
new Struct5(),
new Struct6(),
new Struct7(),
new Struct8(),
new Struct9(),
new Struct10()
);
}
_singleQuery = new QueryDescription().WithAll<Struct1>();
_tenQuery = new QueryDescription().WithAll<Struct1, Struct2, Struct3, Struct4, Struct5, Struct6, Struct7, Struct8, Struct9, Struct10>();
}
[GlobalCleanup]
public void GlobalCleanup()
{
JobScheduler.JobScheduler.Instance.Dispose();
Environment.Exit(0);
}
[Benchmark]
public Struct1 GetSingle()
{
return _world.Get<Struct1>(_entity);
}
[Benchmark]
public (Struct1, Struct2, Struct3, Struct4, Struct5, Struct6, Struct7, Struct8, Struct9, Struct10)
GetTen()
{
return (
_world.Get<Struct1>(_entity),
_world.Get<Struct2>(_entity),
_world.Get<Struct3>(_entity),
_world.Get<Struct4>(_entity),
_world.Get<Struct5>(_entity),
_world.Get<Struct6>(_entity),
_world.Get<Struct7>(_entity),
_world.Get<Struct8>(_entity),
_world.Get<Struct9>(_entity),
_world.Get<Struct10>(_entity)
);
}
[Benchmark]
public bool HasSingle()
{
return _world.Has<Struct1>(_entity);
}
[Benchmark]
public bool HasTen()
{
return _world.Has<Struct1>(_entity) &&
_world.Has<Struct2>(_entity) &&
_world.Has<Struct3>(_entity) &&
_world.Has<Struct4>(_entity) &&
_world.Has<Struct5>(_entity) &&
_world.Has<Struct6>(_entity) &&
_world.Has<Struct7>(_entity) &&
_world.Has<Struct8>(_entity) &&
_world.Has<Struct9>(_entity) &&
_world.Has<Struct10>(_entity);
}
[Benchmark]
public void IterateSingle()
{
_world.Query(_singleQuery, static (ref Struct1 s) => Consumer.Consume(s));
}
[Benchmark]
public void IterateSingleInline()
{
_world.InlineQuery<QueryConsumer>(_singleQuery);
}
[Benchmark]
public void IterateSingleParallel()
{
_world.ParallelQuery(_singleQuery, static (ref Struct1 s) => Consumer.Consume(s));
}
[Benchmark]
public void IterateSingleInlineParallel()
{
_world.InlineParallelQuery<QueryConsumer>(_singleQuery);
}
[Benchmark]
public void IterateTen()
{
_world.Query(_tenQuery,
static (
ref Struct1 s1, ref Struct2 s2, ref Struct3 s3, ref Struct4 s4,
ref Struct5 s5, ref Struct6 s6, ref Struct7 s7, ref Struct8 s8,
ref Struct9 s9, ref Struct10 s10) =>
Consumer.Consume((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)));
}
[Benchmark]
public void IterateTenInline()
{
_world.InlineQuery<QueryConsumer>(_tenQuery);
}
[Benchmark]
public void IterateTenParallel()
{
_world.ParallelQuery(_tenQuery,
static (
ref Struct1 s1, ref Struct2 s2, ref Struct3 s3, ref Struct4 s4,
ref Struct5 s5, ref Struct6 s6, ref Struct7 s7, ref Struct8 s8,
ref Struct9 s9, ref Struct10 s10) =>
Consumer.Consume((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)));
}
[Benchmark]
public void IterateTenInlineParallel()
{
_world.InlineParallelQuery<QueryConsumer>(_tenQuery);
}
private struct QueryConsumer : IForEach
{
private static readonly Consumer Consumer = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(Entity entity)
{
Consumer.Consume(entity);
}
}
}

View File

@@ -41,7 +41,7 @@ public partial class AddRemoveComponentBenchmark
{
for (var i = 2; i <= N+1; i++)
{
var uid = new EntityUid(i, -1);
var uid = new EntityUid(i);
_entityManager.AddComponent<A>(uid);
_entityManager.RemoveComponent<A>(uid);
}

View File

@@ -57,7 +57,7 @@ public class ComponentIndexBenchmark
private static class CompArrayIndex<T>
{
// ReSharper disable once StaticMemberInGenericType
public static readonly CompIdx Idx = new(Interlocked.Increment(ref _compIndexMaster), typeof(T));
public static readonly CompIdx Idx = new(Interlocked.Increment(ref _compIndexMaster));
}
private static CompIdx GetCompIdIndex(Type type)

View File

@@ -46,7 +46,7 @@ public partial class GetComponentBenchmark
{
for (var i = 2; i <= N+1; i++)
{
Comps[i] = _entityManager.GetComponent<A>(new EntityUid(i, -1));
Comps[i] = _entityManager.GetComponent<A>(new EntityUid(i));
}
// Return something so the JIT doesn't optimize out all the GetComponent calls.

View File

@@ -8,7 +8,6 @@
<NoWarn>RA0003</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Arch\Arch.csproj" />
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />

View File

@@ -256,7 +256,7 @@ namespace Robust.Client.Console.Commands
return;
}
var uid = EntityUid.Parse(args[0], "-1");
var uid = EntityUid.Parse(args[0]);
var entmgr = _entityManager;
if (!entmgr.EntityExists(uid))
{

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

@@ -43,7 +43,6 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
GC.Collect();
Span<EntityUid> ents = stackalloc EntityUid[amount];
var stopwatch = new Stopwatch();
stopwatch.Start();
@@ -51,17 +50,12 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
for (var i = 0; i < amount; i++)
{
ents[i] = _entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
_entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
}
MeasureProfiler.SaveData();
shell.WriteLine($"Client: Profiled spawning {amount} entities in {stopwatch.Elapsed.TotalMilliseconds:N3} ms");
foreach (var ent in ents)
{
_entities.DeleteEntity(ent);
}
}
}
#endif

View File

@@ -44,7 +44,7 @@ namespace Robust.Client.GameObjects
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, out MetaDataComponent metadata)
{
return base.CreateEntity(prototypeName, out metadata, out _);
return base.CreateEntity(prototypeName, out metadata);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)

View File

@@ -1,8 +1,9 @@
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Player;
namespace Robust.Client.GameObjects;
@@ -25,13 +26,17 @@ public sealed class EyeSystem : SharedEyeSystem
private void OnEyeAutoState(EntityUid uid, EyeComponent component, ref AfterAutoHandleStateEvent args)
{
UpdateEye((uid, component));
UpdateEye(component);
}
private void OnEyeAttached(EntityUid uid, EyeComponent component, LocalPlayerAttachedEvent args)
{
UpdateEye((uid, component));
_eyeManager.CurrentEye = component.Eye;
// TODO: This probably shouldn't be nullable bruv.
if (component._eye != null)
{
_eyeManager.CurrentEye = component._eye;
}
var ev = new EyeAttachedEvent(uid, component);
RaiseLocalEvent(uid, ref ev, true);
}
@@ -43,7 +48,13 @@ public sealed class EyeSystem : SharedEyeSystem
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
{
UpdateEye((uid, component));
component._eye = new Eye
{
Position = Transform(uid).MapPosition,
Zoom = component.Zoom,
DrawFov = component.DrawFov,
Rotation = component.Rotation,
};
}
/// <inheritdoc />
@@ -53,7 +64,7 @@ public sealed class EyeSystem : SharedEyeSystem
while (query.MoveNext(out var uid, out var eyeComponent))
{
if (eyeComponent.Eye == null)
if (eyeComponent._eye == null)
continue;
if (!TryComp<TransformComponent>(eyeComponent.Target, out var xform))
@@ -62,7 +73,7 @@ public sealed class EyeSystem : SharedEyeSystem
eyeComponent.Target = null;
}
eyeComponent.Eye.Position = xform.MapPosition;
eyeComponent._eye.Position = xform.MapPosition;
}
}
}

View File

@@ -1,9 +1,12 @@
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.Physics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Client.GameObjects
{
@@ -25,5 +28,10 @@ namespace Robust.Client.GameObjects
base.Shutdown();
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
}
}

View File

@@ -54,10 +54,9 @@ namespace Robust.Client.GameObjects
// should show the entity lerping.
// - If the client predicts an entity will move while already lerping due to a state-application, it should
// clear the state's lerp, under the assumption that the client predicted the state and already rendered
// the entity in the state's final position.
// the entity in the final position.
// - If the client predicts that an entity moves, then we only lerp if this is the first time that the tick
// was predicted. I.e., we assume the entity was already rendered in the final position that was
// previously predicted.
// was predicted. I.e., we assume the entity was already rendered in it's final of that lerp.
// - If the client predicts that an entity should lerp twice in the same tick, then we need to combine them.
// I.e. moving from a->b then b->c, the client should lerp from a->c.

View File

@@ -39,7 +39,7 @@ public sealed class ClientDirtySystem : EntitySystem
private void OnTerminate(ref EntityTerminatingEvent ev)
{
if (!_timing.InPrediction || IsClientSide(ev.Entity, ev.Metadata))
if (!_timing.InPrediction || IsClientSide(ev.Entity))
return;
// Client-side entity deletion is not supported and will cause errors.

View File

@@ -5,8 +5,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using Arch.Core;
using Collections.Pooled;
using JetBrains.Annotations;
using Microsoft.Extensions.ObjectPool;
using Robust.Client.GameObjects;
@@ -31,9 +29,7 @@ using Robust.Shared.Network.Messages;
using Robust.Shared.Profiling;
using Robust.Shared.Replays;
using Robust.Shared.Timing;
using Robust.Shared.Toolshed.TypeParsers;
using Robust.Shared.Utility;
using ComponentType = Arch.Core.Utils.ComponentType;
namespace Robust.Client.GameStates
{
@@ -56,12 +52,11 @@ namespace Robust.Client.GameStates
private readonly Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)> _compStateWork = new();
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
private readonly HashSet<NetEntity> _stateEnts = new();
private readonly List<EntityUid> _toDelete = new();
private readonly List<IComponent> _toRemove = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _outputData = new();
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
private readonly List<NetEntity> _created = new();
private readonly List<NetEntity> _detached = new();
private readonly ObjectPool<Dictionary<ushort, ComponentState>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, ComponentState>>(new DictPolicy<ushort, ComponentState>(), 256);
@@ -129,8 +124,6 @@ namespace Robust.Client.GameStates
public bool DropStates;
#endif
private bool _resettingPredictedEntities;
/// <inheritdoc />
public void Initialize()
{
@@ -153,7 +146,6 @@ namespace Robust.Client.GameStates
_config.OnValueChanged(CVars.NetPredictLagBias, i => PredictLagBias = i, true);
_config.OnValueChanged(CVars.NetStateBufMergeThreshold, i => StateBufferMergeThreshold = i, true);
_config.OnValueChanged(CVars.NetPVSEntityExitBudget, i => _pvsDetachBudget = i, true);
_config.OnValueChanged(CVars.NetMaxBufferSize, i => _processor.MaxBufferSize = i, true);
_processor.Interpolation = _config.GetCVar(CVars.NetInterp);
_processor.BufferSize = _config.GetCVar(CVars.NetBufferSize);
@@ -168,8 +160,6 @@ namespace Robust.Client.GameStates
_conHost.RegisterCommand("localdelete", Loc.GetString("cmd-local-delete-desc"), Loc.GetString("cmd-local-delete-help"), LocalDeleteEntCommand);
_conHost.RegisterCommand("fullstatereset", Loc.GetString("cmd-full-state-reset-desc"), Loc.GetString("cmd-full-state-reset-help"), (_,_,_) => RequestFullState());
_entities.ComponentAdded += OnComponentAdded;
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
if (!metaId.HasValue)
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
@@ -177,23 +167,6 @@ namespace Robust.Client.GameStates
_metaCompNetId = metaId.Value;
}
private void OnComponentAdded(AddedComponentEventArgs args)
{
if (_resettingPredictedEntities)
{
var comp = args.ComponentType;
if (comp.NetID == null)
return;
_sawmill.Error($"""
Added component {comp.Name} with net id {comp.NetID} while resetting predicted entities.
Stack trace:
{Environment.StackTrace}
""");
}
}
/// <inheritdoc />
public void Reset()
{
@@ -545,9 +518,7 @@ namespace Robust.Client.GameStates
var countReset = 0;
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
using var toRemove = new PooledList<IComponent>();
using var toAdd = new PooledList<ushort>();
using var toAddStates = new PooledList<ComponentState>();
RemQueue<IComponent> toRemove = new();
foreach (var entity in system.DirtyEntities)
{
@@ -565,64 +536,49 @@ namespace Robust.Client.GameStates
countReset += 1;
try
foreach (var (netId, comp) in meta.NetComponents)
{
_resettingPredictedEntities = true;
if (!comp.NetSyncEnabled)
continue;
foreach (var (netId, comp) in meta.NetComponents)
// Was this component added during prediction?
if (comp.CreationTick > _timing.LastRealTick)
{
if (!comp.NetSyncEnabled)
continue;
// Was this component added during prediction?
if (comp.CreationTick > _timing.LastRealTick)
if (last.ContainsKey(netId))
{
if (last.ContainsKey(netId))
{
// Component was probably removed and then re-addedd during a single prediction run
// Just reset state as normal.
comp.ClearCreationTick();
}
else
{
toRemove.Add(comp);
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A new component was added: {comp.GetType()}");
continue;
}
// Component was probably removed and then re-addedd during a single prediction run
// Just reset state as normal.
comp.ClearCreationTick();
}
if (comp.LastModifiedTick <= _timing.LastRealTick ||
!last.TryGetValue(netId, out var compState))
else
{
toRemove.Add(comp);
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A new component was added: {comp.GetType()}");
continue;
}
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
comp.LastModifiedTick = _timing.LastRealTick;
}
}
finally
{
_resettingPredictedEntities = false;
}
if (toRemove.Count > 0)
{
// Remove predicted component additions
// TODO: 1 archetype change.
foreach (var comp in toRemove)
if (comp.LastModifiedTick <= _timing.LastRealTick || !last.TryGetValue(netId, out var compState))
{
_entities.RemoveComponent(entity, comp, meta);
continue;
}
toRemove.Clear();
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
comp.LastModifiedTick = _timing.LastRealTick;
}
// Remove predicted component additions
foreach (var comp in toRemove)
{
_entities.RemoveComponent(entity, comp);
}
toRemove.Clear();
// Re-add predicted removals
if (system.RemovedComponents.TryGetValue(entity, out var netIds))
{
@@ -634,29 +590,15 @@ namespace Robust.Client.GameStates
if (!last.TryGetValue(netId, out var state))
continue;
toAdd.Add(netId);
toAddStates.Add(state);
}
var comp = _entityManager.AddComponent(entity, netId, meta);
if (toAdd.Count > 0)
{
for (var i = 0; i < toAdd.Count; i++)
{
var netId = toAdd[i];
var state = toAddStates[i];
var comp = _entityManager.AddComponent(entity, netId, meta);
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was removed: {comp.GetType()}");
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was removed: {comp.GetType()}");
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
comp.ClearCreationTick(); // don't undo the re-adding.
comp.LastModifiedTick = _timing.LastRealTick;
}
toAdd.Clear();
toAddStates.Clear();
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
comp.ClearCreationTick(); // don't undo the re-adding.
comp.LastModifiedTick = _timing.LastRealTick;
}
}
@@ -739,9 +681,10 @@ namespace Robust.Client.GameStates
_config.TickProcessMessages();
}
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
using (_prof.Group("Entity"))
{
ApplyEntityStates(curState, nextState);
output = ApplyEntityStates(curState, nextState);
}
using (_prof.Group("Player"))
@@ -751,13 +694,13 @@ namespace Robust.Client.GameStates
using (_prof.Group("Callback"))
{
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, _detached));
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, output.Detached));
}
return _created;
return output.Created;
}
private void ApplyEntityStates(GameState curState, GameState? nextState)
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -766,8 +709,6 @@ namespace Robust.Client.GameStates
var enteringPvs = 0;
_toApply.Clear();
_toCreate.Clear();
_detached.Clear();
_created.Clear();
_pendingReapplyNetStates.Clear();
var curSpan = curState.EntityStates.Span;
@@ -792,7 +733,6 @@ namespace Robust.Client.GameStates
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
_toCreate.Add(es.NetEntity, es);
_created.Add(es.NetEntity);
_toApply.Add(uid, (es.NetEntity, newMeta, false, GameTick.Zero, es, null));
// Client creates a client-side net entity for the newly created entity.
@@ -846,7 +786,7 @@ namespace Robust.Client.GameStates
// Detach entities to null space
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
ProcessPvsDeparture(_detached, curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
var detached = ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
// Check next state (AFTER having created new entities introduced in curstate)
if (nextState != null)
@@ -948,6 +888,8 @@ namespace Robust.Client.GameStates
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
return (_toCreate.Keys, detached);
}
/// <inheritdoc />
@@ -978,7 +920,7 @@ namespace Robust.Client.GameStates
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
using var toDelete = new PooledList<EntityUid>();
_toDelete.Clear();
// Client side entities won't need the transform, but that should always be a tiny minority of entities
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
@@ -989,7 +931,7 @@ namespace Robust.Client.GameStates
if (metadata.NetEntity.IsClientSide())
{
if (deleteClientEntities)
toDelete.Add(ent);
_toDelete.Add(ent);
continue;
}
@@ -1016,14 +958,14 @@ namespace Robust.Client.GameStates
&& !deleteClientEntities // don't add duplicates
&& _entities.IsClientSide(child.Value))
{
toDelete.Add(child.Value);
_toDelete.Add(child.Value);
}
}
toDelete.Add(ent);
_toDelete.Add(ent);
}
foreach (var ent in toDelete)
foreach (var ent in _toDelete)
{
_entities.DeleteEntity(ent);
}
@@ -1083,8 +1025,7 @@ namespace Robust.Client.GameStates
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
}
private void ProcessPvsDeparture(
IList<NetEntity> detached,
private List<NetEntity> ProcessPvsDeparture(
GameTick toTick,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
@@ -1092,17 +1033,18 @@ namespace Robust.Client.GameStates
ContainerSystem containerSys,
EntityLookupSystem lookupSys)
{
using var toDetach = new PooledList<(GameTick Tick, List<NetEntity> Entities)>();
_processor.GetEntitiesToDetach(toDetach, toTick, _pvsDetachBudget);
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
var detached = new List<NetEntity>();
if (toDetach.Count == 0)
return;
return detached;
// TODO optimize
// If an entity is leaving PVS, so are all of its children. If we can preserve the hierarchy we can avoid
// things like container insertion and ejection.
using var _ = _prof.Group("Leave PVS");
detached.EnsureCapacity(toDetach.Count);
foreach (var (tick, ents) in toDetach)
{
@@ -1110,6 +1052,7 @@ namespace Robust.Client.GameStates
}
_prof.WriteValue("Count", ProfData.Int32(detached.Count));
return detached;
}
private void Detach(GameTick maxTick,
@@ -1120,7 +1063,7 @@ namespace Robust.Client.GameStates
SharedTransformSystem xformSys,
ContainerSystem containerSys,
EntityLookupSystem lookupSys,
IList<NetEntity>? detached = null)
List<NetEntity>? detached = null)
{
foreach (var netEntity in entities)
{
@@ -1174,7 +1117,7 @@ namespace Robust.Client.GameStates
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
#if EXCEPTION_TOLERANCE
using var brokenEnts = new PooledList<EntityUid>();
var brokenEnts = new List<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
@@ -1238,28 +1181,18 @@ namespace Robust.Client.GameStates
// First remove any deleted components
if (curState?.NetComponents != null)
{
using var toRemove = new PooledList<IComponent>();
using var compTypes = new PooledList<ComponentType>();
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
{
toRemove.Add(comp);
compTypes.Add(comp.GetType());
}
_toRemove.Add(comp);
}
if (toRemove.Count > 0)
foreach (var comp in _toRemove)
{
foreach (var comp in toRemove)
{
_entityManager.RemoveComponentInternal(uid, comp, terminating: false, archetypeChange: false, meta);
}
_entities.RemoveComponent(uid, comp, meta);
}
if (compTypes.Count > 0)
_entityManager.RemoveComponentRange(uid, compTypes);
}
if (enteringPvs)
@@ -1274,7 +1207,7 @@ namespace Robust.Client.GameStates
if (!meta.NetComponents.TryGetValue(id, out var comp))
{
comp = _compFactory.GetComponent(id);
_entityManager.AddComponent(uid, comp, metadata: meta);
_entityManager.AddComponent(uid, comp, true, metadata: meta);
}
_compStateWork[id] = (comp, state, null);
@@ -1282,49 +1215,18 @@ namespace Robust.Client.GameStates
}
else if (curState != null)
{
using var addedComps = new PooledList<IComponent>();
using var addedCompTypes = new PooledList<ComponentType>();
using var addedRegistrations = new PooledList<ComponentRegistration>();
foreach (var compChange in curState.ComponentChanges.Span)
{
if (!meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
{
var registration = _compFactory.GetRegistration(compChange.NetID);
addedRegistrations.Add(registration);
comp = _compFactory.GetComponent(registration);
comp.Owner = uid;
addedComps.Add(comp);
addedCompTypes.Add(comp.GetType());
comp = _compFactory.GetComponent(compChange.NetID);
_entityManager.AddComponent(uid, comp, true, metadata:meta);
}
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
continue;
_compStateWork[compChange.NetID] = (comp, compChange.State, null);
}
// To avoid shuffling the archetype we'll set the component range up-front.
if (addedComps.Count > 0)
{
// TODO: This fucking sucks but
// - Frequent archetype changes PER COMPONENT sucks
// - the components will be null in event handlers until it's done.
_entityManager.AddComponentRange(uid, addedCompTypes);
for (var i = 0; i < addedComps.Count; i++)
{
var component = addedComps[i];
var reg = addedRegistrations[i];
_entityManager.AddComponentInternalOnly(uid, component, reg, meta);
}
for (var i = 0; i < addedComps.Count; i++)
{
var component = addedComps[i];
var reg = addedRegistrations[i];
_entityManager.AddComponentEvents(uid, component, reg, false, meta);
}
}
}
if (nextState != null)
@@ -1414,7 +1316,7 @@ namespace Robust.Client.GameStates
return false;
}
if (!EntityUid.TryParse(args[0], "-1", out uid))
if (!EntityUid.TryParse(args[0], out uid))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-uid", ("arg", args[0])));
meta = null;
@@ -1546,22 +1448,23 @@ namespace Robust.Client.GameStates
if (!meta.NetComponents.TryGetValue(id, out var comp))
{
comp = _compFactory.GetComponent(id);
_entityManager.AddComponent(uid, comp, meta);
_entityManager.AddComponent(uid, comp, true, meta);
}
var handleState = new ComponentHandleState(state, null);
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
}
using var toRemove = new PooledList<IComponent>();
// ensure we don't have any extra components
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !lastState.ContainsKey(id))
toRemove.Add(comp);
_toRemove.Add(comp);
}
foreach (var comp in toRemove)
foreach (var comp in _toRemove)
{
_entities.RemoveComponent(uid, comp);
}
@@ -1574,10 +1477,10 @@ namespace Robust.Client.GameStates
public sealed class GameStateAppliedArgs : EventArgs
{
public readonly GameState AppliedState;
public readonly IReadOnlyList<NetEntity> Detached;
public GameState AppliedState { get; }
public readonly List<NetEntity> Detached;
public GameStateAppliedArgs(GameState appliedState, IReadOnlyList<NetEntity> detached)
public GameStateAppliedArgs(GameState appliedState, List<NetEntity> detached)
{
AppliedState = appliedState;
Detached = detached;

View File

@@ -14,6 +14,8 @@ namespace Robust.Client.GameStates
/// <inheritdoc />
internal sealed class GameStateProcessor : IGameStateProcessor
{
public const int MaxBufferSize = 512;
private readonly IClientGameTiming _timing;
private readonly IClientGameStateManager _state;
private readonly ISawmill _logger;
@@ -26,8 +28,6 @@ namespace Robust.Client.GameStates
public (GameTick Tick, DateTime Time)? LastFullStateRequested { get; private set; } = (GameTick.Zero, DateTime.MaxValue);
private int _bufferSize;
private int _maxBufferSize = 512;
public const int MinimumMaxBufferSize = 256;
/// <summary>
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
@@ -48,14 +48,7 @@ namespace Robust.Client.GameStates
public int BufferSize
{
get => _bufferSize;
set => _bufferSize = Math.Max(value, 0);
}
public int MaxBufferSize
{
get => _maxBufferSize;
// We place a lower bound on the maximum size to avoid spamming servers with full game state requests.
set => _maxBufferSize = Math.Max(value, MinimumMaxBufferSize);
set => _bufferSize = value < 0 ? 0 : value;
}
/// <inheritdoc />
@@ -107,21 +100,21 @@ namespace Robust.Client.GameStates
return true;
}
if (LastFullState == null && state.FromSequence == GameTick.Zero)
if (LastFullState == null && state.FromSequence == GameTick.Zero && state.ToSequence >= LastFullStateRequested!.Value.Tick)
{
if (state.ToSequence >= LastFullStateRequested!.Value.Tick)
{
LastFullState = state;
_logger.Info($"Received Full GameState: to={state.ToSequence}, sz={state.PayloadSize}");
return true;
}
LastFullState = state;
_logger.Info($"Received a late full game state. Received: {state.ToSequence}. Requested: {LastFullStateRequested.Value.Tick}");
if (Logging)
_logger.Info($"Received Full GameState: to={state.ToSequence}, sz={state.PayloadSize}");
return true;
}
if (LastFullState != null && state.ToSequence <= LastFullState.ToSequence)
{
_logger.Info($"While waiting for full, received late GameState with lower to={state.ToSequence} than the last full state={LastFullState.ToSequence}");
if (Logging)
_logger.Info($"While waiting for full, received late GameState with lower to={state.ToSequence} than the last full state={LastFullState.ToSequence}");
return false;
}
@@ -304,8 +297,9 @@ Had full state: {LastFullState != null}"
public void ClearDetachQueue() => _pvsDetachMessages.Clear();
public void GetEntitiesToDetach(IList<(GameTick Tick, List<NetEntity> Entities)> result, GameTick toTick, int budget)
public List<(GameTick Tick, List<NetEntity> Entities)> GetEntitiesToDetach(GameTick toTick, int budget)
{
var result = new List<(GameTick Tick, List<NetEntity> Entities)>();
foreach (var (tick, entities) in _pvsDetachMessages)
{
if (tick > toTick)
@@ -324,6 +318,7 @@ Had full state: {LastFullState != null}"
entities.RemoveRange(index, budget);
break;
}
return result;
}
private bool TryGetDeltaState(out GameState? curState, out GameState? nextState)

View File

@@ -512,7 +512,7 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldAABB, worldBounds);
}
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
ApplyFovToBuffer(viewport, eye);
}

View File

@@ -97,9 +97,6 @@ namespace Robust.Client.Graphics.Clyde
private (PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)[] _lightsToRenderList = default!;
private LightCapacityComparer _lightCap = new();
private ShadowCapacityComparer _shadowCap = new ShadowCapacityComparer();
private unsafe void InitLighting()
{
@@ -335,7 +332,7 @@ namespace Robust.Client.Graphics.Clyde
private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
if (!_lightManager.Enabled || !eye.DrawLight)
if (!_lightManager.Enabled)
{
return;
}
@@ -573,28 +570,6 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
private sealed class LightCapacityComparer : IComparer<(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)>
{
public int Compare(
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) x,
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) y)
{
if (x.light.CastShadows && !y.light.CastShadows) return 1;
if (!x.light.CastShadows && y.light.CastShadows) return -1;
return 0;
}
}
private sealed class ShadowCapacityComparer : IComparer<(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)>
{
public int Compare(
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) x,
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) y)
{
return x.distanceSquared.CompareTo(y.distanceSquared);
}
}
private (int count, Box2 expandedBounds) GetLightsToRender(
MapId map,
in Box2Rotated worldBounds,
@@ -620,10 +595,20 @@ namespace Robust.Client.Graphics.Clyde
// First, partition the array based on whether the lights are shadow casting or not
// (non shadow casting lights should be the first partition, shadow casting lights the second)
Array.Sort(_lightsToRenderList, 0, state.count, _lightCap);
Array.Sort(_lightsToRenderList, 0, state.count,
Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
if (x.light.CastShadows && !y.light.CastShadows) return 1;
else if (!x.light.CastShadows && y.light.CastShadows) return -1;
else return 0;
}));
// Next, sort just the shadow casting lights by distance.
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount, _shadowCap);
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount,
Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
return x.distanceSquared.CompareTo(y.distanceSquared);
}));
// Then effectively delete the furthest lights, by setting the end of the array to exclude N
// number of shadow casting lights (where N is the number above the max number per scene.)

View File

@@ -42,20 +42,15 @@ public sealed class TileEdgeOverlay : Overlay
_grids.Clear();
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
var mapSystem = _entManager.System<SharedMapSystem>();
var xformSystem = _entManager.System<SharedTransformSystem>();
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
foreach (var grid in _grids)
{
var tileSize = grid.Comp.TileSize;
var tileDimensions = new Vector2(tileSize, tileSize);
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(grid.Owner);
args.WorldHandle.SetTransform(worldMatrix);
var localAABB = invMatrix.TransformBox(args.WorldBounds);
var xform = xformQuery.GetComponent(grid);
args.WorldHandle.SetTransform(xform.WorldMatrix);
var enumerator = mapSystem.GetLocalTilesEnumerator(grid.Owner, grid.Comp, localAABB, false);
while (enumerator.MoveNext(out var tileRef))
foreach (var tileRef in grid.Comp.GetTilesIntersecting(args.WorldBounds, false))
{
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
@@ -71,7 +66,7 @@ public sealed class TileEdgeOverlay : Overlay
continue;
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
var neighborTile = mapSystem.GetTileRef(grid.Owner, grid.Comp, neighborIndices);
var neighborTile = grid.Comp.GetTileRef(neighborIndices);
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
// If it's the same tile then no edge to be drawn.
@@ -123,9 +118,9 @@ public sealed class TileEdgeOverlay : Overlay
}
if (angle == Angle.Zero)
args.WorldHandle.DrawTextureRect(texture.Texture, box);
args.WorldHandle.DrawTextureRect(texture, box);
else
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
args.WorldHandle.DrawTextureRect(texture, new Box2Rotated(box, angle, box.Center));
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Buffers;
using System.Collections.Generic;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
@@ -7,7 +8,6 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Client.Physics;

View File

@@ -44,7 +44,7 @@ namespace Robust.Client.Placement.Modes
var closestEntity = snapToEntities[0];
var closestTransform = pManager.EntityManager.GetComponent<TransformComponent>(closestEntity);
if (!pManager.EntityManager.TryGetComponent(closestEntity, out SpriteComponent? component) || component.BaseRSI == null)
if (!pManager.EntityManager.TryGetComponent<SpriteComponent?>(closestEntity, out var component) || component.BaseRSI == null)
{
return;
}

View File

@@ -15,22 +15,15 @@ public interface IPlayerManager : ISharedPlayerManager
event Action? PlayerListUpdated;
/// <summary>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets attached to a new entity, or when the local
/// session gets updated. See also <see cref="LocalPlayerAttachedEvent"/>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets attached to a new entity. See also <see cref="LocalPlayerAttachedEvent"/>
/// </summary>
event Action<EntityUid>? LocalPlayerAttached;
/// <summary>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets detached from an entity, or when the local
/// session gets updated. See also <see cref="LocalPlayerDetachedEvent"/>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets detached from new entity. See also <see cref="LocalPlayerDetachedEvent"/>
/// </summary>
event Action<EntityUid>? LocalPlayerDetached;
/// <summary>
/// Invoked whenever <see cref="ISharedPlayerManager.LocalSession"/> changes.
/// </summary>
event Action<(ICommonSession? Old, ICommonSession? New)>? LocalSessionChanged;
void ApplyPlayerStates(IReadOnlyCollection<SessionState> list);
/// <summary>
@@ -45,8 +38,34 @@ public interface IPlayerManager : ISharedPlayerManager
/// </summary>
void SetupMultiplayer(INetChannel channel);
void SetLocalSession(ICommonSession session);
[Obsolete("Use LocalSession instead")]
LocalPlayer? LocalPlayer { get;}
}
/// <summary>
/// ECS event that gets raised when the local player gets attached to a new entity. The event is both broadcast and
/// raised directed at the new entity.
/// </summary>
public sealed class LocalPlayerAttachedEvent : EntityEventArgs
{
public LocalPlayerAttachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}
/// <summary>
/// ECS event that gets raised when the local player gets detached from an entity. The event is both broadcast and
/// raised directed at the new entity.
/// </summary>
public sealed class LocalPlayerDetachedEvent : EntityEventArgs
{
public LocalPlayerDetachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}

View File

@@ -42,13 +42,12 @@ namespace Robust.Client.Player
/// <inheritdoc />
public override int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? -1;
public LocalPlayer? LocalPlayer { get; private set; }
public LocalPlayer? LocalPlayer { get; set; }
public event Action<SessionStatusEventArgs>? LocalStatusChanged;
public event Action? PlayerListUpdated;
public event Action<EntityUid>? LocalPlayerDetached;
public event Action<EntityUid>? LocalPlayerAttached;
public event Action<(ICommonSession? Old, ICommonSession? New)>? LocalSessionChanged;
/// <inheritdoc />
public override void Initialize(int maxPlayers)
@@ -65,14 +64,22 @@ namespace Robust.Client.Player
LocalStatusChanged?.Invoke(e);
}
/// <inheritdoc />
public override void Startup()
{
if (LocalSession == null)
throw new InvalidOperationException("LocalSession cannot be null");
LocalPlayer = new LocalPlayer(LocalSession);
base.Startup();
}
public void SetupSinglePlayer(string name)
{
if (LocalSession != null)
throw new InvalidOperationException($"Player manager already running?");
var session = CreateAndAddSession(default, name);
session.ClientSide = true;
SetLocalSession(session);
LocalSession = CreateAndAddSession(default, name);
Startup();
PlayerListUpdated?.Invoke();
}
@@ -82,44 +89,18 @@ namespace Robust.Client.Player
if (LocalSession != null)
throw new InvalidOperationException($"Player manager already running?");
SetLocalSession(CreateAndAddSession(channel));
var session = CreateAndAddSession(channel.UserId, channel.UserName);
session.Channel = channel;
LocalSession = session;
Startup();
_network.ClientSendMessage(new MsgPlayerListReq());
}
public void SetLocalSession(ICommonSession? session)
{
if (session == LocalSession)
return;
var old = LocalSession;
if (old?.AttachedEntity is {} oldUid)
{
LocalSession = null;
LocalPlayer = null;
Sawmill.Info($"Detaching local player from {EntManager.ToPrettyString(oldUid)}.");
EntManager.EventBus.RaiseLocalEvent(oldUid, new LocalPlayerDetachedEvent(oldUid), true);
LocalPlayerDetached?.Invoke(oldUid);
}
LocalSession = session;
LocalPlayer = session == null ? null : new LocalPlayer(session);
Sawmill.Info($"Changing local session from {old?.ToString() ?? "null"} to {session?.ToString() ?? "null"}.");
LocalSessionChanged?.Invoke((old, LocalSession));
if (session?.AttachedEntity is {} newUid)
{
Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(newUid)}.");
EntManager.EventBus.RaiseLocalEvent(newUid, new LocalPlayerAttachedEvent(newUid), true);
LocalPlayerAttached?.Invoke(newUid);
}
}
/// <inheritdoc />
public override void Shutdown()
{
SetAttachedEntity(LocalSession, null, out _);
if (LocalSession != null)
SetAttachedEntity(LocalSession, null);
LocalPlayer = null;
LocalSession = null;
_pendingStates.Clear();
@@ -127,21 +108,16 @@ namespace Robust.Client.Player
PlayerListUpdated?.Invoke();
}
public override bool SetAttachedEntity(ICommonSession? session, EntityUid? uid, out ICommonSession? kicked, bool force = false)
public override void SetAttachedEntity(ICommonSession session, EntityUid? uid)
{
kicked = null;
if (session == null)
return false;
if (session.AttachedEntity == uid)
return true;
return;
var old = session.AttachedEntity;
if (!base.SetAttachedEntity(session, uid, out kicked, force))
return false;
base.SetAttachedEntity(session, uid);
if (session != LocalSession)
return true;
return;
if (old.HasValue)
{
@@ -153,13 +129,13 @@ namespace Robust.Client.Player
if (uid == null)
{
Sawmill.Info($"Local player is no longer attached to any entity.");
return true;
return;
}
if (!EntManager.EntityExists(uid))
{
Sawmill.Error($"Attempted to attach player to non-existent entity {uid}!");
return true;
return;
}
if (!EntManager.EnsureComponent(uid.Value, out EyeComponent eye))
@@ -172,7 +148,6 @@ namespace Robust.Client.Player
Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(uid)}.");
EntManager.EventBus.RaiseLocalEvent(uid.Value, new LocalPlayerAttachedEvent(uid.Value), true);
LocalPlayerAttached?.Invoke(uid.Value);
return true;
}
public void ApplyPlayerStates(IReadOnlyCollection<SessionState> list)
@@ -218,7 +193,7 @@ namespace Robust.Client.Player
_pendingStates.Remove(state.UserId);
}
SetAttachedEntity(LocalSession, uid, out _, true);
SetAttachedEntity(LocalSession, uid);
SetStatus(LocalSession, state.Status);
}
@@ -258,10 +233,11 @@ namespace Robust.Client.Player
{
// This is a new userid, so we create a new session.
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
var newSession = (CommonSession) CreateAndAddSession(state.UserId, state.Name);
var newSession = CreateAndAddSession(state.UserId, state.Name);
newSession.Ping = state.Ping;
newSession.Name = state.Name;
SetStatus(newSession, state.Status);
SetAttachedEntity(newSession, controlled, out _, true);
SetAttachedEntity(newSession, controlled);
dirty = true;
continue;
}
@@ -280,7 +256,7 @@ namespace Robust.Client.Player
local.Name = state.Name;
local.Ping = state.Ping;
SetStatus(local, state.Status);
SetAttachedEntity(local, controlled, out _, true);
SetAttachedEntity(local, controlled);
}
// Remove old users. This only works if the provided state is a list of all players
@@ -288,12 +264,10 @@ namespace Robust.Client.Player
{
foreach (var oldUser in InternalSessions.Keys.ToArray())
{
// clear slot, player left
if (users.Contains(oldUser))
continue;
if (InternalSessions[oldUser].ClientSide)
continue;
DebugTools.Assert(oldUser != LocalUser
|| LocalUser == null
|| LocalUser == default(NetUserId),

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Replays;
using Robust.Shared.Serialization.Markdown.Mapping;
@@ -129,11 +128,6 @@ public interface IReplayPlaybackManager
/// </summary>
event Action? ReplayUnpaused;
/// <summary>
/// Invoked just before a replay applies a game state.
/// </summary>
event Action<(GameState Current, GameState? Next)>? BeforeApplyState;
/// <summary>
/// If currently replaying a client-side recording, this is the user that recorded the replay.
/// Useful for setting default observer spawn positions.
@@ -143,5 +137,5 @@ public interface IReplayPlaybackManager
/// <summary>
/// Fetches the entity that the <see cref="Recorder"/> is currently attached to.
/// </summary>
bool TryGetRecorderEntity([NotNullWhen(true)] out EntityUid? uid);
public bool TryGetRecorderEntity([NotNullWhen(true)] out EntityUid? uid);
}

View File

@@ -66,7 +66,6 @@ internal sealed partial class ReplayPlaybackManager
_gameState.ClearDetachQueue();
EnsureDetachedExist(checkpoint);
_gameState.DetachImmediate(checkpoint.Detached);
BeforeApplyState?.Invoke((checkpoint.State, next));
_gameState.ApplyGameState(checkpoint.State, next);
}

View File

@@ -1,5 +1,4 @@
using System;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Shared.Utility;
@@ -59,13 +58,7 @@ internal sealed partial class ReplayPlaybackManager
_timing.LastRealTick = _timing.LastProcessedTick = _timing.CurTick = Replay.CurTick;
_gameState.UpdateFullRep(state, cloneDelta: true);
// Clear existing lerps
_entMan.EntitySysManager.GetEntitySystem<TransformSystem>().Reset();
var next = Replay.NextState;
BeforeApplyState?.Invoke((state, next));
_gameState.ApplyGameState(state, next);
_gameState.ApplyGameState(state, Replay.NextState);
ProcessMessages(Replay.CurMessages, skipEffectEvents);
// TODO REPLAYS block audio

View File

@@ -42,9 +42,7 @@ internal sealed partial class ReplayPlaybackManager
{
var state = Replay.CurState;
_gameState.UpdateFullRep(state, cloneDelta: true);
var next = Replay.NextState;
BeforeApplyState?.Invoke((state, next));
_gameState.ApplyGameState(state, next);
_gameState.ApplyGameState(state, Replay.NextState);
DebugTools.Assert(Replay.LastApplied >= state.FromSequence);
DebugTools.Assert(Replay.LastApplied + 1 <= state.ToSequence);
Replay.LastApplied = state.ToSequence;

View File

@@ -12,7 +12,6 @@ using Robust.Client.Upload;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
@@ -45,7 +44,6 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
public event Action? ReplayPlaybackStopped;
public event Action? ReplayPaused;
public event Action? ReplayUnpaused;
public event Action<(GameState Current, GameState? Next)>? BeforeApplyState;
public ReplayData? Replay { get; private set; }
public NetUserId? Recorder => Replay?.Recorder;

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" />
@@ -27,9 +27,9 @@
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
</ItemGroup>
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" PrivateAssets="compile" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" PrivateAssets="compile" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" PrivateAssets="compile" />
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
</ItemGroup>

View File

@@ -20,7 +20,6 @@ namespace Robust.Client.UserInterface.Controls
public RichTextLabel()
{
IoCManager.InjectDependencies(this);
VerticalAlignment = VAlignment.Center;
}
public void SetMessage(FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)

View File

@@ -70,7 +70,6 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
return;
var mapSystem = _entityManager.System<SharedMapSystem>();
var xformSystem = _entityManager.System<SharedTransformSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
{
@@ -81,7 +80,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
{
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
mouseWorldMap.Position);
tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager), Tile.Empty);
}
var controlHovered = UserInterfaceManager.CurrentlyHovered;
@@ -91,35 +90,35 @@ Screen Size: {screenSize} (scale: {screenScale})
Mouse Pos:
Screen: {mouseScreenPos}
{mouseWorldMap}
{_entityManager.GetNetCoordinates(mouseGridPos)}
{mouseGridPos}
{tile}
GUI: {controlHovered}");
_textBuilder.AppendLine("\nAttached NetEntity:");
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
_textBuilder.AppendLine("\nAttached Entity:");
var controlledEntity = _playerManager?.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
if (controlledEntity == EntityUid.Invalid)
{
_textBuilder.AppendLine("No attached netentity.");
_textBuilder.AppendLine("No attached entity.");
}
else
{
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
var playerWorldOffset = entityTransform.MapPosition;
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
var playerCoordinates = entityTransform.Coordinates;
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
var playerRotation = entityTransform.WorldRotation;
var gridRotation = entityTransform.GridUid != null
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
? _entityManager.GetComponent<TransformComponent>(entityTransform.GridUid.Value)
.WorldRotation
: Angle.Zero;
_textBuilder.Append($@" Screen: {playerScreen}
{playerWorldOffset}
{_entityManager.GetNetCoordinates(playerCoordinates)}
{playerCoordinates}
Rotation: {playerRotation.Degrees:F2}°
NEntId: {_entityManager.GetNetEntity(controlledEntity)}
Grid NEntId: {_entityManager.GetNetEntity(entityTransform.GridUid)}
EntId: {controlledEntity}
GridUid: {entityTransform.GridUid}
Grid Rotation: {gridRotation.Degrees:F2}°");
}

View File

@@ -62,7 +62,7 @@ namespace Robust.Client.ViewVariables.Editors
void OnEntered(LineEdit.LineEditEventArgs e)
{
var gridVal = EntityUid.Parse(gridId.Text, "-1");
var gridVal = EntityUid.Parse(gridId.Text);
var mapManager = IoCManager.Resolve<IMapManager>();
var xVal = float.Parse(x.Text, CultureInfo.InvariantCulture);
var yVal = float.Parse(y.Text, CultureInfo.InvariantCulture);

View File

@@ -28,7 +28,7 @@ namespace Robust.Client.ViewVariables.Editors
if (!ReadOnly)
{
lineEdit.OnTextEntered += e =>
ValueChanged(EntityUid.Parse(e.Text, ", -1"));
ValueChanged(EntityUid.Parse(e.Text));
}
var vvButton = new Button()

View File

@@ -61,7 +61,7 @@ internal sealed class AssetPassPackRsis : AssetPass
foreach (var (key, dat) in _foundRsis)
{
if (dat.MetaJson == null)
continue;
return;
RunJob(() =>
{

View File

@@ -21,11 +21,11 @@ public sealed class RobustClientAssetGraph
/// </summary>
public IReadOnlyCollection<AssetPass> AllPasses { get; }
public RobustClientAssetGraph(bool parallel = true)
public RobustClientAssetGraph()
{
// The code injecting the list of source files is assumed to be pretty single-threaded.
// We use a parallelizing input to break out all the work on files coming in onto multiple threads.
Input = new AssetPassPipe { Name = "RobustClientAssetGraphInput", Parallelize = parallel };
Input = new AssetPassPipe { Name = "RobustClientAssetGraphInput", Parallelize = true };
PresetPasses = new AssetPassPipe { Name = "RobustClientAssetGraphPresetPasses" };
Output = new AssetPassPipe { Name = "RobustClientAssetGraphOutput", CheckDuplicates = true };
NormalizeText = new AssetPassNormalizeText { Name = "RobustClientAssetGraphNormalizeText" };

View File

@@ -4,10 +4,9 @@ namespace Robust.Packaging;
public sealed class RobustClientPackaging
{
public static IReadOnlySet<string> ClientIgnoredResources { get; } = new HashSet<string>
public static IReadOnlySet<string> ClientIgnoresResources { get; } = new HashSet<string>
{
"Maps",
"ConfigPresets",
// Leaving this here for future archaeologists to ponder at.
"emotes.xml",
"Groups",
@@ -19,8 +18,48 @@ public sealed class RobustClientPackaging
AssetPass pass,
CancellationToken cancel = default)
{
var ignoreSet = ClientIgnoredResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
var ignoreSet = ClientIgnoresResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"), pass, ignoreSet, cancel: cancel);
await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"), pass, ignoreSet, cancel);
}
public static async Task WriteContentAssemblies(
AssetPass pass,
string contentDir,
string binDir,
IEnumerable<string> contentAssemblies,
CancellationToken cancel = default)
{
await WriteContentAssemblies("Assemblies", pass, contentDir, binDir, contentAssemblies, cancel);
}
public static Task WriteContentAssemblies(
string target,
AssetPass pass,
string contentDir,
string binDir,
IEnumerable<string> contentAssemblies,
CancellationToken cancel = default)
{
var files = new List<string>();
var sourceDir = Path.Combine(contentDir, "bin", binDir);
foreach (var asm in contentAssemblies)
{
files.Add($"{asm}.dll");
var pdbPath = $"{asm}.pdb";
if (File.Exists(Path.Combine(sourceDir, pdbPath)))
files.Add(pdbPath);
}
foreach (var f in files)
{
cancel.ThrowIfCancellationRequested();
pass.InjectFileFromDisk($"{target}/{f}", Path.Combine(sourceDir, f));
}
return Task.CompletedTask;
}
}

View File

@@ -1,33 +0,0 @@
using Robust.Packaging.AssetProcessing;
namespace Robust.Packaging;
public sealed class RobustServerPackaging
{
public static IReadOnlySet<string> ServerIgnoresResources { get; } = new HashSet<string>
{
"Audio",
"Textures",
"Fonts",
"Shaders",
};
public static async Task WriteServerResources(
string contentDir,
AssetPass pass,
CancellationToken cancel = default)
{
var ignoreSet = ServerIgnoresResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"),
pass,
ignoreSet,
"Resources",
cancel);
await RobustSharedPackaging.DoResourceCopy(Path.Combine("RobustToolbox", "Resources"),
pass,
ignoreSet,
"Resources",
cancel);
}
}

View File

@@ -15,53 +15,10 @@ public sealed class RobustSharedPackaging
".DS_Store"
};
// IDK what these are supposed to correspond to but targetDir is the target directory.
public static async Task WriteContentAssemblies(
AssetPass pass,
string contentDir,
string binDir,
IEnumerable<string> contentAssemblies,
string targetDir = "Assemblies",
CancellationToken cancel = default)
{
await WriteContentAssemblies(targetDir, pass, contentDir, binDir, contentAssemblies, cancel);
}
public static Task WriteContentAssemblies(
string target,
AssetPass pass,
string contentDir,
string binDir,
IEnumerable<string> contentAssemblies,
CancellationToken cancel = default)
{
var files = new List<string>();
var sourceDir = Path.Combine(contentDir, "bin", binDir);
foreach (var asm in contentAssemblies)
{
files.Add($"{asm}.dll");
var pdbPath = $"{asm}.pdb";
if (File.Exists(Path.Combine(sourceDir, pdbPath)))
files.Add(pdbPath);
}
foreach (var f in files)
{
cancel.ThrowIfCancellationRequested();
pass.InjectFileFromDisk($"{target}/{f}", Path.Combine(sourceDir, f));
}
return Task.CompletedTask;
}
public static Task DoResourceCopy(
string diskSource,
AssetPass pass,
IReadOnlySet<string> ignoreSet,
string targetDir = "",
CancellationToken cancel = default)
{
foreach (var path in Directory.EnumerateFileSystemEntries(diskSource))
@@ -72,7 +29,7 @@ public sealed class RobustSharedPackaging
if (ignoreSet.Contains(filename))
continue;
var targetPath = Path.Combine(targetDir, filename);
var targetPath = filename;
if (Directory.Exists(path))
CopyDirIntoZip(path, targetPath, pass);
else
@@ -87,10 +44,10 @@ public sealed class RobustSharedPackaging
foreach (var file in Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories))
{
var relPath = Path.GetRelativePath(directory, file);
var zipPath = $"{basePath}/{relPath}";
if (Path.DirectorySeparatorChar != '/')
zipPath = zipPath.Replace(Path.DirectorySeparatorChar, '/');
relPath = relPath.Replace(Path.DirectorySeparatorChar, '/');
var zipPath = $"{basePath}/{relPath}";
// Console.WriteLine($"{directory}/{zipPath} -> /{zipPath}");
pass.InjectFileFromDisk(zipPath, file);

View File

@@ -660,14 +660,10 @@ namespace Robust.Server
{
// Write down exception log
var logPath = _config.GetCVar(CVars.LogPath);
if (!Path.IsPathRooted(logPath))
{
logPath = PathHelpers.ExecutableRelativeFile(logPath);
}
var pathToWrite = Path.Combine(logPath,
var relPath = PathHelpers.ExecutableRelativeFile(logPath);
Directory.CreateDirectory(relPath);
var pathToWrite = Path.Combine(relPath,
"Runtime-" + DateTime.Now.ToString("yyyy-MM-dd-THH-mm-ss") + ".txt");
Directory.CreateDirectory(logPath);
File.WriteAllText(pathToWrite, _runtimeLog.Display(), EncodingHelpers.UTF8);
}

View File

@@ -43,7 +43,6 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
GC.Collect();
Span<EntityUid> ents = stackalloc EntityUid[amount];
var stopwatch = new Stopwatch();
stopwatch.Start();
@@ -51,17 +50,12 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
for (var i = 0; i < amount; i++)
{
ents[i] = _entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
_entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
}
MeasureProfiler.SaveData();
shell.WriteLine($"Server: Profiled spawning {amount} entities in {stopwatch.Elapsed.TotalMilliseconds:N3} ms");
foreach (var ent in ents)
{
_entities.DeleteEntity(ent);
}
}
}
#endif

View File

@@ -0,0 +1,189 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects
{
/// <summary>
/// System that handles players being attached/detached from entities.
/// </summary>
[UsedImplicitly]
public sealed class ActorSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActorComponent, ComponentShutdown>(OnActorShutdown);
}
/// <summary>
/// Attaches a player session to an entity, optionally kicking any sessions already attached to it.
/// </summary>
/// <param name="uid">The entity to attach the player to</param>
/// <param name="player">The player to attach to the entity</param>
/// <param name="force">Whether to kick any existing players from the entity</param>
/// <returns>Whether the attach succeeded, or not.</returns>
public bool Attach(EntityUid? uid, ICommonSession player, bool force = false)
{
return Attach(uid, player, false, out _);
}
/// <summary>
/// Attaches a player session to an entity, optionally kicking any sessions already attached to it.
/// </summary>
/// <param name="entity">The entity to attach the player to</param>
/// <param name="player">The player to attach to the entity</param>
/// <param name="force">Whether to kick any existing players from the entity</param>
/// <param name="forceKicked">The player that was forcefully kicked, or null.</param>
/// <returns>Whether the attach succeeded, or not.</returns>
public bool Attach(EntityUid? entity, ICommonSession player, bool force, out ICommonSession? forceKicked)
{
// Null by default.
forceKicked = null;
if (player.AttachedEntity == entity)
{
DebugTools.Assert(entity == null || HasComp<ActorComponent>(entity));
return true;
}
if (entity is not { } uid)
return Detach(player);
// Cannot attach to a deleted, nonexisting or terminating entity.
if (TerminatingOrDeleted(uid))
return false;
// Check if there was a player attached to the entity already...
if (TryComp(uid, out ActorComponent? actor))
{
// If we're not forcing the attach, this fails.
if (!force)
return false;
// Set the event's force-kicked session before detaching it.
forceKicked = actor.PlayerSession;
RemComp(uid, actor);
DebugTools.AssertNull(forceKicked.AttachedEntity);
}
// Detach from the currently attached entity.
Detach(player);
// We add the actor component.
actor = EntityManager.AddComponent<ActorComponent>(uid);
EntityManager.EnsureComponent<EyeComponent>(uid);
actor.PlayerSession = player;
_playerManager.SetAttachedEntity(player, uid);
DebugTools.Assert(player.AttachedEntity == entity);
// The player is fully attached now, raise an event!
RaiseLocalEvent(uid, new PlayerAttachedEvent(uid, player, forceKicked), true);
return true;
}
/// <summary>
/// Detaches an attached session from the entity, if any.
/// </summary>
/// <param name="entity">The entity player sessions will be detached from.</param>
/// <returns>Whether any player session was detached.</returns>
public bool Detach(EntityUid uid, ActorComponent? actor = null)
{
if (!Resolve(uid, ref actor, false))
return false;
RemComp(uid, actor);
return true;
}
/// <summary>
/// Detaches this player from its attached entity, if any.
/// </summary>
/// <param name="player">The player session that will be detached from any attached entities.</param>
/// <returns>Whether the player is now detached from any entities.
/// This returns true if the player wasn't attached to any entity.</returns>
public bool Detach(ICommonSession player, ActorComponent? actor = null)
{
var uid = player.AttachedEntity;
if (uid == null)
return true;
if (!Resolve(uid.Value, ref actor, false))
{
Log.Error($"Player {player} was attached to a deleted entity?");
((CommonSession) player).AttachedEntity = null;
return true;
}
RemComp(uid.Value, actor);
DebugTools.AssertNull(player.AttachedEntity);
return false;
}
private void OnActorShutdown(EntityUid entity, ActorComponent component, ComponentShutdown args)
{
_playerManager.SetAttachedEntity(component.PlayerSession, null);
// The player is fully detached now that the component has shut down.
RaiseLocalEvent(entity, new PlayerDetachedEvent(entity, component.PlayerSession), true);
}
public bool TryGetActorFromUserId(NetUserId? userId, [NotNullWhen(true)] out ICommonSession? actor, out EntityUid? actorEntity)
{
actor = null;
actorEntity = null;
if (userId != null)
{
if (!_playerManager.TryGetSessionById(userId.Value, out actor))
return false;
actorEntity = actor.AttachedEntity;
}
return actor != null;
}
}
/// <summary>
/// Event for when a player has been attached to an entity.
/// </summary>
public sealed class PlayerAttachedEvent : EntityEventArgs
{
public EntityUid Entity { get; }
public ICommonSession Player { get; }
/// <summary>
/// The player session that was forcefully kicked from the entity, if any.
/// </summary>
public ICommonSession? Kicked { get; }
public PlayerAttachedEvent(EntityUid entity, ICommonSession player, ICommonSession? kicked = null)
{
Entity = entity;
Player = player;
Kicked = kicked;
}
}
/// <summary>
/// Event for when a player has been detached from an entity.
/// </summary>
public sealed class PlayerDetachedEvent : EntityEventArgs
{
public EntityUid Entity { get; }
public ICommonSession Player { get; }
public PlayerDetachedEvent(EntityUid entity, ICommonSession player)
{
Entity = entity;
Player = player;
}
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
namespace Robust.Server.GameObjects;

View File

@@ -5,9 +5,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using Arch.Core;
using Arch.Core.Utils;
using Collections.Pooled;
using Robust.Server.Maps;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
@@ -157,11 +154,10 @@ public sealed class MapLoaderSystem : EntitySystem
var sw = new Stopwatch();
sw.Start();
result = Deserialize(data);
_logLoader.Debug($"Loaded map in {sw.Elapsed}");
_logLoader.Info($"Loaded map {resPath} in {sw.Elapsed}");
var xformQuery = _serverEntityManager.GetEntityQuery<TransformComponent>();
var mapEnt = _mapManager.GetMapEntityId(mapId);
var xformQuery = _serverEntityManager.GetEntityQuery<TransformComponent>();
var rootEnts = new List<EntityUid>();
// aeoeoeieioe content
@@ -178,8 +174,6 @@ public sealed class MapLoaderSystem : EntitySystem
}
}
// EntityManager.CleanupArch();
rootUids = rootEnts;
}
@@ -296,9 +290,6 @@ public sealed class MapLoaderSystem : EntitySystem
ReadGrids(data);
// grids prior to engine v175 might've been serialized with empty chunks which now throw debug asserts.
RemoveEmptyChunks(data);
// Then, go hierarchically in order and do the entity things.
StartupEntities(data);
@@ -314,25 +305,6 @@ public sealed class MapLoaderSystem : EntitySystem
return true;
}
private void RemoveEmptyChunks(MapData data)
{
var gridQuery = _serverEntityManager.GetEntityQuery<MapGridComponent>();
foreach (var uid in data.EntitiesToDeserialize.Keys)
{
if (!gridQuery.TryGetComponent(uid, out var gridComp))
continue;
foreach (var (index, chunk) in gridComp.Chunks)
{
if (chunk.FilledTiles > 0)
continue;
Log.Warning($"Encountered empty chunk while deserializing map. Grid: {ToPrettyString(uid)}. Chunk index: {index}");
gridComp.Chunks.Remove(index);
}
}
}
private bool VerifyEntitiesExist(MapData data, BeforeEntityReadEvent ev)
{
_stopwatch.Restart();
@@ -434,10 +406,6 @@ public sealed class MapLoaderSystem : EntitySystem
if (data.Version >= 4)
{
var metaEntities = data.RootMappingNode.Get<SequenceDataNode>("entities");
using var mapSaveCompType = new PooledSet<Type>()
{
typeof(MapSaveIdComponent)
};
foreach (var metaDef in metaEntities.Cast<MappingDataNode>())
{
@@ -457,30 +425,9 @@ public sealed class MapLoaderSystem : EntitySystem
var entities = (SequenceDataNode) metaDef["entities"];
EntityPrototype? proto = null;
var count = entities.Count;
var entTotal = data.Entities.Count + count;
data.Entities.EnsureCapacity(entTotal);
data.UidEntityMap.EnsureCapacity(entTotal);
data.EntitiesToDeserialize.EnsureCapacity(entTotal);
if (type != null)
{
if (_prototypeManager.TryIndex(type, out proto) && count > 1)
{
ComponentType[] compTypes;
if (data.Options.StoreMapUids)
{
compTypes = EntityManager.GetComponentType(proto, mapSaveCompType);
}
else
{
compTypes = EntityManager.GetComponentType(proto);
}
EntityManager.Reserve(compTypes, count);
}
}
_prototypeManager.TryIndex(type, out proto);
foreach (var entityDef in entities.Cast<MappingDataNode>())
{
@@ -495,7 +442,6 @@ public sealed class MapLoaderSystem : EntitySystem
{
deletedPrototypeUids.Add(entity);
}
// TODO: Move this elsewhere?
else if (data.Options.StoreMapUids)
{
var comp = _serverEntityManager.AddComponent<MapSaveIdComponent>(entity);
@@ -604,7 +550,6 @@ public sealed class MapLoaderSystem : EntitySystem
_context.CurrentlyIgnoredComponents = missingComponentList.Cast<ValueDataNode>().Select(x => x.Value).ToHashSet();
_serverEntityManager.FinishEntityLoad(uid, meta.EntityPrototype, _context);
if (_context.CurrentlyIgnoredComponents.Count > 0)
meta.LastComponentRemoved = _timing.CurTick;
}

View File

@@ -24,6 +24,11 @@ namespace Robust.Server.GameObjects
_cfg.OnValueChanged(CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
private void SetGridDeletion(bool value)
{
_deleteEmptyGrids = value;
@@ -61,8 +66,9 @@ namespace Robust.Server.GameObjects
private void HandleGridEmpty(EntityUid uid, MapGridComponent component, EmptyGridEvent args)
{
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
return;
if (!_deleteEmptyGrids) return;
if (!EntityManager.EntityExists(uid)) return;
if (EntityManager.GetComponent<MetaDataComponent>(uid).EntityLifeStage >= EntityLifeStage.Terminating) return;
MapManager.DeleteGrid(args.GridId);
}

View File

@@ -1,7 +1,6 @@
using Robust.Server.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
namespace Robust.Server.GameObjects;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.Player;
@@ -20,6 +21,8 @@ namespace Robust.Server.GameObjects
private readonly List<ICommonSession> _sessionCache = new();
private readonly Dictionary<ICommonSession, List<PlayerBoundUserInterface>> _openInterfaces = new();
/// <inheritdoc />
public override void Initialize()
{
@@ -43,7 +46,7 @@ namespace Robust.Server.GameObjects
if (args.NewStatus != SessionStatus.Disconnected)
return;
if (!OpenInterfaces.TryGetValue(args.Session, out var buis))
if (!_openInterfaces.TryGetValue(args.Session, out var buis))
return;
foreach (var bui in buis.ToArray())
@@ -89,7 +92,7 @@ namespace Robust.Server.GameObjects
/// <summary>
/// Verify that the subscribed clients are still in range of the interface.
/// </summary>
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
private void CheckRange(EntityUid uid, Shared.GameObjects.ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
{
if (ui.InteractionRange <= 0)
return;
@@ -136,6 +139,11 @@ namespace Robust.Server.GameObjects
}
}
private void ActivateInterface(PlayerBoundUserInterface ui)
{
EnsureComp<ActiveUserInterfaceComponent>(ui.Owner).Interfaces.Add(ui);
}
#region Get BUI
public bool HasUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
@@ -160,14 +168,20 @@ namespace Robust.Server.GameObjects
? bui
: null;
}
public bool TryGetUi(EntityUid uid, Enum uiKey, [NotNullWhen(true)] out PlayerBoundUserInterface? bui, UserInterfaceComponent? ui = null)
{
bui = null;
return Resolve(uid, ref ui, false) && ui.Interfaces.TryGetValue(uiKey, out bui);
}
/// <summary>
/// Return UIs a session has open.
/// Null if empty.
/// </summary>
/// <summary>
public List<PlayerBoundUserInterface>? GetAllUIsForSession(ICommonSession session)
{
OpenInterfaces.TryGetValue(session, out var value);
_openInterfaces.TryGetValue(session, out var value);
return value;
}
#endregion
@@ -245,14 +259,94 @@ namespace Robust.Server.GameObjects
bui.StateDirty = true;
}
/// <summary>
/// Switches between closed and open for a specific client.
/// </summary>
public bool TryToggleUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
ToggleUi(bui, session);
return true;
}
/// <summary>
/// Switches between closed and open for a specific client.
/// </summary>
public void ToggleUi(PlayerBoundUserInterface bui, ICommonSession session)
{
if (bui._subscribedSessions.Contains(session))
CloseUi(bui, session);
else
OpenUi(bui, session);
}
#region Open
public bool TryOpen(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return OpenUi(bui, session);
}
/// <summary>
/// Opens this interface for a specific client.
/// </summary>
public bool OpenUi(PlayerBoundUserInterface bui, ICommonSession session)
{
if (session.Status == SessionStatus.Connecting || session.Status == SessionStatus.Disconnected)
return false;
if (!bui._subscribedSessions.Add(session))
return false;
_openInterfaces.GetOrNew(session).Add(bui);
RaiseLocalEvent(bui.Owner, new BoundUIOpenedEvent(bui.UiKey, bui.Owner, session));
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new OpenBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
// Fun fact, clients needs to have BUIs open before they can receive the state.....
if (bui.LastStateMsg != null)
RaiseNetworkEvent(bui.LastStateMsg, session.ConnectedClient);
ActivateInterface(bui);
return true;
}
#endregion
#region Close
public bool TryClose(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return CloseUi(bui, session);
}
/// <summary>
/// Close this interface for a specific client.
/// </summary>
public bool CloseUi(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null)
{
if (!bui._subscribedSessions.Remove(session))
return false;
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new CloseBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
CloseShared(bui, session, activeUis);
return true;
}
protected override void CloseShared(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null)
{
var owner = bui.Owner;
bui._subscribedSessions.Remove(session);
bui.PlayerStateOverrides.Remove(session);
if (OpenInterfaces.TryGetValue(session, out var buis))
if (_openInterfaces.TryGetValue(session, out var buis))
buis.Remove(bui);
RaiseLocalEvent(owner, new BoundUIClosedEvent(bui.UiKey, owner, session));

View File

@@ -26,7 +26,7 @@ namespace Robust.Server.GameObjects
/// Manager for entities -- controls things like template loading and instantiation
/// </summary>
[UsedImplicitly] // DI Container
public sealed partial class ServerEntityManager : EntityManager, IServerEntityManagerInternal
public sealed class ServerEntityManager : EntityManager, IServerEntityManagerInternal
{
private static readonly Gauge EntitiesCount = Metrics.CreateGauge(
"robust_entities_count",
@@ -62,7 +62,7 @@ namespace Robust.Server.GameObjects
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype)
{
return AllocEntity(prototype, out _, out _);
return AllocEntity(prototype, out _);
}
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, IEntityLoadContext? context)
@@ -85,15 +85,15 @@ namespace Robust.Server.GameObjects
StartEntity(entity);
}
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, out TransformComponent xform, IEntityLoadContext? context = null)
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return base.CreateEntity(prototypeName, out metadata, out xform, context);
return base.CreateEntity(prototypeName, out metadata, context);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
var entity = base.CreateEntity(prototype, out metadata, out xform, context);
var entity = base.CreateEntity(prototype, out metadata, context);
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,
@@ -159,7 +159,7 @@ namespace Robust.Server.GameObjects
base.TickUpdate(frameTime, noPredictions, histogram);
EntitiesCount.Set(EntityCount);
EntitiesCount.Set(Entities.Count);
}
public uint GetLastMessageSequence(ICommonSession session)

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
@@ -272,7 +271,6 @@ internal sealed partial class PvsSystem : EntitySystem
{
sessionData.LastSeenAt.Remove(metadata.NetEntity);
sessionData.LastLeftView.Remove(metadata.NetEntity);
if (sessionData.SentEntities.TryGetValue(previousTick, out var ents))
ents.Remove(metadata.NetEntity);
}
@@ -435,15 +433,10 @@ internal sealed partial class PvsSystem : EntitySystem
#endregion
public List<(int, IChunkIndexLocation)> GetChunks(
ICommonSession[] sessions,
ref HashSet<int>[] playerChunks,
ref EntityUid[][] viewerEntities)
public (List<(int, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(ICommonSession[] sessions)
{
// Pass these in to avoid allocating new ones every tick, 99% of the time sessions length is going to be the same size.
// These values will get overridden here and the old values have already been returned to the pool by this point.
Array.Resize(ref playerChunks, sessions.Length);
Array.Resize(ref viewerEntities, sessions.Length);
var playerChunks = new HashSet<int>[sessions.Length];
var viewerEntities = new EntityUid[sessions.Length][];
_chunkList.Clear();
// Keep track of the index of each chunk we use for a faster index lookup.
@@ -466,8 +459,8 @@ internal sealed partial class PvsSystem : EntitySystem
var session = sessions[i];
playerChunks[i] = _playerChunkPool.Get();
ref var viewers = ref viewerEntities[i];
GetSessionViewers(session, ref viewers);
var viewers = GetSessionViewers(session);
viewerEntities[i] = viewers;
for (var j = 0; j < viewers.Length; j++)
{
@@ -559,7 +552,7 @@ internal sealed partial class PvsSystem : EntitySystem
}
}
return _chunkList;
return (_chunkList, playerChunks, viewerEntities);
}
public void RegisterNewPreviousChunkTrees(
@@ -576,20 +569,23 @@ internal sealed partial class PvsSystem : EntitySystem
_reusedTrees.Add(chunks[i]);
}
foreach (var (index, chunk) in _previousTrees)
var previousIndices = _previousTrees.Keys.ToArray();
for (var i = 0; i < previousIndices.Length; i++)
{
var index = previousIndices[i];
// ReSharper disable once InconsistentlySynchronizedField
if (_reusedTrees.Contains(index))
continue;
if (chunk != null)
if (_reusedTrees.Contains(index)) continue;
var chunk = _previousTrees[index];
if (chunk.HasValue)
{
_chunkCachePool.Return(chunk.Value.metadata);
_treePool.Return(chunk.Value.tree);
}
if (!chunks.Contains(index))
{
_previousTrees.Remove(index);
}
}
_previousTrees.EnsureCapacity(chunks.Count);
@@ -892,7 +888,7 @@ internal sealed partial class PvsSystem : EntitySystem
return null;
var tick = _gameTiming.CurTick;
var minSize = Math.Max(0, lastSent.Count - visibleEnts.Count);
var minSize = Math.Max(0, lastSent.Count - lastSent.Count);
var leftView = new List<NetEntity>(minSize);
foreach (var netEntity in lastSent.Keys)
@@ -1317,44 +1313,26 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
return entState;
}
private void GetSessionViewers(ICommonSession session, [NotNull] ref EntityUid[]? viewers)
private EntityUid[] GetSessionViewers(ICommonSession session)
{
if (session.Status != SessionStatus.InGame)
{
viewers = Array.Empty<EntityUid>();
return;
}
return Array.Empty<EntityUid>();
// Fast path
if (session.ViewSubscriptions.Count == 0)
{
if (session.AttachedEntity == null)
{
viewers = Array.Empty<EntityUid>();
return;
}
return Array.Empty<EntityUid>();
Array.Resize(ref viewers, 1);
viewers[0] = session.AttachedEntity.Value;
return;
return new[] { session.AttachedEntity.Value };
}
int i = 0;
if (session.AttachedEntity is { } local)
{
DebugTools.Assert(!session.ViewSubscriptions.Contains(local));
Array.Resize(ref viewers, session.ViewSubscriptions.Count + 1);
viewers[i++] = local;
}
else
{
Array.Resize(ref viewers, session.ViewSubscriptions.Count);
}
var viewers = new HashSet<EntityUid>();
if (session.AttachedEntity != null)
viewers.Add(session.AttachedEntity.Value);
foreach (var ent in session.ViewSubscriptions)
{
viewers[i++] = ent;
}
viewers.UnionWith(session.ViewSubscriptions);
return viewers.ToArray();
}
// Read Safe

View File

@@ -37,9 +37,6 @@ namespace Robust.Server.GameStates
// Mapping of net UID of clients -> last known acked state.
private GameTick _lastOldestAck = GameTick.Zero;
private HashSet<int>[] _playerChunks = Array.Empty<HashSet<int>>();
private EntityUid[][] _viewerEntities = Array.Empty<EntityUid[]>();
private PvsSystem _pvs = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
@@ -270,7 +267,7 @@ Oldest acked clients: {string.Join(", ", players)}
private PvsData? GetPVSData(ICommonSession[] players)
{
var chunks= _pvs.GetChunks(players, ref _playerChunks, ref _viewerEntities);
var (chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
const int ChunkBatchSize = 2;
var chunksCount = chunks.Count;
var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize);
@@ -313,8 +310,8 @@ Oldest acked clients: {string.Join(", ", players)}
ArrayPool<bool>.Shared.Return(reuse);
return new PvsData()
{
PlayerChunks = _playerChunks,
ViewerEntities = _viewerEntities,
PlayerChunks = playerChunks,
ViewerEntities = viewerEntities,
ChunkCache = chunkCache,
};
}
@@ -381,10 +378,15 @@ Oldest acked clients: {string.Join(", ", players)}
if (_gameTiming.CurTick.Value > lastAck.Value + _pvs.ForceAckThreshold)
{
stateUpdateMessage.ForceSendReliably = true;
// Aside from the time shortly after connecting, this shouldn't be common. If it is happening.
// something is probably wrong (or we have a malicious client). Hence we log an error.
// If it is more frequent than I think, this can be downgraded to a warning.
#if FULL_RELEASE
var connectedTime = (DateTime.UtcNow - session.ConnectedTime).TotalMinutes;
if (lastAck > GameTick.Zero && connectedTime > 1)
_logger.Warning($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
_logger.Error($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
#endif
}

View File

@@ -12,7 +12,6 @@ using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Utility;
namespace Robust.Server.Maps;
@@ -94,7 +93,6 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
IDependencyCollection dependencies, bool alwaysWrite = false,
ISerializationContext? context = null)
{
DebugTools.Assert(value.FilledTiles > 0, "Attempting to write an empty chunk");
var root = new MappingDataNode();
var ind = new ValueDataNode($"{value.X},{value.Y}");
root.Add("ind", ind);

View File

@@ -31,10 +31,6 @@ namespace Robust.Server.Placement
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private EntityLookupSystem _lookup => _entityManager.System<EntityLookupSystem>();
private SharedMapSystem _maps => _entityManager.System<SharedMapSystem>();
private SharedTransformSystem _xformSystem => _entityManager.System<SharedTransformSystem>();
//TO-DO: Expand for multiple permission per mob?
// Add support for multi-use placeables (tiles etc.).
public List<PlacementInformation> BuildPermissions { get; set; } = new();
@@ -49,7 +45,6 @@ namespace Robust.Server.Placement
public void Initialize()
{
// Someday PlacementManagerSystem my beloved.
_sawmill = _logManager.GetSawmill("placement");
_networkManager.RegisterNetMessage<MsgPlacement>(HandleNetMessage);
@@ -148,7 +143,7 @@ namespace Robust.Server.Placement
if (_entityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
{
var replacementQuery = _entityManager.GetEntityQuery<PlacementReplacementComponent>();
var anc = _maps.GetAnchoredEntitiesEnumerator(gridUid.Value, grid, _maps.LocalToTile(gridUid.Value, grid, coordinates));
var anc = grid.GetAnchoredEntitiesEnumerator(grid.LocalToTile(coordinates));
var toDelete = new ValueList<EntityUid>();
while (anc.MoveNext(out var ent))
@@ -191,11 +186,14 @@ namespace Robust.Server.Placement
MapGridComponent? grid;
EntityUid gridId = coordinates.EntityId;
if (_entityManager.TryGetComponent(coordinates.EntityId, out grid)
|| _mapManager.TryFindGridAt(coordinates.ToMap(_entityManager, _xformSystem), out gridId, out grid))
_mapManager.TryGetGrid(coordinates.EntityId, out grid);
if (grid == null)
_mapManager.TryFindGridAt(coordinates.ToMap(_entityManager), out _, out grid);
if (grid != null) // stick to existing grid
{
_maps.SetTile(gridId, grid, coordinates, new Tile(tileType));
grid.SetTile(coordinates, new Tile(tileType));
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
@@ -204,9 +202,9 @@ namespace Robust.Server.Placement
{
var newGrid = _mapManager.CreateGridEntity(coordinates.GetMapId(_entityManager));
var newGridXform = _entityManager.GetComponent<TransformComponent>(newGrid);
_xformSystem.SetWorldPosition(newGridXform, coordinates.Position - newGrid.Comp.TileSizeHalfVector); // assume bottom left tile origin
newGridXform.WorldPosition = coordinates.Position - newGrid.Comp.TileSizeHalfVector; // assume bottom left tile origin
var tilePos = newGrid.Comp.WorldToTile(coordinates.Position);
_maps.SetTile(newGrid.Owner, newGrid.Comp, tilePos, new Tile(tileType));
newGrid.Comp.SetTile(tilePos, new Tile(tileType));
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
@@ -230,16 +228,11 @@ namespace Robust.Server.Placement
{
EntityCoordinates start = _entityManager.GetCoordinates(msg.NetCoordinates);
Vector2 rectSize = msg.RectSize;
foreach (var entity in _lookup.GetEntitiesIntersecting(start.GetMapId(_entityManager),
foreach (EntityUid entity in EntitySystem.Get<EntityLookupSystem>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
new Box2(start.Position, start.Position + rectSize)))
{
if (_entityManager.Deleted(entity) ||
_entityManager.HasComponent<MapGridComponent>(entity) ||
_entityManager.HasComponent<ActorComponent>(entity))
{
if (_entityManager.Deleted(entity) || _entityManager.HasComponent<MapGridComponent>(entity) || _entityManager.HasComponent<ActorComponent>(entity))
continue;
}
var placementEraseEvent = new PlacementEntityEvent(entity, _entityManager.GetComponent<TransformComponent>(entity).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
_entityManager.DeleteEntity(entity);
@@ -251,19 +244,19 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBegin(EntityUid mob, int range, string objectType, string alignOption)
{
if (!_entityManager.TryGetComponent(mob, out ActorComponent? actor))
if (!_entityManager.TryGetComponent<ActorComponent?>(mob, out var actor))
return;
var playerConnection = actor.PlayerSession.Channel;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
var message = new MsgPlacement
{
PlaceType = PlacementManagerMessage.StartPlacement,
Range = range,
IsTile = false,
ObjType = objectType,
AlignOption = alignOption
};
var message = new MsgPlacement();
message.PlaceType = PlacementManagerMessage.StartPlacement;
message.Range = range;
message.IsTile = false;
message.ObjType = objectType;
message.AlignOption = alignOption;
_networkManager.ServerSendMessage(message, playerConnection);
}
@@ -272,19 +265,19 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBeginTile(EntityUid mob, int range, string tileType, string alignOption)
{
if (!_entityManager.TryGetComponent(mob, out ActorComponent? actor))
if (!_entityManager.TryGetComponent<ActorComponent?>(mob, out var actor))
return;
var playerConnection = actor.PlayerSession.Channel;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
var message = new MsgPlacement
{
PlaceType = PlacementManagerMessage.StartPlacement,
Range = range,
IsTile = true,
ObjType = tileType,
AlignOption = alignOption
};
var message = new MsgPlacement();
message.PlaceType = PlacementManagerMessage.StartPlacement;
message.Range = range;
message.IsTile = true;
message.ObjType = tileType;
message.AlignOption = alignOption;
_networkManager.ServerSendMessage(message, playerConnection);
}
@@ -293,15 +286,15 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementCancel(EntityUid mob)
{
if (!_entityManager.TryGetComponent(mob, out ActorComponent? actor))
if (!_entityManager.TryGetComponent<ActorComponent?>(mob, out var actor))
return;
var playerConnection = actor.PlayerSession.Channel;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
var message = new MsgPlacement
{
PlaceType = PlacementManagerMessage.CancelPlacement
};
var message = new MsgPlacement();
message.PlaceType = PlacementManagerMessage.CancelPlacement;
_networkManager.ServerSendMessage(message, playerConnection);
}

View File

@@ -80,7 +80,9 @@ namespace Robust.Server.Player
/// <param name="args"></param>
private void NewSession(object? sender, NetChannelArgs args)
{
CreateAndAddSession(args.Channel);
var session = CreateAndAddSession(args.Channel.UserId, args.Channel.UserName);
session.Channel = args.Channel;
PlayerCountMetric.Set(PlayerCount);
// Synchronize base time.
var msgTimeBase = new MsgSyncTimeBase();
@@ -104,7 +106,8 @@ namespace Robust.Server.Player
DebugTools.Assert(session.Channel == args.Channel);
SetStatus(session, SessionStatus.Disconnected);
SetAttachedEntity(session, null, out _, true);
if (session.AttachedEntity != null)
EntManager.System<ActorSystem>().Detach(session.AttachedEntity.Value);
var viewSys = EntManager.System<ViewSubscriberSystem>();
foreach (var eye in session.ViewSubscriptions.ToArray())

View File

@@ -22,7 +22,7 @@
<PackageReference Include="Microsoft.Extensions.Primitives" Version="6.0.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.0" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.2" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" PrivateAssets="compile" />
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" />

View File

@@ -3,7 +3,6 @@ using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
@@ -295,9 +294,9 @@ namespace Robust.Server.Scripting
loader: TextLoader.From(TextAndVersion.Create(SourceText.From(message.Code), VersionStamp.Create()))
));
var results = await (CompletionService
.GetService(document)?
.GetCompletionsAsync(document, message.Cursor) ?? Task.FromResult<CompletionList?>(null));
var results = await CompletionService
.GetService(document)
.GetCompletionsAsync(document, message.Cursor);
if (results is not null)
{

View File

@@ -34,12 +34,12 @@ public sealed class DefaultMagicAczProvider : IMagicAczProvider
var inputPass = graph.Input;
var contentDir = FindContentRootPath(_deps);
await RobustSharedPackaging.WriteContentAssemblies(
await RobustClientPackaging.WriteContentAssemblies(
inputPass,
contentDir,
binFolderPath,
assemblyNames,
cancel: cancel);
cancel);
await RobustClientPackaging.WriteClientResources(contentDir, inputPass, cancel);

View File

@@ -167,7 +167,7 @@ namespace Robust.Shared.CompNetworkGenerator
getStateInit.Append($@"
{name} = GetNetEntitySet(component.{name}),");
handleStateSetters.Append($@"
EnsureEntitySet<{componentName}>(state.{name}, uid, component.{name});");
component.{name} = EnsureEntitySet<{componentName}>(state.{name}, uid);");
break;
case GlobalEntityUidListName:
@@ -177,7 +177,7 @@ namespace Robust.Shared.CompNetworkGenerator
getStateInit.Append($@"
{name} = GetNetEntityList(component.{name}),");
handleStateSetters.Append($@"
EnsureEntityList<{componentName}>(state.{name}, uid, component.{name});");
component.{name} = EnsureEntityList<{componentName}>(state.{name}, uid);");
break;
default:

View File

@@ -8,9 +8,9 @@
<ItemGroup>
<PackageReference Include="ILReader.Core" Version="1.0.0.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
</ItemGroup>

View File

@@ -51,7 +51,7 @@ namespace Robust.Shared.Scripting
public EntityCoordinates gpos(double x, double y, int gridId)
{
return gpos(x, y, new EntityUid(gridId, -1));
return gpos(x, y, new EntityUid(gridId));
}
public EntityCoordinates gpos(double x, double y, EntityUid gridId)
@@ -61,12 +61,12 @@ namespace Robust.Shared.Scripting
public EntityUid eid(int i)
{
return new(i, -1);
return new(i);
}
public MapGridComponent getgrid(int i)
{
return map.GetGrid(new EntityUid(i, -1));
return map.GetGrid(new EntityUid(i));
}
public MapGridComponent getgrid(EntityUid mapId)
@@ -193,7 +193,7 @@ namespace Robust.Shared.Scripting
public bool TryComp<T>(EntityUid uid, out T? comp) where T : IComponent
=> ent.TryGetComponent(uid, out comp);
public bool HasComp<T>(EntityUid uid) where T : IComponent
public bool HasComp<T>(EntityUid uid)
=> ent.HasComponent<T>(uid);
public EntityUid Spawn(string? prototype, EntityCoordinates position)

View File

@@ -53,16 +53,6 @@ namespace Robust.Shared
public static readonly CVarDef<int> NetReceiveBufferSize =
CVarDef.Create("net.receivebuffersize", 131071, CVar.ARCHIVE);
/// <summary>
/// Size of the pool for Lidgren's array buffers to send messages.
/// Set to 0 to disable pooling; max is 8192.
/// </summary>
/// <remarks>
/// Higher just means more potentially wasted space and slower pool retrieval.
/// </remarks>
public static readonly CVarDef<int> NetPoolSize =
CVarDef.Create("net.pool_size", 512, CVar.CLIENT | CVar.SERVER);
/// <summary>
/// Maximum UDP payload size to send.
/// </summary>
@@ -129,13 +119,6 @@ namespace Robust.Shared
public static readonly CVarDef<int> NetBufferSize =
CVarDef.Create("net.buffer_size", 2, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// The maximum size of the game state buffer. If this is exceeded the client will request a full game state.
/// Values less than <see cref="GameStateProcessor.MinimumMaxBufferSize"/> will be ignored.
/// </summary>
public static readonly CVarDef<int> NetMaxBufferSize =
CVarDef.Create("net.max_buffer_size", 512, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// Enable verbose game state/networking logging.
/// </summary>

View File

@@ -1,17 +0,0 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Shared.Console.Commands;
public sealed class ArchTrimCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
public string Command => "arch_trim";
public string Description => "Runs TrimExcess on arch";
public string Help => Command;
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
_entManager.CleanupArch();
}
}

View File

@@ -131,7 +131,6 @@ internal sealed class RunMapInitCommand : LocalizedCommands
internal sealed class ListMapsCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "lsmap";
@@ -145,13 +144,10 @@ internal sealed class ListMapsCommand : LocalizedCommands
foreach (var mapId in _map.GetAllMapIds().OrderBy(id => id.Value))
{
var mapUid = _map.GetMapEntityId(mapId);
msg.AppendFormat("{0}: {1}, init: {2}, paused: {3}, nent: {4}, grids: {5}\n",
mapId, _entManager.GetComponent<MetaDataComponent>(mapUid).EntityName,
_map.IsMapInitialized(mapId),
msg.AppendFormat("{0}: init: {1}, paused: {2}, ent: {3}, grids: {4}\n",
mapId, _map.IsMapInitialized(mapId),
_map.IsMapPaused(mapId),
_entManager.GetNetEntity(_map.GetMapEntityId(mapId)),
_map.GetMapEntityId(mapId),
string.Join(",", _map.GetAllGrids(mapId).Select(grid => grid.Owner)));
}

View File

@@ -79,7 +79,20 @@ namespace Robust.Shared.Containers
Owner = owner;
}
[Obsolete("Use container system method")]
/// <summary>
/// Attempts to insert the entity into this container.
/// </summary>
/// <remarks>
/// If the insertion is successful, the inserted entity will end up parented to the
/// container entity, and the inserted entity's local position will be set to the zero vector.
/// </remarks>
/// <param name="toinsert">The entity to insert.</param>
/// <param name="entMan"></param>
/// <returns>False if the entity could not be inserted.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if this container is a child of the entity,
/// which would cause infinite loops.
/// </exception>
public bool Insert(
EntityUid toinsert,
IEntityManager? entMan = null,
@@ -125,23 +138,16 @@ namespace Robust.Shared.Containers
return false;
}
transform ??= transformQuery.GetComponent(toinsert);
//Verify we can insert into this container
if (!force && !containerSys.CanInsert(toinsert, this, containerXform: ownerTransform))
if (!force && !containerSys.CanInsert(toinsert, this))
return false;
// Please somebody ecs containers
var lookupSys = entMan.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
var xformSys = entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
transform ??= transformQuery.GetComponent(toinsert);
meta ??= entMan.GetComponent<MetaDataComponent>(toinsert);
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
{
Logger.ErrorS("container",
$"Attempted to insert a terminating entity {entMan.ToPrettyString(toinsert)} into a container {ID} in entity: {entMan.ToPrettyString(Owner)}.");
return false;
}
// remove from any old containers.
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
@@ -264,7 +270,16 @@ namespace Robust.Shared.Containers
/// <param name="assumeEmpty">Whether to assume that the container is currently empty.</param>
protected internal virtual bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) => true;
[Obsolete("Use container system method")]
/// <summary>
/// Attempts to remove the entity from this container.
/// </summary>
/// <param name="reparent">If false, this operation will not rigger a move or parent change event. Ignored if
/// destination is not null</param>
/// <param name="force">If true, this will not perform can-remove checks.</param>
/// <param name="destination">Where to place the entity after removing. Avoids unnecessary broadphase updates.
/// If not specified, and reparent option is true, then the entity will either be inserted into a parent
/// container, the grid, or the map.</param>
/// <param name="localRotation">Optional final local rotation after removal. Avoids redundant move events.</param>
public bool Remove(
EntityUid toRemove,
IEntityManager? entMan = null,
@@ -344,7 +359,7 @@ namespace Robust.Shared.Containers
return true;
}
[Obsolete("Use container system method")]
[Obsolete("use force option in Remove()")]
public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null)
=> Remove(toRemove, entMan, meta: meta, reparent: false, force: true);

View File

@@ -1,41 +1,10 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics.Components;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers;
public abstract partial class SharedContainerSystem
{
/// <summary>
/// Attempts to insert the entity into this container.
/// </summary>
/// <remarks>
/// If the insertion is successful, the inserted entity will end up parented to the
/// container entity, and the inserted entity's local position will be set to the zero vector.
/// </remarks>
/// <param name="toInsert">The entity to insert.</param>
/// <param name="container">The container to insert into.</param>
/// <param name="containerXform">The container's transform component.</param>
/// <param name="force">Whether to bypass normal insertion checks.</param>
/// <returns>False if the entity could not be inserted.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if this container is a child of the entity,
/// which would cause infinite loops.
/// </exception>
public bool Insert(Entity<TransformComponent?, MetaDataComponent?, PhysicsComponent?> toInsert,
BaseContainer container,
TransformComponent? containerXform = null,
bool force = false)
{
// Cannot Use Resolve(ref toInsert) as the physics component is optional
if (!Resolve(toInsert.Owner, ref toInsert.Comp1, ref toInsert.Comp2))
return false;
// TODO move logic over to the system.
return container.Insert(toInsert, EntityManager, toInsert, containerXform, toInsert, toInsert, force);
}
/// <summary>
/// Checks if the entity can be inserted into the given container.
/// </summary>
@@ -44,8 +13,8 @@ public abstract partial class SharedContainerSystem
public bool CanInsert(
EntityUid toInsert,
BaseContainer container,
bool assumeEmpty = false,
TransformComponent? containerXform = null)
TransformComponent? toInsertXform = null,
bool assumeEmpty = false)
{
if (container.Owner == toInsert)
return false;
@@ -56,12 +25,15 @@ public abstract partial class SharedContainerSystem
if (!container.CanInsert(toInsert, assumeEmpty, EntityManager))
return false;
if (!TransformQuery.Resolve(toInsert, ref toInsertXform))
return false;
// no, you can't put maps or grids into containers
if (_mapQuery.HasComponent(toInsert) || _gridQuery.HasComponent(toInsert))
return false;
// Prevent circular insertion.
if (_transform.ContainsEntity(toInsert, (container.Owner, containerXform)))
if (_transform.ContainsEntity(toInsertXform, container.Owner))
return false;
var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(container, toInsert, assumeEmpty);

View File

@@ -1,43 +1,9 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Shared.Containers;
public abstract partial class SharedContainerSystem
{
/// <summary>
/// Attempts to remove the entity from this container.
/// </summary>
/// <remarks>
/// If the insertion is successful, the inserted entity will end up parented to the
/// container entity, and the inserted entity's local position will be set to the zero vector.
/// </remarks>
/// <param name="toRemove">The entity to remove.</param>
/// <param name="container">The container to remove from.</param>
/// <param name="reparent">If false, this operation will not rigger a move or parent change event. Ignored if
/// destination is not null</param>
/// <param name="force">If true, this will not perform can-remove checks.</param>
/// <param name="destination">Where to place the entity after removing. Avoids unnecessary broadphase updates.
/// If not specified, and reparent option is true, then the entity will either be inserted into a parent
/// container, the grid, or the map.</param>
/// <param name="localRotation">Optional final local rotation after removal. Avoids redundant move events.</param>
public bool Remove(
Entity<TransformComponent?, MetaDataComponent?> toRemove,
BaseContainer container,
bool reparent = true,
bool force = false,
EntityCoordinates? destination = null,
Angle? localRotation = null)
{
// Cannot Use Resolve(ref toInsert) as the physics component is optional
if (!Resolve(toRemove.Owner, ref toRemove.Comp1, ref toRemove.Comp2))
return false;
// TODO move logic over to the system.
return container.Remove(toRemove, EntityManager, toRemove, toRemove, reparent, force, destination, localRotation);
}
/// <summary>
/// Checks if the entity can be removed from this container.
/// </summary>

View File

@@ -216,27 +216,35 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Recursively check if the entity or any parent is inside of a container.
/// Recursively if the entity, or any parent entity, is inside of a container.
/// </summary>
/// <returns>If the entity is inside of a container.</returns>
public bool IsEntityOrParentInContainer(
EntityUid uid,
MetaDataComponent? meta = null,
TransformComponent? xform = null)
TransformComponent? xform = null,
EntityQuery<MetaDataComponent>? metas = null,
EntityQuery<TransformComponent>? xforms = null)
{
if (!MetaQuery.Resolve(uid, ref meta))
return false;
if (meta == null)
{
metas ??= GetEntityQuery<MetaDataComponent>();
meta = metas.Value.GetComponent(uid);
}
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer)
return true;
if (!TransformQuery.Resolve(uid, ref xform))
return false;
if (xform == null)
{
xforms ??= GetEntityQuery<TransformComponent>();
xform = xforms.Value.GetComponent(uid);
}
if (!xform.ParentUid.Valid)
return false;
return IsEntityOrParentInContainer(xform.ParentUid);
return IsEntityOrParentInContainer(xform.ParentUid, metas: metas, xforms: xforms);
}
/// <summary>

View File

@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
String("short").ThenReturn(PrimitiveTypeCode.Int16);
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
String("int").ThenReturn(PrimitiveTypeCode.Int32);

View File

@@ -73,20 +73,148 @@ WhitelistedNamespaces:
# * The API is not *relevant* to content. e.g. System.Type.IsAnsiClass.
# * I am lazy these API lists are huge dude.
Types:
NetSerializer:
NetListAsArray`1:
Fields:
- "System.Collections.Generic.IReadOnlyCollection`1<!0> Value"
Methods:
- "bool get_HasContents()"
Lidgren.Network:
NetBuffer:
All: True
Methods:
- "byte[] get_Data()"
- "void set_Data(byte[])"
- "int get_LengthBytes()"
- "void set_LengthBytes(int)"
- "int get_LengthBits()"
- "void set_LengthBits(int)"
- "long get_Position()"
- "void set_Position(long)"
- "int get_PositionInBytes()"
- "byte[] PeekDataBuffer()"
- "bool PeekBoolean()"
- "byte PeekByte()"
- "sbyte PeekSByte()"
- "byte PeekByte(int)"
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
- "byte[] PeekBytes(int)"
- "void PeekBytes(byte[], int, int)"
- "short PeekInt16()"
- "ushort PeekUInt16()"
- "int PeekInt32()"
- "int PeekInt32(int)"
- "uint PeekUInt32()"
- "uint PeekUInt32(int)"
- "ulong PeekUInt64()"
- "long PeekInt64()"
- "ulong PeekUInt64(int)"
- "long PeekInt64(int)"
- "float PeekFloat()"
- "System.Half PeekHalf()"
- "float PeekSingle()"
- "double PeekDouble()"
- "string PeekString()"
- "int PeekStringSize()"
- "bool ReadBoolean()"
- "byte ReadByte()"
- "bool ReadByte(ref byte)"
- "sbyte ReadSByte()"
- "byte ReadByte(int)"
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
- "byte[] ReadBytes(int)"
- "bool ReadBytes(int, ref byte[])"
- "bool TryReadBytes(System.Span`1<byte>)"
- "void ReadBytes(byte[], int, int)"
- "void ReadBits(System.Span`1<byte>, int)"
- "void ReadBits(byte[], int, int)"
- "short ReadInt16()"
- "ushort ReadUInt16()"
- "int ReadInt32()"
- "bool ReadInt32(ref int)"
- "int ReadInt32(int)"
- "uint ReadUInt32()"
- "bool ReadUInt32(ref uint)"
- "uint ReadUInt32(int)"
- "ulong ReadUInt64()"
- "long ReadInt64()"
- "ulong ReadUInt64(int)"
- "long ReadInt64(int)"
- "float ReadFloat()"
- "System.Half ReadHalf()"
- "float ReadSingle()"
- "bool ReadSingle(ref float)"
- "double ReadDouble()"
- "uint ReadVariableUInt32()"
- "bool ReadVariableUInt32(ref uint)"
- "int ReadVariableInt32()"
- "long ReadVariableInt64()"
- "ulong ReadVariableUInt64()"
- "float ReadSignedSingle(int)"
- "float ReadUnitSingle(int)"
- "float ReadRangedSingle(float, float, int)"
- "int ReadRangedInteger(int, int)"
- "long ReadRangedInteger(long, long)"
- "string ReadString()"
- "bool ReadString(ref string)"
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
- "System.Net.IPEndPoint ReadIPEndPoint()"
- "void SkipPadBits()"
- "void ReadPadBits()"
- "void SkipPadBits(int)"
- "void EnsureBufferSize(int)"
- "void Write(bool)"
- "void Write(byte)"
- "void WriteAt(int, byte)"
- "void Write(sbyte)"
- "void Write(byte, int)"
- "void Write(byte[])"
- "void Write(System.ReadOnlySpan`1<byte>)"
- "void Write(byte[], int, int)"
- "void Write(ushort)"
- "void WriteAt(int, ushort)"
- "void Write(ushort, int)"
- "void Write(short)"
- "void WriteAt(int, short)"
- "void Write(int)"
- "void WriteAt(int, int)"
- "void Write(uint)"
- "void WriteAt(int, uint)"
- "void Write(uint, int)"
- "void Write(int, int)"
- "void Write(ulong)"
- "void WriteAt(int, ulong)"
- "void Write(ulong, int)"
- "void Write(long)"
- "void Write(long, int)"
- "void Write(System.Half)"
- "void Write(float)"
- "void Write(double)"
- "int WriteVariableUInt32(uint)"
- "int WriteVariableInt32(int)"
- "int WriteVariableInt64(long)"
- "int WriteVariableUInt64(ulong)"
- "void WriteSignedSingle(float, int)"
- "void WriteUnitSingle(float, int)"
- "void WriteRangedSingle(float, float, float, int)"
- "int WriteRangedInteger(int, int, int)"
- "int WriteRangedInteger(long, long, long)"
- "void Write(string)"
- "void Write(System.Net.IPEndPoint)"
- "void WriteTime(bool)"
- "void WriteTime(double, bool)"
- "void WritePadBits()"
- "void WritePadBits(int)"
- "void Write(Lidgren.Network.NetBuffer)"
- "void Zero(int)"
- "void .ctor()"
NetDeliveryMethod: { }
NetIncomingMessage:
All: True
Methods:
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
- "int get_SequenceChannel()"
- "System.Net.IPEndPoint get_SenderEndPoint()"
- "Lidgren.Network.NetConnection get_SenderConnection()"
- "double get_ReceiveTime()"
- "double ReadTime(bool)"
- "string ToString()"
NetOutgoingMessage:
All: True
Methods:
- "string ToString()"
Nett:
CommentLocation: { } # Enum
Toml:

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
path = path.Directory;
var fullPath = GetFullPath(path);
Process.Start(new ProcessStartInfo
if (OperatingSystem.IsWindows())
{
UseShellExecute = true,
FileName = fullPath,
});
Process.Start(new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsMacOS())
{
Process.Start(new ProcessStartInfo
{
FileName = "open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
{
Process.Start(new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else
{
throw new NotSupportedException("Opening OS windows not supported on this OS");
}
}
#endregion

View File

@@ -1,47 +0,0 @@
using Arch.Core;
using Collections.Pooled;
namespace Robust.Shared.GameObjects;
internal struct ArchetypeIterator
{
private readonly PooledList<Archetype> _archetypes;
internal ArchetypeIterator(PooledList<Archetype> archetypes)
{
_archetypes = archetypes;
}
public ArchetypeEnumerator GetEnumerator()
{
return new ArchetypeEnumerator(_archetypes);
}
}
internal struct ArchetypeEnumerator
{
private readonly PooledList<Archetype> _archetypes;
private int _index;
public ArchetypeEnumerator(PooledList<Archetype> archetypes)
{
_archetypes = archetypes;
_index = _archetypes.Count;
}
public bool MoveNext()
{
while (--_index >= 0)
{
var archetype = Current;
if (archetype.EntityCount > 0)
{
return true;
}
}
return false;
}
public Archetype Current => _archetypes[_index];
}

View File

@@ -1,60 +0,0 @@
using Arch.Core;
namespace Robust.Shared.GameObjects;
internal struct ArchChunkIterator
{
private readonly ArchetypeEnumerator _archetypes;
internal ArchChunkIterator(in ArchetypeEnumerator archetypes)
{
_archetypes = archetypes;
}
public ArchChunkEnumerator GetEnumerator()
{
return new ArchChunkEnumerator(_archetypes);
}
}
internal struct ArchChunkEnumerator
{
private ArchetypeEnumerator _archetypes;
private int _chunkIndex;
public Chunk Current => _archetypes.Current.GetChunk(_chunkIndex);
internal ArchChunkEnumerator(in ArchetypeEnumerator archetypes)
{
_archetypes = archetypes;
if (_archetypes.MoveNext())
{
_chunkIndex = _archetypes.Current.ChunkCount;
}
}
public bool MoveNext()
{
if (--_chunkIndex >= 0 && Current.Size > 0)
{
return true;
}
if (!_archetypes.MoveNext())
{
return false;
}
_chunkIndex = _archetypes.Current.ChunkCount - 1;
return true;
}
}
internal static partial class QueryExtensions
{
internal static ArchChunkIterator ChunkIterator(this in Query query, World world)
{
var archetypeEnumerator = new ArchetypeEnumerator(query.Matches);
return new ArchChunkIterator(in archetypeEnumerator);
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Arch.Core.Utils;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -14,7 +13,6 @@ public readonly struct CompIdx : IEquatable<CompIdx>
private static readonly Dictionary<Type, CompIdx> SlowStore = new();
internal readonly int Value;
internal readonly ComponentType Type;
internal static CompIdx Index<T>() => Store<T>.Index;
@@ -64,13 +62,12 @@ public readonly struct CompIdx : IEquatable<CompIdx>
private static class Store<T>
{
// ReSharper disable once StaticMemberInGenericType
public static readonly CompIdx Index = new(Interlocked.Increment(ref _CompIdxMaster), typeof(T));
public static readonly CompIdx Index = new(Interlocked.Increment(ref _CompIdxMaster));
}
internal CompIdx(int value, in ComponentType type)
internal CompIdx(int value)
{
Value = value;
Type = type;
}
public bool Equals(CompIdx other)

View File

@@ -31,9 +31,9 @@ namespace Robust.Shared.GameObjects
public readonly struct AddedComponentEventArgs
{
public readonly ComponentEventArgs BaseArgs;
public readonly ComponentRegistration ComponentType;
public readonly CompIdx ComponentType;
public AddedComponentEventArgs(ComponentEventArgs baseArgs, ComponentRegistration componentType)
public AddedComponentEventArgs(ComponentEventArgs baseArgs, CompIdx componentType)
{
BaseArgs = baseArgs;
ComponentType = componentType;
@@ -55,4 +55,17 @@ namespace Robust.Shared.GameObjects
Meta = meta;
}
}
public readonly struct DeletedComponentEventArgs
{
public readonly ComponentEventArgs BaseArgs;
public readonly bool Terminating;
public DeletedComponentEventArgs(ComponentEventArgs baseArgs, bool terminating)
{
BaseArgs = baseArgs;
Terminating = terminating;
}
}
}

View File

@@ -12,10 +12,16 @@ namespace Robust.Shared.GameObjects
[RegisterComponent, NetworkedComponent, Access(typeof(SharedEyeSystem)), AutoGenerateComponentState(true)]
public sealed partial class EyeComponent : Component
{
public const int DefaultVisibilityMask = 1;
#region Client
[ViewVariables] internal Eye? _eye = default!;
public IEye? Eye => _eye;
[ViewVariables]
public readonly Eye Eye = new();
public MapCoordinates? Position => _eye?.Position;
#endregion
/// <summary>
/// If not null, this entity is used to update the eye's position instead of just using the component's owner.
@@ -31,9 +37,6 @@ namespace Robust.Shared.GameObjects
[ViewVariables(VVAccess.ReadWrite), DataField("drawFov"), AutoNetworkedField]
public bool DrawFov = true;
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool DrawLight = true;
// yes it's not networked, don't ask.
[ViewVariables(VVAccess.ReadWrite), DataField("rotation")]
public Angle Rotation;
@@ -44,6 +47,8 @@ namespace Robust.Shared.GameObjects
[ViewVariables(VVAccess.ReadWrite), DataField("offset"), AutoNetworkedField]
public Vector2 Offset;
public const int DefaultVisibilityMask = 1;
/// <summary>
/// The visibility mask for this eye.
/// The player will be able to get updates for entities whose layers match the mask.
@@ -53,7 +58,7 @@ namespace Robust.Shared.GameObjects
}
/// <summary>
/// Single layer used for Eye visibility. Controls what entities they are allowed to see.
/// Single layer used for Eye visiblity. Controls what entities they are allowed to see.
/// </summary>
public sealed class VisibilityMaskLayer {}
}

View File

@@ -27,7 +27,7 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
private NetEntity NetParent => _entMan.GetNetEntity(_parent);
[DataField("parent")] internal EntityUid _parent = EntityUid.Invalid;
[DataField("parent")] internal EntityUid _parent;
[DataField("pos")] internal Vector2 _localPosition = Vector2.Zero; // holds offset from grid, or offset from parent
[DataField("rot")] internal Angle _localRotation; // local rotation
[DataField("noRot")] internal bool _noLocalRotation;
@@ -311,7 +311,6 @@ namespace Robust.Shared.GameObjects
/// This is effectively a more complete version of <see cref="WorldPosition"/>
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete("Use TransformSystem.GetMapCoordinates")]
public MapCoordinates MapPosition => new(WorldPosition, MapID);
/// <summary>
@@ -733,7 +732,7 @@ namespace Robust.Shared.GameObjects
{
public bool IsValid() => Uid.IsValid();
public bool Valid => IsValid();
public static readonly BroadphaseData Invalid = new(EntityUid.Invalid, EntityUid.Invalid, false, false);
public static readonly BroadphaseData Invalid = default;
// TODO include MapId if ever grids are allowed to enter null-space (leave PVS).
}

View File

@@ -346,7 +346,7 @@ namespace Robust.Shared.GameObjects
{
_subscriptionLock = true;
EntAddComponent(e.BaseArgs.Owner, e.ComponentType.Idx);
EntAddComponent(e.BaseArgs.Owner, e.ComponentType);
}
public void OnComponentRemoved(in RemovedComponentEventArgs e)

View File

@@ -1,48 +0,0 @@
using Arch.Core;
namespace Robust.Shared.GameObjects;
internal struct EntityIterator
{
private readonly Chunk _chunk;
internal EntityIterator(in Chunk chunk)
{
_chunk = chunk;
}
public EntityEnumerator GetEnumerator()
{
return new EntityEnumerator(_chunk);
}
}
internal struct EntityEnumerator
{
private readonly Chunk _chunk;
private int _entityIndex;
public Entity Current { get; private set; }
public EntityEnumerator(in Chunk chunk)
{
_chunk = chunk;
}
public bool MoveNext()
{
if (_entityIndex >= _chunk.Entities.Length)
return false;
Current = _chunk.Entities[_entityIndex];
_entityIndex++;
return true;
}
}
internal static partial class QueryExtensions
{
internal static EntityIterator ChunkIterator(this in Chunk chunk)
{
return new EntityIterator(chunk);
}
}

View File

@@ -1,115 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Arch.Core;
using Arch.Core.Extensions;
using Arch.Core.Utils;
using Collections.Pooled;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public partial class EntityManager
{
private World _world = default!;
private static readonly ComponentType[] DefaultArchetype = new ComponentType[]
{
typeof(MetaDataComponent),
typeof(TransformComponent),
};
protected void InitializeArch()
{
_world = World.Create();
}
protected void ShutdownArch()
{
World.Destroy(_world);
}
protected void DestroyArch(EntityUid uid)
{
var archEnt = (Entity) uid;
var reference = _world.Reference(archEnt);
if (reference.Version != (uid.Version - EntityUid.ArchVersionOffset))
{
throw new InvalidOperationException($"Tried to delete a different matching entity for Arch.");
}
_world.Destroy(archEnt);
}
private void SpawnEntityArch(out EntityUid entity)
{
var archEnt = _world.Create(DefaultArchetype);
var reference = _world.Reference(archEnt);
entity = new EntityUid(reference);
}
public void CleanupArch()
{
var sw = new Stopwatch();
sw.Start();
var arc = _world.Archetypes.Count;
_world.TrimExcess();
arc -= _world.Archetypes.Count;
sw.Stop();
_sawmill.Debug($"Trimming {arc} archetypes took {sw.Elapsed.TotalMilliseconds} milliseconds");
}
internal ComponentType[] GetComponentType(EntityPrototype prototype, ICollection<Type>? added = null, ICollection<Type>? missing = null)
{
var compTypes = new ComponentType[prototype.Components.Count + (added?.Count ?? 0) - (missing?.Count ?? 0)];
var idx = 0;
foreach (var comp in prototype.Components.Values)
{
var componentType = comp.Component.GetType();
if (missing?.Contains(componentType) == true || added?.Contains(componentType) == true)
continue;
compTypes[idx++] = componentType;
}
if (added != null)
{
foreach (var componentType in added)
{
if (missing?.Contains(componentType) == true)
continue;
compTypes[idx++] = componentType;
}
}
return compTypes;
}
/// <summary>
/// Reserves additional slots for the specified ComponentTypes.
/// </summary>
internal void Reserve(ComponentType[] compTypes, int count)
{
_world.Reserve(compTypes, count);
}
/// <summary>
/// WARNING: DO NOT CALL THIS UNLESS YOU KNOW WHAT YOU ARE DOING.
/// Adds the component types to the entity, shuffling its archetype.
/// </summary>
internal void AddComponentRange(EntityUid uid, PooledList<ComponentType> compTypes)
{
DebugTools.Assert(compTypes.Count > 0);
_world.AddRange(uid, compTypes);
}
internal void RemoveComponentRange(EntityUid uid, PooledList<ComponentType> compTypes)
{
DebugTools.Assert(compTypes.Count > 0);
_world.RemoveRange(uid, compTypes.Span);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,8 @@ public partial class EntityManager
/// <inheritdoc />
public HashSet<EntityUid> GetEntitySet(HashSet<NetEntity> netEntities)
{
var entities = new HashSet<EntityUid>(netEntities.Count);
var entities = new HashSet<EntityUid>();
entities.EnsureCapacity(netEntities.Count);
foreach (var netEntity in netEntities)
{
@@ -291,16 +292,6 @@ public partial class EntityManager
return entities;
}
public void EnsureEntitySet<T>(HashSet<NetEntity> netEntities, EntityUid callerEntity, HashSet<EntityUid> entities)
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var netEntity in netEntities)
{
entities.Add(EnsureEntity<T>(netEntity, callerEntity));
}
}
/// <inheritdoc />
public List<EntityUid> EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity)
{
@@ -314,16 +305,6 @@ public partial class EntityManager
return entities;
}
public void EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity, List<EntityUid> entities)
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var netEntity in netEntities)
{
entities.Add(EnsureEntity<T>(netEntity, callerEntity));
}
}
/// <inheritdoc />
public List<EntityUid> GetEntityList(ICollection<NetEntity> netEntities)
{

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Arch.Core.Extensions.Dangerous;
using Robust.Shared.Containers;
namespace Robust.Shared.GameObjects;
@@ -41,16 +40,6 @@ public partial class EntityManager
return ents;
}
public EntityUid[] SpawnEntities(MapCoordinates coordinates, string? prototype, int count)
{
var ents = new EntityUid[count];
for (var i = 0; i < count; i++)
{
ents[i] = Spawn(prototype, coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames)
{

View File

@@ -2,10 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Arch.Core;
using Arch.Core.Extensions;
using Arch.Core.Utils;
using Collections.Pooled;
using System.Runtime.CompilerServices;
using Prometheus;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -16,8 +13,6 @@ using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Runtime.CompilerServices;
using ComponentRegistry = Robust.Shared.Prototypes.ComponentRegistry;
namespace Robust.Shared.GameObjects
{
@@ -44,8 +39,6 @@ namespace Robust.Shared.GameObjects
// positions on spawn....
private SharedTransformSystem _xforms = default!;
private QueryDescription _archMetaQuery = new QueryDescription().WithAll<MetaDataComponent>();
public EntityQuery<MetaDataComponent> MetaQuery;
public EntityQuery<TransformComponent> TransformQuery;
@@ -69,8 +62,15 @@ namespace Robust.Shared.GameObjects
private EntityDiffContext _context = new();
/// <summary>
/// All entities currently stored in the manager.
/// </summary>
protected readonly HashSet<EntityUid> Entities = new();
private EntityEventBus _eventBus = null!;
protected int NextEntityUid = (int) EntityUid.FirstUid;
protected int NextNetworkId = (int) NetEntity.First;
/// <inheritdoc />
@@ -116,7 +116,7 @@ namespace Robust.Shared.GameObjects
_eventBus = new EntityEventBus(this);
InitializeArch();
InitializeComponents();
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
_xformReg = _componentFactory.GetRegistration(typeof(TransformComponent));
_xformName = _xformReg.Name;
@@ -136,6 +136,15 @@ namespace Robust.Shared.GameObjects
var prototype = metadata.EntityPrototype;
// Prototype may or may not have metadata / transforms
var protoComps = prototype.Components.Keys.ToList();
protoComps.Remove(_xformName);
// Fast check if the component counts match.
if (protoComps.Count != ComponentCount(uid) - 2)
return false;
// Check if entity name / description match
if (metadata.EntityName != prototype.Name ||
metadata.EntityDescription != prototype.Description)
@@ -143,28 +152,43 @@ namespace Robust.Shared.GameObjects
return false;
}
var protoData = PrototypeManager.GetPrototypeData(prototype);
var comps = _world.GetAllComponents(uid);
// Fast check if the component counts match.
// Note that transform and metadata are not included in the prototype data.
if (protoData.Count + 2 != comps.Length)
return false;
foreach (var comp in comps)
// Get default prototype data
Dictionary<string, MappingDataNode> protoData = new();
try
{
var component = (IComponent)comp!;
_context.WritingReadingPrototypes = true;
if (component.Deleted)
return false;
foreach (var compType in protoComps)
{
if (compType == _xformName)
continue;
var comp = prototype.Components[compType];
protoData.Add(compType, _serManager.WriteValueAs<MappingDataNode>(comp.Component.GetType(), comp.Component, alwaysWrite: true, context: _context));
}
_context.WritingReadingPrototypes = false;
}
catch (Exception e)
{
_sawmill.Error($"Failed to convert prototype {prototype.ID} into yaml. Exception: {e.Message}");
return false;
}
var comps = new HashSet<IComponent>(GetComponents(uid));
var compNames = new HashSet<string>(protoComps.Count);
foreach (var component in comps)
{
var compType = component.GetType();
var compName = _componentFactory.GetComponentName(compType);
if (compName == _xformName || compName == _metaReg.Name)
if (compType == typeof(MetaDataComponent) || compType == typeof(TransformComponent))
continue;
compNames.Add(compName);
// If the component isn't on the prototype then it's custom.
if (!protoData.TryGetValue(compName, out var protoMapping))
if (!protoComps.Contains(compName))
return false;
MappingDataNode compMapping;
@@ -178,9 +202,25 @@ namespace Robust.Shared.GameObjects
return false;
}
var diff = compMapping.Except(protoMapping);
if (protoData.TryGetValue(compName, out var protoMapping))
{
var diff = compMapping.Except(protoMapping);
if (diff != null && diff.Children.Count != 0)
if (diff != null && diff.Children.Count != 0)
{
return false;
}
}
else
{
return false;
}
}
// An entity may also remove components on init -> check no components are missing.
foreach (var compType in protoComps)
{
if (!compNames.Contains(compType))
return false;
}
@@ -210,7 +250,6 @@ namespace Robust.Shared.GameObjects
FlushEntities();
_eventBus.ClearEventTables();
_entitySystemManager.Shutdown();
ShutdownArch();
ClearComponents();
ShuttingDown = false;
Started = false;
@@ -218,12 +257,12 @@ namespace Robust.Shared.GameObjects
public virtual void Cleanup()
{
_componentFactory.ComponentAdded -= OnComponentAdded;
ShuttingDown = true;
FlushEntities();
_entitySystemManager.Clear();
_eventBus.Dispose();
_eventBus = null!;
ShutdownArch();
ClearComponents();
ShuttingDown = false;
@@ -272,27 +311,28 @@ namespace Robust.Shared.GameObjects
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, out _, overrides);
return CreateEntity(prototypeName, out _, overrides);
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, out _, overrides);
return CreateEntity(prototypeName, out _, overrides);
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, out _, out var xform, overrides);
_xforms.SetCoordinates(newEntity, xform, coordinates, unanchor: false);
var newEntity = CreateEntity(prototypeName, out _, overrides);
_xforms.SetCoordinates(newEntity, TransformQuery.GetComponent(newEntity), coordinates, unanchor: false);
return newEntity;
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, out _, out var transform, overrides);
var newEntity = CreateEntity(prototypeName, out _, overrides);
var transform = TransformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
{
@@ -322,19 +362,10 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public int EntityCount => _world.Size;
public int EntityCount => Entities.Count;
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntities()
{
var ents = new List<Entity>();
_world.GetEntities(_archMetaQuery, ents);
foreach (var entity in ents)
{
yield return EntityUid.FromArch(_world, entity);
}
}
public IEnumerable<EntityUid> GetEntities() => Entities;
/// <inheritdoc />
public virtual void DirtyEntity(EntityUid uid, MetaDataComponent? metadata = null)
@@ -386,7 +417,7 @@ namespace Robust.Shared.GameObjects
/// <param name="e">Entity to remove</param>
public virtual void DeleteEntity(EntityUid? uid)
{
if (uid == null || uid == EntityUid.Invalid)
if (uid == null)
return;
var e = uid.Value;
@@ -411,9 +442,9 @@ namespace Robust.Shared.GameObjects
}
// Notify all entities they are being terminated prior to detaching & deleting
var xform = TransformQuery.GetComponent(e);
RecursiveFlagEntityTermination(e, meta, xform);
RecursiveFlagEntityTermination(e, meta);
var xform = TransformQuery.GetComponent(e);
TransformComponent? parentXform = null;
if (xform.ParentUid.IsValid())
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
@@ -424,14 +455,14 @@ namespace Robust.Shared.GameObjects
private void RecursiveFlagEntityTermination(
EntityUid uid,
MetaDataComponent metadata,
TransformComponent transform)
MetaDataComponent metadata)
{
var transform = TransformQuery.GetComponent(uid);
metadata.EntityLifeStage = EntityLifeStage.Terminating;
try
{
var ev = new EntityTerminatingEvent(uid, metadata);
var ev = new EntityTerminatingEvent(uid);
EventBus.RaiseLocalEvent(uid, ref ev, true);
}
catch (Exception e)
@@ -448,7 +479,7 @@ namespace Robust.Shared.GameObjects
continue;
}
RecursiveFlagEntityTermination(child, childMeta, TransformQuery.GetComponent(child));
RecursiveFlagEntityTermination(child, childMeta);
}
}
@@ -496,12 +527,8 @@ namespace Robust.Shared.GameObjects
_sawmill.Error($"Failed to delete all children of entity: {ToPrettyString(uid)}");
// Shut down all components.
var objComps = _world.GetAllComponents(uid);
foreach (var comp in objComps)
foreach (var component in InSafeOrder(_entCompIndex[uid]))
{
var component = (IComponent)comp!;
if (component.Running)
{
try
@@ -516,7 +543,7 @@ namespace Robust.Shared.GameObjects
}
// Dispose all my components, in a safe order so transform is available
DisposeComponents(uid, metadata);
DisposeComponents(uid);
metadata.EntityLifeStage = EntityLifeStage.Deleted;
try
@@ -529,14 +556,14 @@ namespace Robust.Shared.GameObjects
}
_eventBus.OnEntityDeleted(uid);
DestroyArch(uid);
Entities.Remove(uid);
// Need to get the ID above before MetadataComponent shutdown but only remove it after everything else is done.
NetEntityLookup.Remove(metadata.NetEntity);
}
public virtual void QueueDeleteEntity(EntityUid? uid)
{
if (uid == null || uid.Value == EntityUid.Invalid)
if (uid == null)
return;
if (!QueuedDeletionsSet.Add(uid.Value))
@@ -550,7 +577,7 @@ namespace Robust.Shared.GameObjects
public bool EntityExists(EntityUid uid)
{
return IsAlive(uid);
return MetaQuery.HasComponentInternal(uid);
}
public bool EntityExists(EntityUid? uid)
@@ -569,26 +596,12 @@ namespace Robust.Shared.GameObjects
public bool Deleted(EntityUid uid)
{
return !IsAlive(uid) || !_world.TryGet(uid, out MetaDataComponent? comp) || comp!.EntityLifeStage > EntityLifeStage.Terminating;
}
/// <summary>
/// Returns whether the entity is alive inside of the ECS world.
/// </summary>
internal bool IsAlive(EntityUid uid)
{
return ((EntityReference) uid).IsAlive(_world);
}
internal bool TryAlive(EntityUid uid, out EntityReference entity)
{
entity = uid;
return entity.IsAlive(_world);
return !MetaQuery.TryGetComponentInternal(uid, out var comp) || comp.EntityDeleted;
}
public bool Deleted([NotNullWhen(false)] EntityUid? uid)
{
return !uid.HasValue || Deleted(uid.Value);
return !uid.HasValue || !MetaQuery.TryGetComponentInternal(uid.Value, out var comp) || comp.EntityDeleted;
}
/// <summary>
@@ -603,10 +616,7 @@ namespace Robust.Shared.GameObjects
DeleteEntity(e);
}
// Arch bug atm
// CleanupArch();
if (_world.Size > 0)
if (Entities.Count != 0)
_sawmill.Error("Entities were spawned while flushing entities.");
}
@@ -615,10 +625,9 @@ namespace Robust.Shared.GameObjects
/// </summary>
private protected EntityUid AllocEntity(
EntityPrototype? prototype,
out MetaDataComponent metadata,
out TransformComponent xform)
out MetaDataComponent metadata)
{
var entity = AllocEntity(out metadata, out xform);
var entity = AllocEntity(out metadata);
metadata._entityPrototype = prototype;
Dirty(entity, metadata, metadata);
return entity;
@@ -627,9 +636,16 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Allocates an entity and stores it but does not load components or do initialization.
/// </summary>
private EntityUid AllocEntity(out MetaDataComponent metadata, out TransformComponent xform)
private EntityUid AllocEntity(out MetaDataComponent metadata)
{
SpawnEntityArch(out var uid);
var uid = GenerateEntityUid();
#if DEBUG
if (EntityExists(uid))
{
throw new InvalidOperationException($"UID already taken: {uid}");
}
#endif
// we want this called before adding components
EntityAdded?.Invoke(uid);
@@ -646,15 +662,16 @@ namespace Robust.Shared.GameObjects
SetNetEntity(uid, netEntity, metadata);
Entities.Add(uid);
// add the required MetaDataComponent directly.
AddComponentInternal(uid, metadata, _metaReg, true, metadata);
AddComponentInternal(uid, metadata, _metaReg, false, true, metadata);
// allocate the required TransformComponent
xform = Unsafe.As<TransformComponent>(_componentFactory.GetComponent(_xformReg));
var xformComp = Unsafe.As<TransformComponent>(_componentFactory.GetComponent(_xformReg));
#pragma warning disable CS0618 // Type or member is obsolete
xform.Owner = uid;
xformComp.Owner = uid;
#pragma warning restore CS0618 // Type or member is obsolete
AddComponentInternal(uid, xform, true, metadata);
AddComponentInternal(uid, xformComp, false, true, metadata);
return uid;
}
@@ -662,26 +679,26 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, out TransformComponent xform, IEntityLoadContext? context = null)
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return AllocEntity(out metadata, out xform);
return AllocEntity(out metadata);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
return CreateEntity(prototype, out metadata, out xform, context);
return CreateEntity(prototype, out metadata, context);
}
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected EntityUid CreateEntity(EntityPrototype prototype, out MetaDataComponent metadata, out TransformComponent xform, IEntityLoadContext? context = null)
private protected EntityUid CreateEntity(EntityPrototype prototype, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
var entity = AllocEntity(prototype, out metadata, out xform);
var entity = AllocEntity(prototype, out metadata);
try
{
LoadEntity(metadata.EntityPrototype, entity, context);
EntityPrototype.LoadEntity(metadata.EntityPrototype, entity, ComponentFactory, this, _serManager, context);
return entity;
}
catch (Exception e)
@@ -695,105 +712,18 @@ namespace Robust.Shared.GameObjects
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
{
LoadEntity(MetaQuery.GetComponent(entity).EntityPrototype, entity, context);
EntityPrototype.LoadEntity(MetaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
}
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
{
LoadEntity(prototype, entity, context);
}
internal void LoadEntity(EntityPrototype? prototype, EntityUid entity, IEntityLoadContext? context)
{
var count = prototype?.Components.Count ?? 2;
// Lort forgiv
using var types = new PooledList<ComponentType>(count);
using var comps = new PooledList<IComponent>(count);
using var compRegs = new PooledList<ComponentRegistration>(count);
Archetype arc;
var metadata = MetaQuery.GetComponent(entity);
#if DEBUG
arc = _world.GetArchetype(entity);
#endif
if (prototype != null)
{
foreach (var (name, entry) in prototype.Components)
{
if (context != null && context.ShouldSkipComponent(name))
continue;
var fullData = context != null && context.TryGetComponent(name, out var data) ? data : entry.Component;
var comp = EntityPrototype.EnsureCompExistsAndDeserialize(entity, _componentFactory, this, _serManager, name, fullData, context as ISerializationContext, metadata);
var compType = comp.CompReg.Idx.Type;
// Don't double add an existing component, just set data above.
if (!comp.Add)
{
continue;
}
types.Add(compType);
comps.Add(comp.Comp);
compRegs.Add(comp.CompReg);
}
}
if (context != null)
{
foreach (var name in context.GetExtraComponentTypes())
{
if (prototype != null && prototype.Components.ContainsKey(name))
{
// This component also exists in the prototype.
// This means that the previous step already caught both the prototype data AND map data.
// Meaning that re-running EnsureCompExistsAndDeserialize would wipe prototype data.
continue;
}
if (!context.TryGetComponent(name, out var data))
{
throw new InvalidOperationException(
$"{nameof(IEntityLoadContext)} provided component name {name} but refused to provide data");
}
var comp = EntityPrototype.EnsureCompExistsAndDeserialize(entity, _componentFactory, this, _serManager, name, data, context as ISerializationContext, metadata);
var compType = comp.CompReg.Idx.Type;
// Don't double add an existing component, just set data above.
if (!comp.Add)
{
continue;
}
types.Add(compType);
comps.Add(comp.Comp);
compRegs.Add(comp.CompReg);
}
}
// Shouldn't be changing archetype above or we're having a bad time.
DebugTools.Assert(_world.GetArchetype(entity).Equals(arc));
// Yeah it can happen.
if (types.Count == 0)
return;
_world.AddRange(entity, types);
for (var i = 0; i < comps.Count; i++)
{
AddComponentInternal(entity, comps[i], compRegs[i], true, metadata: metadata);
}
EntityPrototype.LoadEntity(prototype, entity, ComponentFactory, this, _serManager, context);
}
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
{
try
{
// TODO: Pass this + transformcomp around
var meta = MetaQuery.GetComponent(entity);
InitializeEntity(entity, meta);
StartEntity(entity);
@@ -872,10 +802,30 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert("Why are you raising predictive events on the server?");
}
/// <summary>
/// Factory for generating a new EntityUid for an entity currently being created.
/// </summary>
internal EntityUid GenerateEntityUid()
{
return new EntityUid(NextEntityUid++);
}
/// <summary>
/// Generates a unique network id and increments <see cref="NextNetworkId"/>
/// </summary>
protected virtual NetEntity GenerateNetEntity() => new(NextNetworkId++);
private sealed class EntityDiffContext : ISerializationContext
{
public SerializationManager.SerializerProvider SerializerProvider { get; }
public bool WritingReadingPrototypes { get; set; }
public EntityDiffContext()
{
SerializerProvider = new();
SerializerProvider.RegisterSerializer(this);
}
}
}
public enum EntityMessageType : byte

View File

@@ -154,26 +154,9 @@ public partial class EntitySystem
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null) where T : IComponent?
protected void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null) where T : IComponent
{
var comp = ent.Comp;
if (comp == null && !EntityManager.TryGetComponent(ent.Owner, out comp))
return;
EntityManager.Dirty(ent.Owner, comp, meta);
}
/// <summary>
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void Dirty<T>(Entity<T, MetaDataComponent> ent) where T : IComponent?
{
var comp = ent.Comp1;
if (comp == null && !EntityManager.TryGetComponent(ent.Owner, out comp))
return;
EntityManager.Dirty(ent.Owner, comp, ent.Comp2);
EntityManager.Dirty(ent.Owner, ent.Comp, meta);
}
/// <summary>
@@ -525,7 +508,7 @@ public partial class EntitySystem
/// Retrieves whether the entity has the specified component or not.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool HasComp<T>(EntityUid uid) where T : IComponent
protected bool HasComp<T>(EntityUid uid)
{
return EntityManager.HasComponent<T>(uid);
}
@@ -543,7 +526,7 @@ public partial class EntitySystem
/// Retrieves whether the entity has the specified component or not.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool HasComp<T>([NotNullWhen(true)] EntityUid? uid) where T : IComponent
protected bool HasComp<T>([NotNullWhen(true)] EntityUid? uid)
{
return EntityManager.HasComponent<T>(uid);
}
@@ -572,7 +555,7 @@ public partial class EntitySystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void AddComp<T>(EntityUid uid, T component, bool overwrite = false) where T : IComponent
{
EntityManager.AddComponent(uid, component);
EntityManager.AddComponent(uid, component, overwrite);
}
/// <inheritdoc cref="IEntityManager.EnsureComponent&lt;T&gt;(EntityUid)"/>
@@ -614,6 +597,13 @@ public partial class EntitySystem
return EntityManager.RemoveComponentDeferred(uid, type);
}
/// <inheritdoc cref="IEntityManager.RemoveComponentDeferred(EntityUid, Component)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void RemCompDeferred(EntityUid uid, Component component)
{
EntityManager.RemoveComponentDeferred(uid, component);
}
/// <inheritdoc cref="IEntityManager.RemoveComponentDeferred(EntityUid, IComponent)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void RemCompDeferred(EntityUid uid, IComponent component)
@@ -656,6 +646,13 @@ public partial class EntitySystem
return EntityManager.RemoveComponent(uid, type);
}
/// <inheritdoc cref="IEntityManager.RemoveComponent(EntityUid, Component)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void RemComp(EntityUid uid, Component component)
{
EntityManager.RemoveComponent(uid, component);
}
/// <inheritdoc cref="IEntityManager.RemoveComponent(EntityUid, IComponent)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void RemComp(EntityUid uid, IComponent component)
@@ -1010,24 +1007,12 @@ public partial class EntitySystem
return EntityManager.EnsureEntitySet<T>(netEntities, callerEntity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntitySet<T>(HashSet<NetEntity> netEntities, EntityUid callerEntity, HashSet<EntityUid> entities)
{
EntityManager.EnsureEntitySet<T>(netEntities, callerEntity, entities);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected List<EntityUid> EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity)
{
return EntityManager.EnsureEntityList<T>(netEntities, callerEntity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity, List<EntityUid> entities)
{
EntityManager.EnsureEntityList<T>(netEntities, callerEntity, entities);
}
/// <summary>
/// Returns the <see cref="EntityUid"/> of a <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
/// </summary>

View File

@@ -32,7 +32,7 @@ namespace Robust.Shared.GameObjects
return found;
}
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
/// <inheritdoc cref="Resolve{TComp}"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
bool logMissing = true)
@@ -40,7 +40,7 @@ namespace Robust.Shared.GameObjects
return EntityManager.MetaQuery.Resolve(uid, ref component);
}
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
/// <inheritdoc cref="Resolve{TComp}"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
bool logMissing = true)

View File

@@ -7,12 +7,10 @@ namespace Robust.Shared.GameObjects
public readonly struct EntityTerminatingEvent
{
public readonly EntityUid Entity;
public readonly MetaDataComponent Metadata;
public EntityTerminatingEvent(EntityUid entity, MetaDataComponent metadata)
public EntityTerminatingEvent(EntityUid entity)
{
Entity = entity;
Metadata = metadata;
}
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Runtime.CompilerServices;
using Arch.Core;
using Arch.Core.Extensions.Dangerous;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
@@ -12,7 +9,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// This type contains a local identification number of an entity.
/// This type contains a network identification number of an entity.
/// This can be used by the EntityManager to access an entity
/// </summary>
[CopyByRef]
@@ -20,40 +17,22 @@ namespace Robust.Shared.GameObjects
{
public readonly int Id;
public readonly int Version;
/// <summary>
/// An Invalid entity UID you can compare against.
/// </summary>
public static readonly EntityUid Invalid = new(-1 + ArchUidOffset, -1 + ArchVersionOffset);
public static readonly EntityUid Invalid = new(0);
/// <summary>
/// The first entity UID the entityManager should use when the manager is initialized.
/// </summary>
public static readonly EntityUid FirstUid = new(0 + ArchUidOffset, 1 + ArchVersionOffset);
internal const int ArchUidOffset = 1;
internal const int ArchVersionOffset = 1;
public EntityUid()
{
Id = Invalid.Id;
Version = Invalid.Version;
}
internal EntityUid(EntityReference reference)
{
Id = reference.Entity.Id + ArchUidOffset;
Version = reference.Version + ArchVersionOffset;
}
public static readonly EntityUid FirstUid = new(1);
/// <summary>
/// Creates an instance of this structure, with the given network ID.
/// </summary>
public EntityUid(int id, int version)
public EntityUid(int id)
{
Id = id;
Version = version;
}
public bool Valid => IsValid();
@@ -61,16 +40,16 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Creates an entity UID by parsing a string number.
/// </summary>
public static EntityUid Parse(ReadOnlySpan<char> uid, ReadOnlySpan<char> version)
public static EntityUid Parse(ReadOnlySpan<char> uid)
{
return new EntityUid(int.Parse(uid), int.Parse(version));
return new EntityUid(int.Parse(uid));
}
public static bool TryParse(ReadOnlySpan<char> uid, ReadOnlySpan<char> version, out EntityUid entityUid)
public static bool TryParse(ReadOnlySpan<char> uid, out EntityUid entityUid)
{
try
{
entityUid = Parse(uid, version);
entityUid = Parse(uid);
return true;
}
catch (FormatException)
@@ -80,15 +59,6 @@ namespace Robust.Shared.GameObjects
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EntityUid FromArch(in World world, in Entity entity)
{
return new EntityUid(entity.Id + ArchUidOffset, world.Reference(entity).Version + ArchVersionOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetArchId() => Id - ArchUidOffset;
/// <summary>
/// Checks if the ID value is valid. Does not check if it identifies
/// a valid Entity.
@@ -96,13 +66,13 @@ namespace Robust.Shared.GameObjects
[Pure]
public bool IsValid()
{
return Id > Invalid.Id;
return Id > 0;
}
/// <inheritdoc />
public bool Equals(EntityUid other)
{
return Id == other.Id && Version == other.Version;
return Id == other.Id;
}
/// <inheritdoc />
@@ -115,10 +85,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return Id.GetHashCode() * 397 ^ Version.GetHashCode();
}
return Id;
}
/// <summary>
@@ -126,7 +93,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public static bool operator ==(EntityUid a, EntityUid b)
{
return a.Id == b.Id && a.Version == b.Version;
return a.Id == b.Id;
}
/// <summary>
@@ -146,21 +113,6 @@ namespace Robust.Shared.GameObjects
return self.Id;
}
public static implicit operator Entity(EntityUid self)
{
return DangerousEntityExtensions.CreateEntityStruct(self.Id - ArchUidOffset, 0);
}
public static implicit operator EntityReference(EntityUid other)
{
return DangerousEntityExtensions.CreateEntityReferenceStruct(other.Id - ArchUidOffset, other.Version - ArchVersionOffset, 0);
}
public static implicit operator EntityUid(EntityReference other)
{
return new EntityUid(other);
}
/// <inheritdoc />
public override string ToString()
{

View File

@@ -71,7 +71,7 @@ namespace Robust.Shared.GameObjects
/// <param name="uid">Entity being modified.</param>
/// <param name="component">Component to add.</param>
/// <param name="overwrite">Should it overwrite existing components?</param>
void AddComponent<T>(EntityUid uid, T component, MetaDataComponent? metadata = null) where T : IComponent;
void AddComponent<T>(EntityUid uid, T component, bool overwrite = false, MetaDataComponent? metadata = null) where T : IComponent;
/// <summary>
/// Removes the component with the specified reference type,
@@ -136,6 +136,14 @@ namespace Robust.Shared.GameObjects
/// <param name="component">Component to remove.</param>
void RemoveComponentDeferred(EntityUid uid, IComponent component);
/// <summary>
/// Immediately shuts down a component, but defers the removal and deletion until the end of the tick.
/// Throws if the given component does not belong to the entity.
/// </summary>
/// <param name="uid">Entity UID to modify.</param>
/// <param name="component">Component to remove.</param>
void RemoveComponentDeferred(EntityUid uid, Component component);
/// <summary>
/// Removes all components from an entity, except the required components.
/// </summary>
@@ -156,7 +164,7 @@ namespace Robust.Shared.GameObjects
/// <typeparam name="T">Component reference type to check for.</typeparam>
/// <param name="uid">Entity UID to check.</param>
/// <returns>True if the entity has the component type, otherwise false.</returns>
bool HasComponent<T>(EntityUid uid) where T : IComponent;
bool HasComponent<T>(EntityUid uid);
/// <summary>
/// Checks if the entity has a component type.
@@ -164,7 +172,7 @@ namespace Robust.Shared.GameObjects
/// <typeparam name="T">Component reference type to check for.</typeparam>
/// <param name="uid">Entity UID to check.</param>
/// <returns>True if the entity has the component type, otherwise false.</returns>
bool HasComponent<T>(EntityUid? uid) where T : IComponent;
bool HasComponent<T>(EntityUid? uid);
/// <summary>
/// Checks if the entity has a component type.
@@ -273,7 +281,7 @@ namespace Robust.Shared.GameObjects
/// <param name="uid">Entity UID to check.</param>
/// <param name="component">Component of the specified type (if exists).</param>
/// <returns>If the component existed in the entity.</returns>
bool TryGetComponent<T>(EntityUid uid, [NotNullWhen(true)] out T? component) where T : IComponent?;
bool TryGetComponent<T>(EntityUid uid, [NotNullWhen(true)] out T? component);
/// <summary>
/// Returns the component of a specific type.
@@ -282,7 +290,7 @@ namespace Robust.Shared.GameObjects
/// <param name="uid">Entity UID to check.</param>
/// <param name="component">Component of the specified type (if exists).</param>
/// <returns>If the component existed in the entity.</returns>
bool TryGetComponent<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component) where T : IComponent?;
bool TryGetComponent<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component);
/// <summary>
/// Returns the component of a specific type.
@@ -497,7 +505,5 @@ namespace Robust.Shared.GameObjects
/// Culls all components from the collection that are marked as deleted. This needs to be called often.
/// </summary>
void CullRemovedComponents();
void CleanupArch();
}
}

View File

@@ -19,7 +19,6 @@ public partial interface IEntityManager
EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
EntityUid[] SpawnEntities(MapCoordinates coordinates, string? prototype, int count);
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames);
@@ -53,7 +52,7 @@ public partial interface IEntityManager
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <summary>
@@ -65,7 +64,7 @@ public partial interface IEntityManager
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <summary>
@@ -85,8 +84,8 @@ public partial interface IEntityManager
/// instead attempt to spawn the entity next to the target's parent.
/// </summary>
EntityUid SpawnNextToOrDrop(
string? protoName,
EntityUid target,
TransformComponent? xform = null,
string? protoName,
EntityUid target,
TransformComponent? xform = null,
ComponentRegistry? overrides = null);
}

View File

@@ -171,16 +171,11 @@ public sealed partial class EntityLookupSystem
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd)
{
if (!_xformQuery.TryGetComponent(uid, out var xform))
{
Log.Error($"Encountered deleted entity {uid} while performing entity lookup.");
return;
}
var childEnumerator = _xformQuery.GetComponent(uid).ChildEnumerator;
toAdd.Add(uid);
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
toAdd.Add(child.Value);
RecursiveAdd(child.Value, ref toAdd);
}
}
@@ -190,11 +185,6 @@ public sealed partial class EntityLookupSystem
if ((flags & LookupFlags.Contained) == 0x0 || intersecting.Count == 0)
return;
// TODO PERFORMANCE.
// toAdd only exists because we can't add directly to intersecting w/o enumeration issues.
// If we assume that there are more entities in containers than there are entities in the intersecting set, then
// we would be better off creating a fixed-size EntityUid array and coping all intersecting entities into that
// instead of creating a value list here that needs to be resized.
var toAdd = new ValueList<EntityUid>();
foreach (var uid in intersecting)
@@ -206,6 +196,7 @@ public sealed partial class EntityLookupSystem
{
foreach (var contained in con.ContainedEntities)
{
toAdd.Add(contained);
RecursiveAdd(contained, ref toAdd);
}
}

View File

@@ -6,11 +6,7 @@ using Robust.Shared.Collections;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
@@ -19,6 +15,18 @@ public sealed partial class EntityLookupSystem
{
#region Private
private void AddComponentsIntersecting<T>(
EntityUid lookupUid,
HashSet<T> intersecting,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<T> query) where T : IComponent
{
var intersectingEntities = new HashSet<Entity<T>>();
AddEntitiesIntersecting(lookupUid, intersectingEntities, worldAABB, flags, query);
intersecting.UnionWith(intersectingEntities.Select(e => e.Comp));
}
private void AddEntitiesIntersecting<T>(
EntityUid lookupUid,
HashSet<Entity<T>> intersecting,
@@ -80,155 +88,6 @@ public sealed partial class EntityLookupSystem
}
}
private void AddEntitiesIntersecting<T>(
EntityUid lookupUid,
HashSet<Entity<T>> intersecting,
IPhysShape shape,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<T> query) where T : IComponent
{
var lookup = _broadQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
var localAABB = invMatrix.TransformBox(worldAABB);
var transform = new Transform(0);
var state = new QueryState<T>(
intersecting,
shape,
transform,
_fixtures,
_physics,
_transform,
_manifoldManager,
query,
_fixturesQuery,
(flags & LookupFlags.Sensors) != 0
);
if ((flags & LookupFlags.Dynamic) != 0x0)
{
lookup.DynamicTree.QueryAabb(ref state, static (ref QueryState<T> state, in FixtureProxy value) =>
{
if (!state.Sensors && !value.Fixture.Hard)
return true;
if (!state.Query.TryGetComponent(value.Entity, out var comp))
return true;
var intersectingTransform = state.Physics.GetPhysicsTransform(value.Entity);
if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform))
{
return true;
}
state.Intersecting.Add((value.Entity, comp));
return true;
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & (LookupFlags.Static)) != 0x0)
{
lookup.StaticTree.QueryAabb(ref state, static (ref QueryState<T> state, in FixtureProxy value) =>
{
if (!state.Sensors && !value.Fixture.Hard)
return true;
if (!state.Query.TryGetComponent(value.Entity, out var comp))
return true;
var intersectingTransform = state.Physics.GetPhysicsTransform(value.Entity);
if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform))
{
return true;
}
state.Intersecting.Add((value.Entity, comp));
return true;
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
{
lookup.StaticSundriesTree.QueryAabb(ref state, static (ref QueryState<T> state, in EntityUid value) =>
{
if (!state.Query.TryGetComponent(value, out var comp))
return true;
var intersectingTransform = state.Physics.GetPhysicsTransform(value);
if (state.FixturesQuery.TryGetComponent(value, out var fixtures))
{
bool anyFixture = false;
foreach (var fixture in fixtures.Fixtures.Values)
{
if (!state.Sensors && !fixture.Hard)
continue;
anyFixture = true;
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
if (state.Manifolds.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform,
intersectingTransform))
{
state.Intersecting.Add((value, comp));
return true;
}
}
}
if (anyFixture)
return true;
}
if (state.Fixtures.TestPoint(state.Shape, state.Transform, intersectingTransform.Position))
state.Intersecting.Add((value, comp));
return true;
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.Sundries) != 0x0)
{
lookup.SundriesTree.QueryAabb(ref state, static (ref QueryState<T> state,
in EntityUid value) =>
{
if (!state.Query.TryGetComponent(value, out var comp))
return true;
var intersectingTransform = state.Physics.GetPhysicsTransform(value);
if (state.FixturesQuery.TryGetComponent(value, out var fixtures))
{
bool anyFixture = false;
foreach (var fixture in fixtures.Fixtures.Values)
{
if (!state.Sensors && !fixture.Hard)
continue;
anyFixture = true;
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
if (state.Manifolds.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform,
intersectingTransform))
{
state.Intersecting.Add((value, comp));
return true;
}
}
}
if (anyFixture)
return true;
}
if (state.Fixtures.TestPoint(state.Shape, state.Transform, intersectingTransform.Position))
state.Intersecting.Add((value, comp));
return true;
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
}
private bool AnyComponentsIntersecting<T>(
EntityUid lookupUid,
Box2 worldAABB,
@@ -411,7 +270,7 @@ public sealed partial class EntityLookupSystem
if (xform.MapID != mapId ||
!worldAABB.Contains(_transform.GetWorldPosition(xform)) ||
((flags & LookupFlags.Contained) == 0x0 &&
_container.IsEntityOrParentInContainer(uid, null, xform)))
_container.IsEntityOrParentInContainer(uid, _metaQuery.GetComponent(uid), xform, _metaQuery, _xformQuery)))
{
continue;
}
@@ -449,6 +308,15 @@ public sealed partial class EntityLookupSystem
return false;
}
[Obsolete]
public HashSet<IComponent> GetComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
var intersectingEntities = new HashSet<Entity<IComponent>>();
GetEntitiesIntersecting(type, mapId, worldAABB, intersectingEntities, flags);
var intersecting = new HashSet<IComponent>(intersectingEntities.Select(e => e.Comp));
return intersecting;
}
public void GetEntitiesIntersecting(Type type, MapId mapId, Box2 worldAABB, HashSet<Entity<IComponent>> intersecting, LookupFlags flags = DefaultFlags)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
@@ -464,7 +332,7 @@ public sealed partial class EntityLookupSystem
if (xform.MapID != mapId ||
!worldAABB.Contains(_transform.GetWorldPosition(xform)) ||
((flags & LookupFlags.Contained) == 0x0 &&
_container.IsEntityOrParentInContainer(uid, _metaQuery.GetComponent(uid), xform)))
_container.IsEntityOrParentInContainer(uid, _metaQuery.GetComponent(uid), xform, _metaQuery, _xformQuery)))
{
continue;
}
@@ -498,6 +366,14 @@ public sealed partial class EntityLookupSystem
}
}
[Obsolete]
public HashSet<T> GetComponentsIntersecting<T>(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags) where T : IComponent
{
var intersectingEntities = new HashSet<Entity<T>>();
GetEntitiesIntersecting(mapId, worldAABB, intersectingEntities, flags);
return new HashSet<T>(intersectingEntities.Select(e => e.Comp));
}
public void GetEntitiesIntersecting<T>(MapId mapId, Box2 worldAABB, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
if (mapId == MapId.Nullspace) return;
@@ -540,163 +416,14 @@ public sealed partial class EntityLookupSystem
#endregion
#region IPhysShape
public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, HashSet<Entity<IComponent>> intersecting, LookupFlags flags = DefaultFlags)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
if (mapId == MapId.Nullspace)
return;
var shapeTransform = new Transform(0);
var worldAABB = shape.ComputeAABB(shapeTransform, 0);
var sensors = (flags & LookupFlags.Sensors) != 0;
if (!UseBoundsQuery(type, worldAABB.Height * worldAABB.Width))
{
foreach (var (uid, comp) in EntityManager.GetAllComponents(type, true))
{
var xform = _xformQuery.GetComponent(uid);
var (pos, rot) = _transform.GetWorldPositionRotation(xform);
if (xform.MapID != mapId ||
!worldAABB.Contains(pos) ||
((flags & LookupFlags.Contained) == 0x0 &&
_container.IsEntityOrParentInContainer(uid, _metaQuery.GetComponent(uid), xform)))
{
continue;
}
if (_fixturesQuery.TryGetComponent(uid, out var fixtures))
{
var transform = new Transform(pos, rot);
bool anyFixture = false;
foreach (var fixture in fixtures.Fixtures.Values)
{
if (!sensors && !fixture.Hard)
continue;
anyFixture = true;
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
if (_manifoldManager.TestOverlap(shape, 0, fixture.Shape, i, shapeTransform, transform))
{
goto found;
}
}
}
if (anyFixture)
continue;
}
if (!_fixtures.TestPoint(shape, shapeTransform, pos))
continue;
found:
intersecting.Add((uid, comp));
}
}
else
{
var query = EntityManager.GetEntityQuery(type);
// Get grid entities
var state = new GridQueryState<IComponent>(intersecting, shape, worldAABB, this, flags, query);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent grid, ref GridQueryState<IComponent> state) =>
{
state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, state.WorldAABB, state.Flags, state.Query);
return true;
}, (flags & LookupFlags.Approximate) != 0x0);
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, shape, worldAABB, flags, query);
AddContained(intersecting, flags, query);
}
}
public void GetEntitiesIntersecting<T>(MapId mapId, IPhysShape shape, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
if (mapId == MapId.Nullspace) return;
var shapeTransform = new Transform(0);
var worldAABB = shape.ComputeAABB(shapeTransform, 0);
var sensors = (flags & LookupFlags.Sensors) != 0;
if (!UseBoundsQuery<T>(worldAABB.Height * worldAABB.Width))
{
var query = AllEntityQuery<T, TransformComponent>();
while (query.MoveNext(out var uid, out var comp, out var xform))
{
var (pos, rot) = _transform.GetWorldPositionRotation(xform);
if (xform.MapID != mapId || !worldAABB.Contains(pos))
continue;
if (_fixturesQuery.TryGetComponent(uid, out var fixtures))
{
var transform = new Transform(pos, rot);
bool anyFixture = false;
foreach (var fixture in fixtures.Fixtures.Values)
{
if (!sensors && !fixture.Hard)
continue;
anyFixture = true;
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
if (_manifoldManager.TestOverlap(shape, 0, fixture.Shape, i, shapeTransform, transform))
{
goto found;
}
}
}
if (anyFixture)
continue;
}
if (!_fixtures.TestPoint(shape, shapeTransform, pos))
continue;
found:
entities.Add((uid, comp));
}
}
else
{
var query = GetEntityQuery<T>();
// Get grid entities
var state = (this, shape, worldAABB, flags, query, entities);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent grid,
ref (EntityLookupSystem system,
IPhysShape shape,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<T> query,
HashSet<Entity<T>> intersecting) tuple) =>
{
tuple.system.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.shape, tuple.worldAABB, tuple.flags, tuple.query);
return true;
}, (flags & LookupFlags.Approximate) != 0x0);
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, entities, shape, worldAABB, flags, query);
AddContained(entities, flags, query);
}
}
#endregion
#region EntityCoordinates
public HashSet<T> GetComponentsInRange<T>(EntityCoordinates coordinates, float range) where T : IComponent
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
return GetComponentsInRange<T>(mapPos, range);
}
public void GetEntitiesInRange<T>(EntityCoordinates coordinates, float range, HashSet<Entity<T>> entities) where T : IComponent
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
@@ -714,34 +441,35 @@ public sealed partial class EntityLookupSystem
#region MapCoordinates
[Obsolete]
public HashSet<IComponent> GetComponentsInRange(Type type, MapCoordinates coordinates, float range)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
return GetComponentsInRange(type, coordinates.MapId, coordinates.Position, range);
}
public HashSet<Entity<IComponent>> GetEntitiesInRange(Type type, MapCoordinates coordinates, float range)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
var entities = new HashSet<Entity<IComponent>>();
GetEntitiesInRange(type, coordinates, range, entities);
GetEntitiesInRange(type, coordinates.MapId, coordinates.Position, range, entities);
return entities;
}
public void GetEntitiesInRange(Type type, MapCoordinates coordinates, float range, HashSet<Entity<IComponent>> entities)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
GetEntitiesInRange(type, coordinates.MapId, coordinates.Position, range, entities);
}
[Obsolete]
public HashSet<T> GetComponentsInRange<T>(MapCoordinates coordinates, float range) where T : IComponent
{
return GetComponentsInRange<T>(coordinates.MapId, coordinates.Position, range);
}
public void GetEntitiesInRange<T>(MapCoordinates coordinates, float range, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
public void GetEntitiesInRange<T>(MapCoordinates coordinates, float range, HashSet<Entity<T>> entities) where T : IComponent
{
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities, flags);
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities);
}
public HashSet<Entity<T>> GetEntitiesInRange<T>(MapCoordinates coordinates, float range, LookupFlags flags = DefaultFlags) where T : IComponent
public HashSet<Entity<T>> GetEntitiesInRange<T>(MapCoordinates coordinates, float range) where T : IComponent
{
var entities = new HashSet<Entity<T>>();
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities, flags);
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities);
return entities;
}
@@ -749,15 +477,40 @@ public sealed partial class EntityLookupSystem
#region MapId
public void GetEntitiesInRange(Type type, MapId mapId, Vector2 worldPos, float range, HashSet<Entity<IComponent>> entities, LookupFlags flags = DefaultFlags)
public bool AnyComponentsInRange(Type type, MapId mapId, Vector2 worldPos, float range)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
DebugTools.Assert(range > 0, "Range must be a positive float");
if (mapId == MapId.Nullspace) return false;
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(worldPos - rangeVec, worldPos + rangeVec);
return AnyComponentsIntersecting(type, mapId, worldAABB);
}
[Obsolete]
public HashSet<IComponent> GetComponentsInRange(Type type, MapId mapId, Vector2 worldPos, float range)
{
var entities = new HashSet<Entity<IComponent>>();
GetEntitiesInRange(type, mapId, worldPos, range, entities);
return new HashSet<IComponent>(entities.Select(e => e.Comp));
}
public void GetEntitiesInRange(Type type, MapId mapId, Vector2 worldPos, float range, HashSet<Entity<IComponent>> entities)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
DebugTools.Assert(range > 0, "Range must be a positive float");
if (mapId == MapId.Nullspace) return;
var circle = new PhysShapeCircle(range, worldPos);
GetEntitiesIntersecting(type, mapId, circle, entities, flags);
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(worldPos - rangeVec, worldPos + rangeVec);
GetEntitiesIntersecting(type, mapId, worldAABB, entities);
}
[Obsolete]
@@ -768,41 +521,18 @@ public sealed partial class EntityLookupSystem
return new HashSet<T>(entities.Select(e => e.Comp));
}
public void GetEntitiesInRange<T>(MapId mapId, Vector2 worldPos, float range, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
public void GetEntitiesInRange<T>(MapId mapId, Vector2 worldPos, float range, HashSet<Entity<T>> entities) where T : IComponent
{
GetEntitiesInRange(mapId, new PhysShapeCircle(range, worldPos), entities, flags);
}
public void GetEntitiesInRange<T>(MapId mapId, IPhysShape shape, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
DebugTools.Assert(shape.Radius > 0, "Range must be a positive float");
DebugTools.Assert(range > 0, "Range must be a positive float");
if (mapId == MapId.Nullspace) return;
GetEntitiesIntersecting(mapId, shape, entities, flags);
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(worldPos - rangeVec, worldPos + rangeVec);
GetEntitiesIntersecting(mapId, worldAABB, entities);
}
#endregion
private readonly record struct GridQueryState<T>(
HashSet<Entity<T>> Intersecting,
IPhysShape Shape,
Box2 WorldAABB,
EntityLookupSystem Lookup,
LookupFlags Flags,
EntityQuery<T> Query
) where T : IComponent;
private readonly record struct QueryState<T>(
HashSet<Entity<T>> Intersecting,
IPhysShape Shape,
Transform Transform,
FixtureSystem Fixtures,
SharedPhysicsSystem Physics,
SharedTransformSystem TransformSystem,
IManifoldManager Manifolds,
EntityQuery<T> Query,
EntityQuery<FixturesComponent> FixturesQuery,
bool Sensors
) where T : IComponent;
}

View File

@@ -11,11 +11,9 @@ using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.BroadPhase;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -74,14 +72,11 @@ public record struct WorldAABBEvent
public sealed partial class EntityLookupSystem : EntitySystem
{
[Dependency] private readonly IManifoldManager _manifoldManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<BroadphaseComponent> _broadQuery;
@@ -113,6 +108,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
SubscribeLocalEvent<BroadphaseComponent, EntityTerminatingEvent>(OnBroadphaseTerminating);
SubscribeLocalEvent<BroadphaseComponent, ComponentAdd>(OnBroadphaseAdd);
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<MapChangedEvent>(OnMapChange);
SubscribeLocalEvent<MoveEvent>(OnMove);
@@ -191,6 +187,12 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
}
private void OnGridAdd(GridAddEvent ev)
{
// Must be done before initialization as that's when broadphase data starts getting set.
EnsureComp<BroadphaseComponent>(ev.EntityUid);
}
private void OnBroadphaseAdd(EntityUid uid, BroadphaseComponent component, ComponentAdd args)
{
component.StaticSundriesTree = new DynamicTree<EntityUid>(
@@ -366,7 +368,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
FixturesComponent manager,
EntityQuery<TransformComponent> xformQuery)
{
DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform));
DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform, null, xformQuery));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static));
DebugTools.Assert(broadphase.Owner == broadUid);
@@ -432,7 +434,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
{
DebugTools.Assert(!_container.IsEntityOrParentInContainer(uid));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, default, false, staticBody));
xform.Broadphase ??= new BroadphaseData(broadUid, EntityUid.Invalid, false, staticBody);
xform.Broadphase ??= new(broadUid, default, false, staticBody);
(staticBody ? broadphase.StaticSundriesTree : broadphase.SundriesTree).AddOrUpdate(uid, aabb);
}
@@ -841,7 +843,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
TransformComponent xform,
[NotNullWhen(true)] out BroadphaseComponent? broadphase)
{
if (xform.MapID == MapId.Nullspace || _container.IsEntityOrParentInContainer(xform.Owner, null, xform))
if (xform.MapID == MapId.Nullspace || _container.IsEntityOrParentInContainer(xform.Owner, null, xform, null, _xformQuery))
{
broadphase = null;
return false;

View File

@@ -6,20 +6,20 @@ namespace Robust.Shared.GameObjects;
public abstract class SharedEyeSystem : EntitySystem
{
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
/// <summary>
/// Refreshes all values for IEye with the component.
/// </summary>
public void UpdateEye(Entity<EyeComponent?> entity)
public void UpdateEye(EyeComponent component)
{
var component = entity.Comp;
if (!Resolve(entity, ref component))
if (component._eye == null)
return;
component.Eye.Offset = component.Offset;
component.Eye.DrawFov = component.DrawFov;
component.Eye.DrawLight = component.DrawLight;
component.Eye.Rotation = component.Rotation;
component.Eye.Zoom = component.Zoom;
component._eye.Offset = component.Offset;
component._eye.DrawFov = component.DrawFov;
component._eye.Rotation = component.Rotation;
component._eye.Zoom = component.Zoom;
}
public void SetOffset(EntityUid uid, Vector2 value, EyeComponent? eyeComponent = null)
@@ -31,7 +31,10 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.Offset = value;
eyeComponent.Eye.Offset = value;
if (eyeComponent._eye != null)
{
eyeComponent._eye.Offset = value;
}
Dirty(uid, eyeComponent);
}
@@ -44,23 +47,13 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.DrawFov = value;
eyeComponent.Eye.DrawFov = value;
if (eyeComponent._eye != null)
{
eyeComponent._eye.DrawFov = value;
}
Dirty(uid, eyeComponent);
}
public void SetDrawLight(Entity<EyeComponent?> entity, bool value)
{
if (!Resolve(entity, ref entity.Comp))
return;
if (entity.Comp.DrawLight == value)
return;
entity.Comp.DrawLight = value;
entity.Comp.Eye.DrawLight = value;
Dirty(entity);
}
public void SetRotation(EntityUid uid, Angle rotation, EyeComponent? eyeComponent = null)
{
if (!Resolve(uid, ref eyeComponent))
@@ -70,7 +63,10 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.Rotation = rotation;
eyeComponent.Eye.Rotation = rotation;
if (eyeComponent._eye != null)
{
eyeComponent._eye.Rotation = rotation;
}
}
public void SetTarget(EntityUid uid, EntityUid? value, EyeComponent? eyeComponent = null)
@@ -94,7 +90,10 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.Zoom = value;
eyeComponent.Eye.Zoom = value;
if (eyeComponent._eye != null)
{
eyeComponent._eye.Zoom = value;
}
}
public void SetVisibilityMask(EntityUid uid, int value, EyeComponent? eyeComponent = null)

View File

@@ -103,7 +103,7 @@ internal sealed class SharedGridTraversalSystem : EntitySystem
// Attach them to map / they are on an invalid grid
if (oldGridId != null)
{
_transform.SetParent(entity, xform, xform.MapUid!.Value);
_transform.SetParent(entity, xform, _mapManager.GetMapEntityIdOrThrow(xform.MapID));
var ev = new ChangedGridEvent(entity, oldGridId, null);
RaiseLocalEvent(entity, ref ev);
}

View File

@@ -429,7 +429,8 @@ public abstract partial class SharedMapSystem
private void OnGridInit(EntityUid uid, MapGridComponent component, ComponentInit args)
{
var xform = _xformQuery.GetComponent(uid);
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
// Force networkedmapmanager to send it due to non-ECS legacy code.
var curTick = _timing.CurTick;
@@ -442,7 +443,7 @@ public abstract partial class SharedMapSystem
component.LastTileModifiedTick = curTick;
if (xform.MapUid != null && xform.MapUid != uid)
_transform.SetParent(uid, xform, xform.MapUid.Value);
_transform.SetParent(uid, xform, xform.MapUid.Value, xformQuery);
if (!HasComp<MapComponent>(uid))
{
@@ -558,10 +559,7 @@ public abstract partial class SharedMapSystem
internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnlySet<MapChunk> chunks)
{
if (HasComp<MapComponent>(uid))
{
ClearEmptyMapChunks(uid, grid, chunks);
return;
}
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
var removedChunks = new List<MapChunk>();
@@ -624,23 +622,6 @@ public abstract partial class SharedMapSystem
RaiseLocalEvent(ref ev);
}
/// <summary>
/// Variation of <see cref="RegenerateCollision(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Map.Components.MapGridComponent,Robust.Shared.Map.MapChunk)"/>
/// that only simply removes empty chunks. Intended for use with "planet-maps", which have no grid fixtures.
/// </summary>
private void ClearEmptyMapChunks(EntityUid uid, MapGridComponent grid, IReadOnlySet<MapChunk> modified)
{
foreach (var chunk in modified)
{
DebugTools.Assert(chunk.FilledTiles >= 0);
if (chunk.FilledTiles > 0)
continue;
DebugTools.AssertEqual(chunk.Fixtures.Count, 0, "maps should not have grid-chunk fixtures");
RemoveChunk(uid, grid, chunk.Indices);
}
}
#region TileAccess
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
@@ -688,8 +669,9 @@ public abstract partial class SharedMapSystem
public IEnumerable<TileRef> GetAllTiles(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
foreach (var chunk in grid.Chunks.Values)
foreach (var kvChunk in grid.Chunks)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
@@ -740,7 +722,7 @@ public abstract partial class SharedMapSystem
if (tiles.Count == 0)
return;
var modified = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
@@ -748,7 +730,7 @@ public abstract partial class SharedMapSystem
if (!grid.Chunks.TryGetValue(chunkIndex, out var chunk))
{
if (tile.IsEmpty)
continue;
return;
grid.Chunks[chunkIndex] = chunk = new MapChunk(chunkIndex.X, chunkIndex.Y, grid.ChunkSize)
{
@@ -757,67 +739,24 @@ public abstract partial class SharedMapSystem
}
var offset = chunk.GridTileToChunkTile(gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
if (SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile))
modified.Add(chunk);
SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile);
}
foreach (var chunk in modified)
foreach (var chunk in chunks)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(uid, grid, modified);
}
public TilesEnumerator GetLocalTilesEnumerator(EntityUid uid, MapGridComponent grid, Box2 aabb,
bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, aabb);
return enumerator;
}
public TilesEnumerator GetTilesEnumerator(EntityUid uid, MapGridComponent grid, Box2 aabb, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var invMatrix = _transform.GetInvWorldMatrix(uid);
var localAABB = invMatrix.TransformBox(aabb);
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localAABB);
return enumerator;
}
public TilesEnumerator GetTilesEnumerator(EntityUid uid, MapGridComponent grid, Box2Rotated bounds, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var invMatrix = _transform.GetInvWorldMatrix(uid);
var localAABB = invMatrix.TransformBox(bounds);
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localAABB);
return enumerator;
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 localAABB, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localAABB);
while (enumerator.MoveNext(out var tileRef))
{
yield return tileRef;
}
RegenerateCollision(uid, grid, chunks);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localAABB);
while (enumerator.MoveNext(out var tileRef))
{
yield return tileRef;
}
return GetLocalTilesIntersecting(uid, grid, localAABB, ignoreEmpty, predicate);
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea, bool ignoreEmpty = true,
@@ -826,11 +765,9 @@ public abstract partial class SharedMapSystem
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localArea);
while (enumerator.MoveNext(out var tileRef))
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tileRef;
yield return tile;
}
}
@@ -840,11 +777,46 @@ public abstract partial class SharedMapSystem
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localArea);
while (enumerator.MoveNext(out var tileRef))
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tileRef;
yield return tile;
}
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(uid, grid, new Vector2i(x, y));
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
yield return tile;
}
else if (!ignoreEmpty)
{
var tile = new TileRef(uid, x, y, Tile.Empty);
if (predicate == null || predicate(tile))
yield return tile;
}
}
}
}
@@ -994,25 +966,12 @@ public abstract partial class SharedMapSystem
// create an entire chunk for it.
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk))
return Enumerable.Empty<EntityUid>();
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
var chunkTile = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
}
public void GetAnchoredEntities(Entity<MapGridComponent> grid, Vector2i pos, List<EntityUid> list)
{
var gridChunkPos = GridTileToChunkIndices(grid.Owner, grid.Comp, pos);
if (!grid.Comp.Chunks.TryGetValue(gridChunkPos, out var chunk))
return;
var chunkTile = chunk.GridTileToChunkTile(pos);
var anchored = chunk.GetSnapGrid((ushort) chunkTile.X, (ushort) chunkTile.Y);
if (anchored != null)
list.AddRange(anchored);
}
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
@@ -1029,32 +988,22 @@ public abstract partial class SharedMapSystem
public IEnumerable<EntityUid> GetLocalAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
var enumerator = new TilesEnumerator(this, true, null, uid, grid, localAABB);
while (enumerator.MoveNext(out var tileRef))
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localAABB, true, null))
{
var anchoredEnumerator = GetAnchoredEntitiesEnumerator(uid, grid, tileRef.GridIndices);
while (anchoredEnumerator.MoveNext(out var ent))
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent.Value;
yield return ent;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
var invWorldMatrix = _transform.GetInvWorldMatrix(uid);
var localAABB = invWorldMatrix.TransformBox(worldAABB);
var enumerator = new TilesEnumerator(this, true, null, uid, grid, localAABB);
while (enumerator.MoveNext(out var tileRef))
foreach (var tile in GetTilesIntersecting(uid, grid, worldAABB))
{
var anchoredEnumerator = GetAnchoredEntitiesEnumerator(uid, grid, tileRef.GridIndices);
while (anchoredEnumerator.MoveNext(out var ent))
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent.Value;
yield return ent;
}
}
}
@@ -1310,9 +1259,6 @@ public abstract partial class SharedMapSystem
}
public Vector2i GridTileToChunkIndices(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
=> GridTileToChunkIndices(grid, gridTile);
public Vector2i GridTileToChunkIndices(MapGridComponent grid, Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float) grid.ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float) grid.ChunkSize);
@@ -1355,36 +1301,6 @@ public abstract partial class SharedMapSystem
return true;
}
public bool TryGetTile(MapGridComponent grid, Vector2i indices, out Tile tile)
{
var chunkIndices = GridTileToChunkIndices(grid, indices);
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = chunk.Tiles[cTileIndices.X, cTileIndices.Y];
return true;
}
/// <summary>
/// Attempts to get the <see cref="ITileDefinition"/> for the tile at the given grid indices. This will throw an
/// exception if the tile at this location has no registered tile definition.
/// </summary>
public bool TryGetTileDef(MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out ITileDefinition? tileDef)
{
if (!TryGetTile(grid, indices, out var tile))
{
tileDef = null;
return false;
}
tileDef = _tileMan[tile.TypeId];
return true;
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords), out tile);
@@ -1441,98 +1357,4 @@ public abstract partial class SharedMapSystem
RegenerateCollision(uid, grid, mapChunk);
}
}
/// <summary>
/// Iterates the local tiles of the specified data.
/// </summary>
public struct TilesEnumerator
{
private readonly SharedMapSystem _mapSystem;
private readonly EntityUid _uid;
private readonly MapGridComponent _grid;
private readonly bool _ignoreEmpty;
private readonly Predicate<TileRef>? _predicate;
private readonly int _lowerY;
private readonly int _upperX;
private readonly int _upperY;
private int _x;
private int _y;
public TilesEnumerator(
SharedMapSystem mapSystem,
bool ignoreEmpty,
Predicate<TileRef>? predicate,
EntityUid uid,
MapGridComponent grid,
Box2 aabb)
{
_mapSystem = mapSystem;
_uid = uid;
_grid = grid;
_ignoreEmpty = ignoreEmpty;
_predicate = predicate;
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(aabb.Left), (int)Math.Floor(aabb.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(aabb.Right), (int)Math.Ceiling(aabb.Top));
_x = gridTileLb.X;
_y = gridTileLb.Y;
_lowerY = gridTileLb.Y;
_upperX = gridTileRt.X;
_upperY = gridTileRt.Y;
}
public bool MoveNext(out TileRef tile)
{
if (_x >= _upperX)
{
tile = TileRef.Zero;
return false;
}
var gridTile = new Vector2i(_x, _y);
_y++;
if (_y >= _upperY)
{
_x++;
_y = _lowerY;
}
var gridChunk = _mapSystem.GridTileToChunkIndices(_uid, _grid, gridTile);
if (_grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(gridTile);
tile = _mapSystem.GetTileRef(_uid, _grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (_ignoreEmpty && tile.Tile.IsEmpty)
return MoveNext(out tile);
if (_predicate == null || _predicate(tile))
{
return true;
}
}
else if (!_ignoreEmpty)
{
tile = new TileRef(_uid, gridTile.X, gridTile.Y, Tile.Empty);
if (_predicate == null || _predicate(tile))
{
return true;
}
}
return MoveNext(out tile);
}
}
}

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