Compare commits

..

64 Commits

Author SHA1 Message Date
metalgearsloth
b6cadfedd5 Update arch (#4605) 2023-11-25 14:56:56 +11:00
metalgearsloth
9f57b705d7 Version: 182.0.0 2023-11-24 00:21:00 +11:00
metalgearsloth
68be9712ad Add entity gen to hashcode (#4601) 2023-11-24 00:19:58 +11:00
metalgearsloth
aaa446254c Version: 181.0.2 2023-11-23 23:43:47 +11:00
metalgearsloth
5e2d2ab317 Fix too many pointlights causing blackscreen (#4599) 2023-11-23 23:39:51 +11:00
metalgearsloth
20ae63fbbd Replace tile intersecting with enumerator (#4595) 2023-11-23 22:36:40 +11:00
metalgearsloth
a92c0cbef4 Fix nullable comps being raised for client gamestates (#4596) 2023-11-23 22:07:27 +11:00
metalgearsloth
95649a2dd0 Version: 181.0.1 2023-11-23 16:53:03 +11:00
metalgearsloth
861807f8b4 Fix HasComp(uid, Type) (#4594) 2023-11-23 16:52:31 +11:00
metalgearsloth
bd73f1c05a Version: 181.0.0 2023-11-23 15:28:35 +11:00
metalgearsloth
7dce51e2cf Arch PR two electric boogaloo (#4388)
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-11-23 14:29:37 +11:00
DrSmugleaf
d9b0f3a227 Version: 180.2.1 2023-11-22 17:02:13 -08:00
Vasilis
05766a2eaa Fix not using dotnet 7 for actions in engine (#4591) 2023-11-23 00:43:15 +01:00
metalgearsloth
a761fbc09e Version: 180.2.0 2023-11-22 22:00:05 +11:00
metalgearsloth
f69440b3f2 Minor PVS stuff (#4573)
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-11-22 21:54:07 +11:00
Leon Friedrich
b459d2ce21 Add more map system helper methods. (#4589) 2023-11-22 21:51:46 +11:00
Leon Friedrich
202182e3d4 Add new EnsureEntity variants (#4586) 2023-11-20 17:44:36 +11:00
metalgearsloth
96cb52e5d2 Version: 180.1.0 2023-11-20 16:31:41 +11:00
metalgearsloth
82e0c0baeb Add cvar for lidgren pool size (#4585) 2023-11-20 16:28:13 +11:00
Leon Friedrich
54d6552164 Fix shape lookups for non-hard fixtures (#4583) 2023-11-20 16:15:27 +11:00
Jordan Dominion
c21b6c993c Fix potential error when writing runtime log (#4575) 2023-11-19 15:47:53 +01:00
metalgearsloth
2fe4a8b859 Add map name to lsmap (#4576) 2023-11-19 15:47:19 +01:00
metalgearsloth
8325966dbb Fix contact constraints allocs (#4581) 2023-11-19 15:47:07 +01:00
metalgearsloth
2459a9d688 Version: 180.0.0 2023-11-19 15:09:59 +11:00
Leon Friedrich
2cd2d1edd6 Add misc helpful methods (#4577) 2023-11-19 15:05:26 +11:00
metalgearsloth
b982350851 Use NetEntities for F3 panel (#4571) 2023-11-16 20:53:23 +11:00
DrSmugleaf
4a50bc2154 Add AddEntitiesIntersecting for phys shapes, change float range overload to use circles, remove obsolete methods (#4572) 2023-11-16 20:44:21 +11:00
metalgearsloth
4c85e205b9 Add chain support to TryGetNearest (#4567) 2023-11-16 20:40:23 +11:00
ElectroJr
0b447d9d82 Version: 179.0.0 2023-11-12 13:35:17 -05:00
Leon Friedrich
ceb205ad52 Allow per-eye lighting toggling. (#4569) 2023-11-13 05:30:00 +11:00
Leon Friedrich
a48ff3dbf1 Fix PlacementManager bug (#4568) 2023-11-13 05:29:18 +11:00
DrSmugleaf
2b85fa88c1 Print stack trace when adding a component while iterating net comps in ResetPredictedEntities (#4541) 2023-11-13 05:26:13 +11:00
Leon Friedrich
19564a421b Fix deserialization of empty grid chunks (#4565) 2023-11-13 05:22:20 +11:00
Leon Friedrich
b3f0e467ee Improve UnknownPrototypeException error message (#4566) 2023-11-13 05:22:05 +11:00
Leon Friedrich
216292c849 Make EyeComponent.Eye not nullable (#4564) 2023-11-13 04:20:09 +11:00
Jerry
68753d15e0 Fix stack overflow error on planet station (#4563) 2023-11-12 13:28:35 +11:00
ElectroJr
2a357051ae Version: 178.0.0 2023-11-10 20:58:35 -05:00
Leon Friedrich
58e0b62145 Merge ActorSystem and IPlayerManager (#4530) 2023-11-11 12:50:21 +11:00
Leon Friedrich
14cc273997 Add NetListAsArray<T>.Value to sandbox whitelist (#4537) 2023-11-11 11:57:58 +11:00
DrSmugleaf
93f4428635 Version: 177.0.0 2023-11-08 00:21:12 -08:00
DrSmugleaf
164bf68aca Move TryGetUi/TryToggleUi/ToggleUi/TryOpen/OpenUi/TryClose/CloseUi to SharedUserInterfaceSystem (#4562) 2023-11-08 16:52:38 +11:00
Leon Friedrich
773b87672b Fix terminating entity reparenting bug (#4549) 2023-11-08 15:39:08 +11:00
metalgearsloth
eecf834039 Fix PlacementManager warnings (#4557) 2023-11-08 15:34:54 +11:00
Leon Friedrich
325fe46aa3 Add More Entity<T> query methods (#4550) 2023-11-07 20:24:42 -08:00
metalgearsloth
2f6c29ab43 Add GetMapCoordinates to TransformSystem (#4556) 2023-11-07 20:23:05 -08:00
metalgearsloth
aab1a2dba9 Fix transform test warnings (#4558) 2023-11-07 20:22:07 -08:00
DrSmugleaf
f36fbd9c83 Fix inverted GetAllMapGrids mapid check (#4561) 2023-11-07 14:26:01 -08:00
metalgearsloth
126c863f45 Hotfix containersystem.remove (#4560) 2023-11-07 16:18:08 +11:00
Leon Friedrich
618a8491bf Add BeforeApplyState event to replay playback (#4536) 2023-11-07 15:07:26 +11:00
Leon Friedrich
2743b64a2b Mark container methods as obsolete (#4551) 2023-11-07 15:05:32 +11:00
Leon Friedrich
28cc91934c Change PVS error log into warning (#4548) 2023-11-07 15:02:13 +11:00
metalgearsloth
eadfcd4c09 Specify RichTextLabel VAlignment as Center (#4520) 2023-11-07 10:27:49 +11:00
metalgearsloth
7871b0010e Version: 176.0.0 2023-11-07 09:51:32 +11:00
metalgearsloth
3da04ed17e Robust.Packaging updates (#4547) 2023-11-07 09:36:33 +11:00
metalgearsloth
170d192791 Revert audio rework (#4554) 2023-11-07 09:34:09 +11:00
Leon Friedrich
dcd9939554 Fix PVS initial list capacity bug (#4546) 2023-11-06 04:41:56 +11:00
Leon Friedrich
98ef58eca6 Add max game state buffer size cvar (#4543) 2023-11-05 02:58:48 +11:00
metalgearsloth
ab1e99a0df Add GetEntitiesInRange that takes in a set (#4544) 2023-11-04 15:02:20 +11:00
Leon Friedrich
499c236798 Fix replay lerp error spam (#4534) 2023-10-30 04:29:47 +11:00
metalgearsloth
8dc2345ceb Fix audio position on first tick (#4533) 2023-10-29 15:30:59 +11:00
metalgearsloth
9b04270178 Version: 175.0.0 2023-10-29 15:03:09 +11:00
metalgearsloth
d75dbc901f Audio rework (#4421) 2023-10-29 14:58:19 +11:00
Leon Friedrich
19a3e82848 Cache prototype data for IEntityManager.IsDefault() (#4531) 2023-10-29 12:54:52 +11:00
Leon Friedrich
911abf2693 Remove empty planet-map chunks (#4529) 2023-10-29 12:52:03 +11:00
155 changed files with 4157 additions and 2328 deletions

3
.gitmodules vendored
View File

@@ -13,3 +13,6 @@
[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

1
Arch/Arch Submodule

Submodule Arch/Arch added at c76d18feb7

94
Arch/Arch.csproj Normal file
View File

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

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,7 +54,192 @@ END TEMPLATE-->
*None yet*
## 174.0.1
## 182.0.0
### Breaking changes
* Add EntityUid's generation / version to the hashcode.
## 181.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.0

View File

@@ -2203,3 +2203,207 @@
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

@@ -0,0 +1,177 @@
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);
var uid = new EntityUid(i, -1);
_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));
public static readonly CompIdx Idx = new(Interlocked.Increment(ref _compIndexMaster), typeof(T));
}
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));
Comps[i] = _entityManager.GetComponent<A>(new EntityUid(i, -1));
}
// Return something so the JIT doesn't optimize out all the GetComponent calls.

View File

@@ -8,6 +8,7 @@
<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]);
var uid = EntityUid.Parse(args[0], "-1");
var entmgr = _entityManager;
if (!entmgr.EntityExists(uid))
{

View File

@@ -43,6 +43,7 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
GC.Collect();
Span<EntityUid> ents = stackalloc EntityUid[amount];
var stopwatch = new Stopwatch();
stopwatch.Start();
@@ -50,12 +51,17 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
for (var i = 0; i < amount; i++)
{
_entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
ents[i] = _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);
return base.CreateEntity(prototypeName, out metadata, out _);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)

View File

@@ -1,9 +1,8 @@
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;
@@ -26,17 +25,13 @@ public sealed class EyeSystem : SharedEyeSystem
private void OnEyeAutoState(EntityUid uid, EyeComponent component, ref AfterAutoHandleStateEvent args)
{
UpdateEye(component);
UpdateEye((uid, component));
}
private void OnEyeAttached(EntityUid uid, EyeComponent component, LocalPlayerAttachedEvent args)
{
// TODO: This probably shouldn't be nullable bruv.
if (component._eye != null)
{
_eyeManager.CurrentEye = component._eye;
}
UpdateEye((uid, component));
_eyeManager.CurrentEye = component.Eye;
var ev = new EyeAttachedEvent(uid, component);
RaiseLocalEvent(uid, ref ev, true);
}
@@ -48,13 +43,7 @@ public sealed class EyeSystem : SharedEyeSystem
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
{
component._eye = new Eye
{
Position = Transform(uid).MapPosition,
Zoom = component.Zoom,
DrawFov = component.DrawFov,
Rotation = component.Rotation,
};
UpdateEye((uid, component));
}
/// <inheritdoc />
@@ -64,7 +53,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))
@@ -73,7 +62,7 @@ public sealed class EyeSystem : SharedEyeSystem
eyeComponent.Target = null;
}
eyeComponent._eye.Position = xform.MapPosition;
eyeComponent.Eye.Position = xform.MapPosition;
}
}
}

View File

@@ -1,12 +1,9 @@
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
{
@@ -28,10 +25,5 @@ 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,9 +54,10 @@ 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 final position.
// the entity in the state's 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 it's final of that lerp.
// was predicted. I.e., we assume the entity was already rendered in the final position that was
// previously predicted.
// - 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))
if (!_timing.InPrediction || IsClientSide(ev.Entity, ev.Metadata))
return;
// Client-side entity deletion is not supported and will cause errors.

View File

@@ -5,6 +5,8 @@ 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;
@@ -29,7 +31,9 @@ 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
{
@@ -52,11 +56,12 @@ 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);
@@ -124,6 +129,8 @@ namespace Robust.Client.GameStates
public bool DropStates;
#endif
private bool _resettingPredictedEntities;
/// <inheritdoc />
public void Initialize()
{
@@ -146,6 +153,7 @@ 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);
@@ -160,6 +168,8 @@ 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.");
@@ -167,6 +177,23 @@ 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()
{
@@ -518,7 +545,9 @@ namespace Robust.Client.GameStates
var countReset = 0;
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
RemQueue<IComponent> toRemove = new();
using var toRemove = new PooledList<IComponent>();
using var toAdd = new PooledList<ushort>();
using var toAddStates = new PooledList<ComponentState>();
foreach (var entity in system.DirtyEntities)
{
@@ -536,48 +565,63 @@ namespace Robust.Client.GameStates
countReset += 1;
foreach (var (netId, comp) in meta.NetComponents)
try
{
if (!comp.NetSyncEnabled)
continue;
_resettingPredictedEntities = true;
// Was this component added during prediction?
if (comp.CreationTick > _timing.LastRealTick)
foreach (var (netId, comp) in meta.NetComponents)
{
if (last.ContainsKey(netId))
if (!comp.NetSyncEnabled)
continue;
// Was this component added during prediction?
if (comp.CreationTick > _timing.LastRealTick)
{
// Component was probably removed and then re-addedd during a single prediction run
// Just reset state as normal.
comp.ClearCreationTick();
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;
}
}
else
if (comp.LastModifiedTick <= _timing.LastRealTick ||
!last.TryGetValue(netId, out var compState))
{
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;
}
if (comp.LastModifiedTick <= _timing.LastRealTick || !last.TryGetValue(netId, out var compState))
{
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;
}
// Remove predicted component additions
foreach (var comp in toRemove)
finally
{
_entities.RemoveComponent(entity, comp);
_resettingPredictedEntities = false;
}
if (toRemove.Count > 0)
{
// Remove predicted component additions
// TODO: 1 archetype change.
foreach (var comp in toRemove)
{
_entities.RemoveComponent(entity, comp, meta);
}
toRemove.Clear();
}
toRemove.Clear();
// Re-add predicted removals
if (system.RemovedComponents.TryGetValue(entity, out var netIds))
@@ -590,15 +634,29 @@ namespace Robust.Client.GameStates
if (!last.TryGetValue(netId, out var state))
continue;
var comp = _entityManager.AddComponent(entity, netId, meta);
toAdd.Add(netId);
toAddStates.Add(state);
}
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was removed: {comp.GetType()}");
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);
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
comp.ClearCreationTick(); // don't undo the re-adding.
comp.LastModifiedTick = _timing.LastRealTick;
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();
}
}
@@ -681,10 +739,9 @@ namespace Robust.Client.GameStates
_config.TickProcessMessages();
}
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
using (_prof.Group("Entity"))
{
output = ApplyEntityStates(curState, nextState);
ApplyEntityStates(curState, nextState);
}
using (_prof.Group("Player"))
@@ -694,13 +751,13 @@ namespace Robust.Client.GameStates
using (_prof.Group("Callback"))
{
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, output.Detached));
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, _detached));
}
return output.Created;
return _created;
}
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
private void ApplyEntityStates(GameState curState, GameState? nextState)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -709,6 +766,8 @@ namespace Robust.Client.GameStates
var enteringPvs = 0;
_toApply.Clear();
_toCreate.Clear();
_detached.Clear();
_created.Clear();
_pendingReapplyNetStates.Clear();
var curSpan = curState.EntityStates.Span;
@@ -733,6 +792,7 @@ 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.
@@ -786,7 +846,7 @@ namespace Robust.Client.GameStates
// Detach entities to null space
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
var detached = ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
ProcessPvsDeparture(_detached, curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
// Check next state (AFTER having created new entities introduced in curstate)
if (nextState != null)
@@ -888,8 +948,6 @@ namespace Robust.Client.GameStates
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
return (_toCreate.Keys, detached);
}
/// <inheritdoc />
@@ -920,7 +978,7 @@ namespace Robust.Client.GameStates
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_toDelete.Clear();
using var toDelete = new PooledList<EntityUid>();
// Client side entities won't need the transform, but that should always be a tiny minority of entities
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
@@ -931,7 +989,7 @@ namespace Robust.Client.GameStates
if (metadata.NetEntity.IsClientSide())
{
if (deleteClientEntities)
_toDelete.Add(ent);
toDelete.Add(ent);
continue;
}
@@ -958,14 +1016,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);
}
@@ -1025,7 +1083,8 @@ namespace Robust.Client.GameStates
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
}
private List<NetEntity> ProcessPvsDeparture(
private void ProcessPvsDeparture(
IList<NetEntity> detached,
GameTick toTick,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
@@ -1033,18 +1092,17 @@ namespace Robust.Client.GameStates
ContainerSystem containerSys,
EntityLookupSystem lookupSys)
{
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
var detached = new List<NetEntity>();
using var toDetach = new PooledList<(GameTick Tick, List<NetEntity> Entities)>();
_processor.GetEntitiesToDetach(toDetach, toTick, _pvsDetachBudget);
if (toDetach.Count == 0)
return detached;
return;
// 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)
{
@@ -1052,7 +1110,6 @@ namespace Robust.Client.GameStates
}
_prof.WriteValue("Count", ProfData.Int32(detached.Count));
return detached;
}
private void Detach(GameTick maxTick,
@@ -1063,7 +1120,7 @@ namespace Robust.Client.GameStates
SharedTransformSystem xformSys,
ContainerSystem containerSys,
EntityLookupSystem lookupSys,
List<NetEntity>? detached = null)
IList<NetEntity>? detached = null)
{
foreach (var netEntity in entities)
{
@@ -1117,7 +1174,7 @@ namespace Robust.Client.GameStates
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
#if EXCEPTION_TOLERANCE
var brokenEnts = new List<EntityUid>();
using var brokenEnts = new PooledList<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
@@ -1181,18 +1238,28 @@ namespace Robust.Client.GameStates
// First remove any deleted components
if (curState?.NetComponents != null)
{
_toRemove.Clear();
using var toRemove = new PooledList<IComponent>();
using var compTypes = new PooledList<ComponentType>();
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
_toRemove.Add(comp);
{
toRemove.Add(comp);
compTypes.Add(comp.GetType());
}
}
foreach (var comp in _toRemove)
if (toRemove.Count > 0)
{
_entities.RemoveComponent(uid, comp, meta);
foreach (var comp in toRemove)
{
_entityManager.RemoveComponentInternal(uid, comp, terminating: false, archetypeChange: false, meta);
}
}
if (compTypes.Count > 0)
_entityManager.RemoveComponentRange(uid, compTypes);
}
if (enteringPvs)
@@ -1207,7 +1274,7 @@ namespace Robust.Client.GameStates
if (!meta.NetComponents.TryGetValue(id, out var comp))
{
comp = _compFactory.GetComponent(id);
_entityManager.AddComponent(uid, comp, true, metadata: meta);
_entityManager.AddComponent(uid, comp, metadata: meta);
}
_compStateWork[id] = (comp, state, null);
@@ -1215,18 +1282,49 @@ 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))
{
comp = _compFactory.GetComponent(compChange.NetID);
_entityManager.AddComponent(uid, comp, true, metadata:meta);
var registration = _compFactory.GetRegistration(compChange.NetID);
addedRegistrations.Add(registration);
comp = _compFactory.GetComponent(registration);
comp.Owner = uid;
addedComps.Add(comp);
addedCompTypes.Add(comp.GetType());
}
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)
@@ -1316,7 +1414,7 @@ namespace Robust.Client.GameStates
return false;
}
if (!EntityUid.TryParse(args[0], out uid))
if (!EntityUid.TryParse(args[0], "-1", out uid))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-uid", ("arg", args[0])));
meta = null;
@@ -1448,23 +1546,22 @@ namespace Robust.Client.GameStates
if (!meta.NetComponents.TryGetValue(id, out var comp))
{
comp = _compFactory.GetComponent(id);
_entityManager.AddComponent(uid, comp, true, meta);
_entityManager.AddComponent(uid, comp, meta);
}
var handleState = new ComponentHandleState(state, null);
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
}
// ensure we don't have any extra components
_toRemove.Clear();
using var toRemove = new PooledList<IComponent>();
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);
}
@@ -1477,10 +1574,10 @@ namespace Robust.Client.GameStates
public sealed class GameStateAppliedArgs : EventArgs
{
public GameState AppliedState { get; }
public readonly List<NetEntity> Detached;
public readonly GameState AppliedState;
public readonly IReadOnlyList<NetEntity> Detached;
public GameStateAppliedArgs(GameState appliedState, List<NetEntity> detached)
public GameStateAppliedArgs(GameState appliedState, IReadOnlyList<NetEntity> detached)
{
AppliedState = appliedState;
Detached = detached;

View File

@@ -14,8 +14,6 @@ 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;
@@ -28,6 +26,8 @@ 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,7 +48,14 @@ namespace Robust.Client.GameStates
public int BufferSize
{
get => _bufferSize;
set => _bufferSize = value < 0 ? 0 : value;
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);
}
/// <inheritdoc />
@@ -100,21 +107,21 @@ namespace Robust.Client.GameStates
return true;
}
if (LastFullState == null && state.FromSequence == GameTick.Zero && state.ToSequence >= LastFullStateRequested!.Value.Tick)
if (LastFullState == null && state.FromSequence == GameTick.Zero)
{
LastFullState = state;
if (Logging)
if (state.ToSequence >= LastFullStateRequested!.Value.Tick)
{
LastFullState = state;
_logger.Info($"Received Full GameState: to={state.ToSequence}, sz={state.PayloadSize}");
return true;
}
return true;
_logger.Info($"Received a late full game state. Received: {state.ToSequence}. Requested: {LastFullStateRequested.Value.Tick}");
}
if (LastFullState != null && state.ToSequence <= 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}");
_logger.Info($"While waiting for full, received late GameState with lower to={state.ToSequence} than the last full state={LastFullState.ToSequence}");
return false;
}
@@ -297,9 +304,8 @@ Had full state: {LastFullState != null}"
public void ClearDetachQueue() => _pvsDetachMessages.Clear();
public List<(GameTick Tick, List<NetEntity> Entities)> GetEntitiesToDetach(GameTick toTick, int budget)
public void GetEntitiesToDetach(IList<(GameTick Tick, List<NetEntity> Entities)> result, GameTick toTick, int budget)
{
var result = new List<(GameTick Tick, List<NetEntity> Entities)>();
foreach (var (tick, entities) in _pvsDetachMessages)
{
if (tick > toTick)
@@ -318,7 +324,6 @@ 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.DrawFov)
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
{
ApplyFovToBuffer(viewport, eye);
}

View File

@@ -97,6 +97,9 @@ 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()
{
@@ -332,7 +335,7 @@ namespace Robust.Client.Graphics.Clyde
private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
if (!_lightManager.Enabled)
if (!_lightManager.Enabled || !eye.DrawLight)
{
return;
}
@@ -570,6 +573,28 @@ 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,
@@ -595,20 +620,10 @@ 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,
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;
}));
Array.Sort(_lightsToRenderList, 0, state.count, _lightCap);
// Next, sort just the shadow casting lights by distance.
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);
}));
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount, _shadowCap);
// 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,15 +42,20 @@ public sealed class TileEdgeOverlay : Overlay
_grids.Clear();
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var mapSystem = _entManager.System<SharedMapSystem>();
var xformSystem = _entManager.System<SharedTransformSystem>();
foreach (var grid in _grids)
{
var tileSize = grid.Comp.TileSize;
var tileDimensions = new Vector2(tileSize, tileSize);
var xform = xformQuery.GetComponent(grid);
args.WorldHandle.SetTransform(xform.WorldMatrix);
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(grid.Owner);
args.WorldHandle.SetTransform(worldMatrix);
var localAABB = invMatrix.TransformBox(args.WorldBounds);
foreach (var tileRef in grid.Comp.GetTilesIntersecting(args.WorldBounds, false))
var enumerator = mapSystem.GetLocalTilesEnumerator(grid.Owner, grid.Comp, localAABB, false);
while (enumerator.MoveNext(out var tileRef))
{
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
@@ -66,7 +71,7 @@ public sealed class TileEdgeOverlay : Overlay
continue;
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
var neighborTile = grid.Comp.GetTileRef(neighborIndices);
var neighborTile = mapSystem.GetTileRef(grid.Owner, grid.Comp, neighborIndices);
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
// If it's the same tile then no edge to be drawn.
@@ -118,9 +123,9 @@ public sealed class TileEdgeOverlay : Overlay
}
if (angle == Angle.Zero)
args.WorldHandle.DrawTextureRect(texture, box);
args.WorldHandle.DrawTextureRect(texture.Texture, box);
else
args.WorldHandle.DrawTextureRect(texture, new Box2Rotated(box, angle, box.Center));
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
}
}
}

View File

@@ -1,6 +1,5 @@
using System.Buffers;
using System.Collections.Generic;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
@@ -8,6 +7,7 @@ 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<SpriteComponent?>(closestEntity, out var component) || component.BaseRSI == null)
if (!pManager.EntityManager.TryGetComponent(closestEntity, out SpriteComponent? component) || component.BaseRSI == null)
{
return;
}

View File

@@ -15,15 +15,22 @@ public interface IPlayerManager : ISharedPlayerManager
event Action? PlayerListUpdated;
/// <summary>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets attached to a new entity. See also <see cref="LocalPlayerAttachedEvent"/>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets attached to a new entity, or when the local
/// session gets updated. See also <see cref="LocalPlayerAttachedEvent"/>
/// </summary>
event Action<EntityUid>? LocalPlayerAttached;
/// <summary>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets detached from new entity. See also <see cref="LocalPlayerDetachedEvent"/>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets detached from an entity, or when the local
/// session gets updated. 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>
@@ -38,34 +45,8 @@ 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,12 +42,13 @@ namespace Robust.Client.Player
/// <inheritdoc />
public override int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? -1;
public LocalPlayer? LocalPlayer { get; set; }
public LocalPlayer? LocalPlayer { get; private 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)
@@ -64,22 +65,14 @@ 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?");
LocalSession = CreateAndAddSession(default, name);
var session = CreateAndAddSession(default, name);
session.ClientSide = true;
SetLocalSession(session);
Startup();
PlayerListUpdated?.Invoke();
}
@@ -89,18 +82,44 @@ namespace Robust.Client.Player
if (LocalSession != null)
throw new InvalidOperationException($"Player manager already running?");
var session = CreateAndAddSession(channel.UserId, channel.UserName);
session.Channel = channel;
LocalSession = session;
SetLocalSession(CreateAndAddSession(channel));
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()
{
if (LocalSession != null)
SetAttachedEntity(LocalSession, null);
SetAttachedEntity(LocalSession, null, out _);
LocalPlayer = null;
LocalSession = null;
_pendingStates.Clear();
@@ -108,16 +127,21 @@ namespace Robust.Client.Player
PlayerListUpdated?.Invoke();
}
public override void SetAttachedEntity(ICommonSession session, EntityUid? uid)
public override bool SetAttachedEntity(ICommonSession? session, EntityUid? uid, out ICommonSession? kicked, bool force = false)
{
kicked = null;
if (session == null)
return false;
if (session.AttachedEntity == uid)
return;
return true;
var old = session.AttachedEntity;
base.SetAttachedEntity(session, uid);
if (!base.SetAttachedEntity(session, uid, out kicked, force))
return false;
if (session != LocalSession)
return;
return true;
if (old.HasValue)
{
@@ -129,13 +153,13 @@ namespace Robust.Client.Player
if (uid == null)
{
Sawmill.Info($"Local player is no longer attached to any entity.");
return;
return true;
}
if (!EntManager.EntityExists(uid))
{
Sawmill.Error($"Attempted to attach player to non-existent entity {uid}!");
return;
return true;
}
if (!EntManager.EnsureComponent(uid.Value, out EyeComponent eye))
@@ -148,6 +172,7 @@ 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)
@@ -193,7 +218,7 @@ namespace Robust.Client.Player
_pendingStates.Remove(state.UserId);
}
SetAttachedEntity(LocalSession, uid);
SetAttachedEntity(LocalSession, uid, out _, true);
SetStatus(LocalSession, state.Status);
}
@@ -233,11 +258,10 @@ namespace Robust.Client.Player
{
// This is a new userid, so we create a new session.
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
var newSession = CreateAndAddSession(state.UserId, state.Name);
var newSession = (CommonSession) CreateAndAddSession(state.UserId, state.Name);
newSession.Ping = state.Ping;
newSession.Name = state.Name;
SetStatus(newSession, state.Status);
SetAttachedEntity(newSession, controlled);
SetAttachedEntity(newSession, controlled, out _, true);
dirty = true;
continue;
}
@@ -256,7 +280,7 @@ namespace Robust.Client.Player
local.Name = state.Name;
local.Ping = state.Ping;
SetStatus(local, state.Status);
SetAttachedEntity(local, controlled);
SetAttachedEntity(local, controlled, out _, true);
}
// Remove old users. This only works if the provided state is a list of all players
@@ -264,10 +288,12 @@ 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,6 +2,7 @@ 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;
@@ -128,6 +129,11 @@ 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.
@@ -137,5 +143,5 @@ public interface IReplayPlaybackManager
/// <summary>
/// Fetches the entity that the <see cref="Recorder"/> is currently attached to.
/// </summary>
public bool TryGetRecorderEntity([NotNullWhen(true)] out EntityUid? uid);
bool TryGetRecorderEntity([NotNullWhen(true)] out EntityUid? uid);
}

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ 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;
@@ -44,6 +45,7 @@ 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.7" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<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.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" />
<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" />
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
</ItemGroup>

View File

@@ -20,6 +20,7 @@ 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,6 +70,7 @@ 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))
{
@@ -80,7 +81,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), Tile.Empty);
tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
}
var controlHovered = UserInterfaceManager.CurrentlyHovered;
@@ -90,35 +91,35 @@ Screen Size: {screenSize} (scale: {screenScale})
Mouse Pos:
Screen: {mouseScreenPos}
{mouseWorldMap}
{mouseGridPos}
{_entityManager.GetNetCoordinates(mouseGridPos)}
{tile}
GUI: {controlHovered}");
_textBuilder.AppendLine("\nAttached Entity:");
var controlledEntity = _playerManager?.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
_textBuilder.AppendLine("\nAttached NetEntity:");
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
if (controlledEntity == EntityUid.Invalid)
{
_textBuilder.AppendLine("No attached entity.");
_textBuilder.AppendLine("No attached netentity.");
}
else
{
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
var playerWorldOffset = entityTransform.MapPosition;
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
var playerCoordinates = entityTransform.Coordinates;
var playerRotation = entityTransform.WorldRotation;
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
var gridRotation = entityTransform.GridUid != null
? _entityManager.GetComponent<TransformComponent>(entityTransform.GridUid.Value)
.WorldRotation
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
: Angle.Zero;
_textBuilder.Append($@" Screen: {playerScreen}
{playerWorldOffset}
{playerCoordinates}
{_entityManager.GetNetCoordinates(playerCoordinates)}
Rotation: {playerRotation.Degrees:F2}°
EntId: {controlledEntity}
GridUid: {entityTransform.GridUid}
NEntId: {_entityManager.GetNetEntity(controlledEntity)}
Grid NEntId: {_entityManager.GetNetEntity(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);
var gridVal = EntityUid.Parse(gridId.Text, "-1");
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));
ValueChanged(EntityUid.Parse(e.Text, ", -1"));
}
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)
return;
continue;
RunJob(() =>
{

View File

@@ -21,11 +21,11 @@ public sealed class RobustClientAssetGraph
/// </summary>
public IReadOnlyCollection<AssetPass> AllPasses { get; }
public RobustClientAssetGraph()
public RobustClientAssetGraph(bool parallel = true)
{
// 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 = true };
Input = new AssetPassPipe { Name = "RobustClientAssetGraphInput", Parallelize = parallel };
PresetPasses = new AssetPassPipe { Name = "RobustClientAssetGraphPresetPasses" };
Output = new AssetPassPipe { Name = "RobustClientAssetGraphOutput", CheckDuplicates = true };
NormalizeText = new AssetPassNormalizeText { Name = "RobustClientAssetGraphNormalizeText" };

View File

@@ -4,9 +4,10 @@ namespace Robust.Packaging;
public sealed class RobustClientPackaging
{
public static IReadOnlySet<string> ClientIgnoresResources { get; } = new HashSet<string>
public static IReadOnlySet<string> ClientIgnoredResources { get; } = new HashSet<string>
{
"Maps",
"ConfigPresets",
// Leaving this here for future archaeologists to ponder at.
"emotes.xml",
"Groups",
@@ -18,48 +19,8 @@ public sealed class RobustClientPackaging
AssetPass pass,
CancellationToken cancel = default)
{
var ignoreSet = ClientIgnoresResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
var ignoreSet = ClientIgnoredResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
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;
await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"), pass, ignoreSet, cancel: cancel);
}
}

View File

@@ -0,0 +1,33 @@
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,10 +15,53 @@ 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))
@@ -29,7 +72,7 @@ public sealed class RobustSharedPackaging
if (ignoreSet.Contains(filename))
continue;
var targetPath = filename;
var targetPath = Path.Combine(targetDir, filename);
if (Directory.Exists(path))
CopyDirIntoZip(path, targetPath, pass);
else
@@ -44,11 +87,11 @@ public sealed class RobustSharedPackaging
foreach (var file in Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories))
{
var relPath = Path.GetRelativePath(directory, file);
if (Path.DirectorySeparatorChar != '/')
relPath = relPath.Replace(Path.DirectorySeparatorChar, '/');
var zipPath = $"{basePath}/{relPath}";
if (Path.DirectorySeparatorChar != '/')
zipPath = zipPath.Replace(Path.DirectorySeparatorChar, '/');
// Console.WriteLine($"{directory}/{zipPath} -> /{zipPath}");
pass.InjectFileFromDisk(zipPath, file);
}

View File

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

View File

@@ -43,6 +43,7 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
GC.Collect();
Span<EntityUid> ents = stackalloc EntityUid[amount];
var stopwatch = new Stopwatch();
stopwatch.Start();
@@ -50,12 +51,17 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
for (var i = 0; i < amount; i++)
{
_entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
ents[i] = _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

@@ -1,189 +0,0 @@
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,5 +1,4 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
namespace Robust.Server.GameObjects;

View File

@@ -5,6 +5,9 @@ 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;
@@ -154,10 +157,11 @@ public sealed class MapLoaderSystem : EntitySystem
var sw = new Stopwatch();
sw.Start();
result = Deserialize(data);
_logLoader.Debug($"Loaded map in {sw.Elapsed}");
var mapEnt = _mapManager.GetMapEntityId(mapId);
_logLoader.Info($"Loaded map {resPath} in {sw.Elapsed}");
var xformQuery = _serverEntityManager.GetEntityQuery<TransformComponent>();
var mapEnt = _mapManager.GetMapEntityId(mapId);
var rootEnts = new List<EntityUid>();
// aeoeoeieioe content
@@ -174,6 +178,8 @@ public sealed class MapLoaderSystem : EntitySystem
}
}
// EntityManager.CleanupArch();
rootUids = rootEnts;
}
@@ -290,6 +296,9 @@ 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);
@@ -305,6 +314,25 @@ 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();
@@ -406,6 +434,10 @@ 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>())
{
@@ -425,9 +457,30 @@ 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)
_prototypeManager.TryIndex(type, out proto);
{
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);
}
}
foreach (var entityDef in entities.Cast<MappingDataNode>())
{
@@ -442,6 +495,7 @@ public sealed class MapLoaderSystem : EntitySystem
{
deletedPrototypeUids.Add(entity);
}
// TODO: Move this elsewhere?
else if (data.Options.StoreMapUids)
{
var comp = _serverEntityManager.AddComponent<MapSaveIdComponent>(entity);
@@ -550,6 +604,7 @@ 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,11 +24,6 @@ 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;
@@ -66,9 +61,8 @@ namespace Robust.Server.GameObjects
private void HandleGridEmpty(EntityUid uid, MapGridComponent component, EmptyGridEvent args)
{
if (!_deleteEmptyGrids) return;
if (!EntityManager.EntityExists(uid)) return;
if (EntityManager.GetComponent<MetaDataComponent>(uid).EntityLifeStage >= EntityLifeStage.Terminating) return;
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
return;
MapManager.DeleteGrid(args.GridId);
}

View File

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

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.Player;
@@ -21,8 +20,6 @@ namespace Robust.Server.GameObjects
private readonly List<ICommonSession> _sessionCache = new();
private readonly Dictionary<ICommonSession, List<PlayerBoundUserInterface>> _openInterfaces = new();
/// <inheritdoc />
public override void Initialize()
{
@@ -46,7 +43,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())
@@ -92,7 +89,7 @@ namespace Robust.Server.GameObjects
/// <summary>
/// Verify that the subscribed clients are still in range of the interface.
/// </summary>
private void CheckRange(EntityUid uid, Shared.GameObjects.ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
{
if (ui.InteractionRange <= 0)
return;
@@ -139,11 +136,6 @@ 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)
@@ -168,20 +160,14 @@ 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
@@ -259,94 +245,14 @@ 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 class ServerEntityManager : EntityManager, IServerEntityManagerInternal
public sealed partial 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 _);
return AllocEntity(prototype, out _, 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, IEntityLoadContext? context = null)
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, out TransformComponent xform, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return base.CreateEntity(prototypeName, out metadata, context);
return base.CreateEntity(prototypeName, out metadata, out xform, 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, context);
var entity = base.CreateEntity(prototype, out metadata, out xform, 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(Entities.Count);
EntitiesCount.Set(EntityCount);
}
public uint GetLastMessageSequence(ICommonSession session)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
@@ -271,6 +272,7 @@ 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);
}
@@ -433,10 +435,15 @@ internal sealed partial class PvsSystem : EntitySystem
#endregion
public (List<(int, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(ICommonSession[] sessions)
public List<(int, IChunkIndexLocation)> GetChunks(
ICommonSession[] sessions,
ref HashSet<int>[] playerChunks,
ref EntityUid[][] viewerEntities)
{
var playerChunks = new HashSet<int>[sessions.Length];
var viewerEntities = new EntityUid[sessions.Length][];
// 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);
_chunkList.Clear();
// Keep track of the index of each chunk we use for a faster index lookup.
@@ -459,8 +466,8 @@ internal sealed partial class PvsSystem : EntitySystem
var session = sessions[i];
playerChunks[i] = _playerChunkPool.Get();
var viewers = GetSessionViewers(session);
viewerEntities[i] = viewers;
ref var viewers = ref viewerEntities[i];
GetSessionViewers(session, ref viewers);
for (var j = 0; j < viewers.Length; j++)
{
@@ -552,7 +559,7 @@ internal sealed partial class PvsSystem : EntitySystem
}
}
return (_chunkList, playerChunks, viewerEntities);
return _chunkList;
}
public void RegisterNewPreviousChunkTrees(
@@ -569,23 +576,20 @@ internal sealed partial class PvsSystem : EntitySystem
_reusedTrees.Add(chunks[i]);
}
var previousIndices = _previousTrees.Keys.ToArray();
for (var i = 0; i < previousIndices.Length; i++)
foreach (var (index, chunk) in _previousTrees)
{
var index = previousIndices[i];
// ReSharper disable once InconsistentlySynchronizedField
if (_reusedTrees.Contains(index)) continue;
var chunk = _previousTrees[index];
if (chunk.HasValue)
if (_reusedTrees.Contains(index))
continue;
if (chunk != null)
{
_chunkCachePool.Return(chunk.Value.metadata);
_treePool.Return(chunk.Value.tree);
}
if (!chunks.Contains(index))
{
_previousTrees.Remove(index);
}
}
_previousTrees.EnsureCapacity(chunks.Count);
@@ -888,7 +892,7 @@ internal sealed partial class PvsSystem : EntitySystem
return null;
var tick = _gameTiming.CurTick;
var minSize = Math.Max(0, lastSent.Count - lastSent.Count);
var minSize = Math.Max(0, lastSent.Count - visibleEnts.Count);
var leftView = new List<NetEntity>(minSize);
foreach (var netEntity in lastSent.Keys)
@@ -1313,26 +1317,44 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
return entState;
}
private EntityUid[] GetSessionViewers(ICommonSession session)
private void GetSessionViewers(ICommonSession session, [NotNull] ref EntityUid[]? viewers)
{
if (session.Status != SessionStatus.InGame)
return Array.Empty<EntityUid>();
{
viewers = Array.Empty<EntityUid>();
return;
}
// Fast path
if (session.ViewSubscriptions.Count == 0)
{
if (session.AttachedEntity == null)
return Array.Empty<EntityUid>();
{
viewers = Array.Empty<EntityUid>();
return;
}
return new[] { session.AttachedEntity.Value };
Array.Resize(ref viewers, 1);
viewers[0] = session.AttachedEntity.Value;
return;
}
var viewers = new HashSet<EntityUid>();
if (session.AttachedEntity != null)
viewers.Add(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);
}
viewers.UnionWith(session.ViewSubscriptions);
return viewers.ToArray();
foreach (var ent in session.ViewSubscriptions)
{
viewers[i++] = ent;
}
}
// Read Safe

View File

@@ -37,6 +37,9 @@ 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!;
@@ -267,7 +270,7 @@ Oldest acked clients: {string.Join(", ", players)}
private PvsData? GetPVSData(ICommonSession[] players)
{
var (chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
var chunks= _pvs.GetChunks(players, ref _playerChunks, ref _viewerEntities);
const int ChunkBatchSize = 2;
var chunksCount = chunks.Count;
var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize);
@@ -310,8 +313,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,
};
}
@@ -378,15 +381,10 @@ 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.Error($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
_logger.Warning($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
#endif
}

View File

@@ -12,6 +12,7 @@ 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;
@@ -93,6 +94,7 @@ 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,6 +31,10 @@ 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();
@@ -45,6 +49,7 @@ namespace Robust.Server.Placement
public void Initialize()
{
// Someday PlacementManagerSystem my beloved.
_sawmill = _logManager.GetSawmill("placement");
_networkManager.RegisterNetMessage<MsgPlacement>(HandleNetMessage);
@@ -143,7 +148,7 @@ namespace Robust.Server.Placement
if (_entityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
{
var replacementQuery = _entityManager.GetEntityQuery<PlacementReplacementComponent>();
var anc = grid.GetAnchoredEntitiesEnumerator(grid.LocalToTile(coordinates));
var anc = _maps.GetAnchoredEntitiesEnumerator(gridUid.Value, grid, _maps.LocalToTile(gridUid.Value, grid, coordinates));
var toDelete = new ValueList<EntityUid>();
while (anc.MoveNext(out var ent))
@@ -186,14 +191,11 @@ namespace Robust.Server.Placement
MapGridComponent? 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
EntityUid gridId = coordinates.EntityId;
if (_entityManager.TryGetComponent(coordinates.EntityId, out grid)
|| _mapManager.TryFindGridAt(coordinates.ToMap(_entityManager, _xformSystem), out gridId, out grid))
{
grid.SetTile(coordinates, new Tile(tileType));
_maps.SetTile(gridId, grid, coordinates, new Tile(tileType));
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
@@ -202,9 +204,9 @@ namespace Robust.Server.Placement
{
var newGrid = _mapManager.CreateGridEntity(coordinates.GetMapId(_entityManager));
var newGridXform = _entityManager.GetComponent<TransformComponent>(newGrid);
newGridXform.WorldPosition = coordinates.Position - newGrid.Comp.TileSizeHalfVector; // assume bottom left tile origin
_xformSystem.SetWorldPosition(newGridXform, coordinates.Position - newGrid.Comp.TileSizeHalfVector); // assume bottom left tile origin
var tilePos = newGrid.Comp.WorldToTile(coordinates.Position);
newGrid.Comp.SetTile(tilePos, new Tile(tileType));
_maps.SetTile(newGrid.Owner, newGrid.Comp, tilePos, new Tile(tileType));
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
@@ -228,11 +230,16 @@ namespace Robust.Server.Placement
{
EntityCoordinates start = _entityManager.GetCoordinates(msg.NetCoordinates);
Vector2 rectSize = msg.RectSize;
foreach (EntityUid entity in EntitySystem.Get<EntityLookupSystem>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
foreach (var entity in _lookup.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);
@@ -244,19 +251,19 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBegin(EntityUid mob, int range, string objectType, string alignOption)
{
if (!_entityManager.TryGetComponent<ActorComponent?>(mob, out var actor))
if (!_entityManager.TryGetComponent(mob, out ActorComponent? actor))
return;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
var playerConnection = actor.PlayerSession.Channel;
var message = new MsgPlacement();
message.PlaceType = PlacementManagerMessage.StartPlacement;
message.Range = range;
message.IsTile = false;
message.ObjType = objectType;
message.AlignOption = alignOption;
var message = new MsgPlacement
{
PlaceType = PlacementManagerMessage.StartPlacement,
Range = range,
IsTile = false,
ObjType = objectType,
AlignOption = alignOption
};
_networkManager.ServerSendMessage(message, playerConnection);
}
@@ -265,19 +272,19 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBeginTile(EntityUid mob, int range, string tileType, string alignOption)
{
if (!_entityManager.TryGetComponent<ActorComponent?>(mob, out var actor))
if (!_entityManager.TryGetComponent(mob, out ActorComponent? actor))
return;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
var playerConnection = actor.PlayerSession.Channel;
var message = new MsgPlacement();
message.PlaceType = PlacementManagerMessage.StartPlacement;
message.Range = range;
message.IsTile = true;
message.ObjType = tileType;
message.AlignOption = alignOption;
var message = new MsgPlacement
{
PlaceType = PlacementManagerMessage.StartPlacement,
Range = range,
IsTile = true,
ObjType = tileType,
AlignOption = alignOption
};
_networkManager.ServerSendMessage(message, playerConnection);
}
@@ -286,15 +293,15 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementCancel(EntityUid mob)
{
if (!_entityManager.TryGetComponent<ActorComponent?>(mob, out var actor))
if (!_entityManager.TryGetComponent(mob, out ActorComponent? actor))
return;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
var playerConnection = actor.PlayerSession.Channel;
var message = new MsgPlacement();
message.PlaceType = PlacementManagerMessage.CancelPlacement;
var message = new MsgPlacement
{
PlaceType = PlacementManagerMessage.CancelPlacement
};
_networkManager.ServerSendMessage(message, playerConnection);
}

View File

@@ -80,9 +80,7 @@ namespace Robust.Server.Player
/// <param name="args"></param>
private void NewSession(object? sender, NetChannelArgs args)
{
var session = CreateAndAddSession(args.Channel.UserId, args.Channel.UserName);
session.Channel = args.Channel;
CreateAndAddSession(args.Channel);
PlayerCountMetric.Set(PlayerCount);
// Synchronize base time.
var msgTimeBase = new MsgSyncTimeBase();
@@ -106,8 +104,7 @@ namespace Robust.Server.Player
DebugTools.Assert(session.Channel == args.Channel);
SetStatus(session, SessionStatus.Disconnected);
if (session.AttachedEntity != null)
EntManager.System<ActorSystem>().Detach(session.AttachedEntity.Value);
SetAttachedEntity(session, null, out _, true);
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="6.0.2" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.0" 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,6 +3,7 @@ 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;
@@ -294,9 +295,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);
var results = await (CompletionService
.GetService(document)?
.GetCompletionsAsync(document, message.Cursor) ?? Task.FromResult<CompletionList?>(null));
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 RobustClientPackaging.WriteContentAssemblies(
await RobustSharedPackaging.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($@"
component.{name} = EnsureEntitySet<{componentName}>(state.{name}, uid);");
EnsureEntitySet<{componentName}>(state.{name}, uid, component.{name});");
break;
case GlobalEntityUidListName:
@@ -177,7 +177,7 @@ namespace Robust.Shared.CompNetworkGenerator
getStateInit.Append($@"
{name} = GetNetEntityList(component.{name}),");
handleStateSetters.Append($@"
component.{name} = EnsureEntityList<{componentName}>(state.{name}, uid);");
EnsureEntityList<{componentName}>(state.{name}, uid, component.{name});");
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.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.1.0" />
<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));
return gpos(x, y, new EntityUid(gridId, -1));
}
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);
return new(i, -1);
}
public MapGridComponent getgrid(int i)
{
return map.GetGrid(new EntityUid(i));
return map.GetGrid(new EntityUid(i, -1));
}
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)
public bool HasComp<T>(EntityUid uid) where T : IComponent
=> ent.HasComponent<T>(uid);
public EntityUid Spawn(string? prototype, EntityCoordinates position)

View File

@@ -53,6 +53,16 @@ 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>
@@ -119,6 +129,13 @@ 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

@@ -0,0 +1,17 @@
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,6 +131,7 @@ 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";
@@ -144,10 +145,13 @@ internal sealed class ListMapsCommand : LocalizedCommands
foreach (var mapId in _map.GetAllMapIds().OrderBy(id => id.Value))
{
msg.AppendFormat("{0}: init: {1}, paused: {2}, ent: {3}, grids: {4}\n",
mapId, _map.IsMapInitialized(mapId),
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),
_map.IsMapPaused(mapId),
_map.GetMapEntityId(mapId),
_entManager.GetNetEntity(_map.GetMapEntityId(mapId)),
string.Join(",", _map.GetAllGrids(mapId).Select(grid => grid.Owner)));
}

View File

@@ -79,20 +79,7 @@ namespace Robust.Shared.Containers
Owner = owner;
}
/// <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>
[Obsolete("Use container system method")]
public bool Insert(
EntityUid toinsert,
IEntityManager? entMan = null,
@@ -138,16 +125,23 @@ namespace Robust.Shared.Containers
return false;
}
transform ??= transformQuery.GetComponent(toinsert);
//Verify we can insert into this container
if (!force && !containerSys.CanInsert(toinsert, this))
if (!force && !containerSys.CanInsert(toinsert, this, containerXform: ownerTransform))
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 &&
@@ -270,16 +264,7 @@ 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;
/// <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>
[Obsolete("Use container system method")]
public bool Remove(
EntityUid toRemove,
IEntityManager? entMan = null,
@@ -359,7 +344,7 @@ namespace Robust.Shared.Containers
return true;
}
[Obsolete("use force option in Remove()")]
[Obsolete("Use container system method")]
public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null)
=> Remove(toRemove, entMan, meta: meta, reparent: false, force: true);

View File

@@ -1,10 +1,41 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Utility;
using Robust.Shared.Physics.Components;
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>
@@ -13,8 +44,8 @@ public abstract partial class SharedContainerSystem
public bool CanInsert(
EntityUid toInsert,
BaseContainer container,
TransformComponent? toInsertXform = null,
bool assumeEmpty = false)
bool assumeEmpty = false,
TransformComponent? containerXform = null)
{
if (container.Owner == toInsert)
return false;
@@ -25,15 +56,12 @@ 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(toInsertXform, container.Owner))
if (_transform.ContainsEntity(toInsert, (container.Owner, containerXform)))
return false;
var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(container, toInsert, assumeEmpty);

View File

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

View File

@@ -73,6 +73,12 @@ 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

View File

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

@@ -0,0 +1,60 @@
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,6 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Arch.Core.Utils;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -13,6 +14,7 @@ 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;
@@ -62,12 +64,13 @@ 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));
public static readonly CompIdx Index = new(Interlocked.Increment(ref _CompIdxMaster), typeof(T));
}
internal CompIdx(int value)
internal CompIdx(int value, in ComponentType type)
{
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 CompIdx ComponentType;
public readonly ComponentRegistration ComponentType;
public AddedComponentEventArgs(ComponentEventArgs baseArgs, CompIdx componentType)
public AddedComponentEventArgs(ComponentEventArgs baseArgs, ComponentRegistration componentType)
{
BaseArgs = baseArgs;
ComponentType = componentType;
@@ -55,17 +55,4 @@ 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,16 +12,10 @@ namespace Robust.Shared.GameObjects
[RegisterComponent, NetworkedComponent, Access(typeof(SharedEyeSystem)), AutoGenerateComponentState(true)]
public sealed partial class EyeComponent : Component
{
#region Client
[ViewVariables] internal Eye? _eye = default!;
public IEye? Eye => _eye;
public const int DefaultVisibilityMask = 1;
[ViewVariables]
public MapCoordinates? Position => _eye?.Position;
#endregion
public readonly Eye Eye = new();
/// <summary>
/// If not null, this entity is used to update the eye's position instead of just using the component's owner.
@@ -37,6 +31,9 @@ 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;
@@ -47,8 +44,6 @@ 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.
@@ -58,7 +53,7 @@ namespace Robust.Shared.GameObjects
}
/// <summary>
/// Single layer used for Eye visiblity. Controls what entities they are allowed to see.
/// Single layer used for Eye visibility. 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;
[DataField("parent")] internal EntityUid _parent = EntityUid.Invalid;
[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,6 +311,7 @@ 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>
@@ -732,7 +733,7 @@ namespace Robust.Shared.GameObjects
{
public bool IsValid() => Uid.IsValid();
public bool Valid => IsValid();
public static readonly BroadphaseData Invalid = default;
public static readonly BroadphaseData Invalid = new(EntityUid.Invalid, EntityUid.Invalid, false, false);
// 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);
EntAddComponent(e.BaseArgs.Owner, e.ComponentType.Idx);
}
public void OnComponentRemoved(in RemovedComponentEventArgs e)

View File

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

@@ -0,0 +1,115 @@
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,8 +256,7 @@ public partial class EntityManager
/// <inheritdoc />
public HashSet<EntityUid> GetEntitySet(HashSet<NetEntity> netEntities)
{
var entities = new HashSet<EntityUid>();
entities.EnsureCapacity(netEntities.Count);
var entities = new HashSet<EntityUid>(netEntities.Count);
foreach (var netEntity in netEntities)
{
@@ -292,6 +291,16 @@ 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)
{
@@ -305,6 +314,16 @@ 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,6 +4,7 @@ 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;
@@ -40,6 +41,16 @@ 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,7 +2,10 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Arch.Core;
using Arch.Core.Extensions;
using Arch.Core.Utils;
using Collections.Pooled;
using Prometheus;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -13,6 +16,8 @@ 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
{
@@ -39,6 +44,8 @@ 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;
@@ -62,15 +69,8 @@ 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);
InitializeComponents();
InitializeArch();
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
_xformReg = _componentFactory.GetRegistration(typeof(TransformComponent));
_xformName = _xformReg.Name;
@@ -136,15 +136,6 @@ 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)
@@ -152,43 +143,28 @@ namespace Robust.Shared.GameObjects
return false;
}
// Get default prototype data
Dictionary<string, MappingDataNode> protoData = new();
try
{
_context.WritingReadingPrototypes = true;
var protoData = PrototypeManager.GetPrototypeData(prototype);
var comps = _world.GetAllComponents(uid);
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}");
// 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;
}
var comps = new HashSet<IComponent>(GetComponents(uid));
var compNames = new HashSet<string>(protoComps.Count);
foreach (var component in comps)
foreach (var comp in comps)
{
var component = (IComponent)comp!;
if (component.Deleted)
return false;
var compType = component.GetType();
var compName = _componentFactory.GetComponentName(compType);
if (compType == typeof(MetaDataComponent) || compType == typeof(TransformComponent))
if (compName == _xformName || compName == _metaReg.Name)
continue;
compNames.Add(compName);
// If the component isn't on the prototype then it's custom.
if (!protoComps.Contains(compName))
if (!protoData.TryGetValue(compName, out var protoMapping))
return false;
MappingDataNode compMapping;
@@ -202,25 +178,9 @@ namespace Robust.Shared.GameObjects
return false;
}
if (protoData.TryGetValue(compName, out var protoMapping))
{
var diff = compMapping.Except(protoMapping);
var diff = compMapping.Except(protoMapping);
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))
if (diff != null && diff.Children.Count != 0)
return false;
}
@@ -250,6 +210,7 @@ namespace Robust.Shared.GameObjects
FlushEntities();
_eventBus.ClearEventTables();
_entitySystemManager.Shutdown();
ShutdownArch();
ClearComponents();
ShuttingDown = false;
Started = false;
@@ -257,12 +218,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;
@@ -311,28 +272,27 @@ namespace Robust.Shared.GameObjects
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, overrides);
return CreateEntity(prototypeName, out _, out _, overrides);
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, overrides);
return CreateEntity(prototypeName, out _, out _, overrides);
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, out _, overrides);
_xforms.SetCoordinates(newEntity, TransformQuery.GetComponent(newEntity), coordinates, unanchor: false);
var newEntity = CreateEntity(prototypeName, out _, out var xform, overrides);
_xforms.SetCoordinates(newEntity, xform, coordinates, unanchor: false);
return newEntity;
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, out _, overrides);
var transform = TransformQuery.GetComponent(newEntity);
var newEntity = CreateEntity(prototypeName, out _, out var transform, overrides);
if (coordinates.MapId == MapId.Nullspace)
{
@@ -362,10 +322,19 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public int EntityCount => Entities.Count;
public int EntityCount => _world.Size;
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntities() => Entities;
public IEnumerable<EntityUid> GetEntities()
{
var ents = new List<Entity>();
_world.GetEntities(_archMetaQuery, ents);
foreach (var entity in ents)
{
yield return EntityUid.FromArch(_world, entity);
}
}
/// <inheritdoc />
public virtual void DirtyEntity(EntityUid uid, MetaDataComponent? metadata = null)
@@ -417,7 +386,7 @@ namespace Robust.Shared.GameObjects
/// <param name="e">Entity to remove</param>
public virtual void DeleteEntity(EntityUid? uid)
{
if (uid == null)
if (uid == null || uid == EntityUid.Invalid)
return;
var e = uid.Value;
@@ -442,9 +411,9 @@ namespace Robust.Shared.GameObjects
}
// Notify all entities they are being terminated prior to detaching & deleting
RecursiveFlagEntityTermination(e, meta);
var xform = TransformQuery.GetComponent(e);
RecursiveFlagEntityTermination(e, meta, xform);
TransformComponent? parentXform = null;
if (xform.ParentUid.IsValid())
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
@@ -455,14 +424,14 @@ namespace Robust.Shared.GameObjects
private void RecursiveFlagEntityTermination(
EntityUid uid,
MetaDataComponent metadata)
MetaDataComponent metadata,
TransformComponent transform)
{
var transform = TransformQuery.GetComponent(uid);
metadata.EntityLifeStage = EntityLifeStage.Terminating;
try
{
var ev = new EntityTerminatingEvent(uid);
var ev = new EntityTerminatingEvent(uid, metadata);
EventBus.RaiseLocalEvent(uid, ref ev, true);
}
catch (Exception e)
@@ -479,7 +448,7 @@ namespace Robust.Shared.GameObjects
continue;
}
RecursiveFlagEntityTermination(child, childMeta);
RecursiveFlagEntityTermination(child, childMeta, TransformQuery.GetComponent(child));
}
}
@@ -527,8 +496,12 @@ namespace Robust.Shared.GameObjects
_sawmill.Error($"Failed to delete all children of entity: {ToPrettyString(uid)}");
// Shut down all components.
foreach (var component in InSafeOrder(_entCompIndex[uid]))
var objComps = _world.GetAllComponents(uid);
foreach (var comp in objComps)
{
var component = (IComponent)comp!;
if (component.Running)
{
try
@@ -543,7 +516,7 @@ namespace Robust.Shared.GameObjects
}
// Dispose all my components, in a safe order so transform is available
DisposeComponents(uid);
DisposeComponents(uid, metadata);
metadata.EntityLifeStage = EntityLifeStage.Deleted;
try
@@ -556,14 +529,14 @@ namespace Robust.Shared.GameObjects
}
_eventBus.OnEntityDeleted(uid);
Entities.Remove(uid);
DestroyArch(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)
if (uid == null || uid.Value == EntityUid.Invalid)
return;
if (!QueuedDeletionsSet.Add(uid.Value))
@@ -577,7 +550,7 @@ namespace Robust.Shared.GameObjects
public bool EntityExists(EntityUid uid)
{
return MetaQuery.HasComponentInternal(uid);
return IsAlive(uid);
}
public bool EntityExists(EntityUid? uid)
@@ -596,12 +569,26 @@ namespace Robust.Shared.GameObjects
public bool Deleted(EntityUid uid)
{
return !MetaQuery.TryGetComponentInternal(uid, out var comp) || comp.EntityDeleted;
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);
}
public bool Deleted([NotNullWhen(false)] EntityUid? uid)
{
return !uid.HasValue || !MetaQuery.TryGetComponentInternal(uid.Value, out var comp) || comp.EntityDeleted;
return !uid.HasValue || Deleted(uid.Value);
}
/// <summary>
@@ -616,7 +603,10 @@ namespace Robust.Shared.GameObjects
DeleteEntity(e);
}
if (Entities.Count != 0)
// Arch bug atm
// CleanupArch();
if (_world.Size > 0)
_sawmill.Error("Entities were spawned while flushing entities.");
}
@@ -625,9 +615,10 @@ namespace Robust.Shared.GameObjects
/// </summary>
private protected EntityUid AllocEntity(
EntityPrototype? prototype,
out MetaDataComponent metadata)
out MetaDataComponent metadata,
out TransformComponent xform)
{
var entity = AllocEntity(out metadata);
var entity = AllocEntity(out metadata, out xform);
metadata._entityPrototype = prototype;
Dirty(entity, metadata, metadata);
return entity;
@@ -636,16 +627,9 @@ 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)
private EntityUid AllocEntity(out MetaDataComponent metadata, out TransformComponent xform)
{
var uid = GenerateEntityUid();
#if DEBUG
if (EntityExists(uid))
{
throw new InvalidOperationException($"UID already taken: {uid}");
}
#endif
SpawnEntityArch(out var uid);
// we want this called before adding components
EntityAdded?.Invoke(uid);
@@ -662,16 +646,15 @@ namespace Robust.Shared.GameObjects
SetNetEntity(uid, netEntity, metadata);
Entities.Add(uid);
// add the required MetaDataComponent directly.
AddComponentInternal(uid, metadata, _metaReg, false, true, metadata);
AddComponentInternal(uid, metadata, _metaReg, true, metadata);
// allocate the required TransformComponent
var xformComp = Unsafe.As<TransformComponent>(_componentFactory.GetComponent(_xformReg));
xform = Unsafe.As<TransformComponent>(_componentFactory.GetComponent(_xformReg));
#pragma warning disable CS0618 // Type or member is obsolete
xformComp.Owner = uid;
xform.Owner = uid;
#pragma warning restore CS0618 // Type or member is obsolete
AddComponentInternal(uid, xformComp, false, true, metadata);
AddComponentInternal(uid, xform, true, metadata);
return uid;
}
@@ -679,26 +662,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, IEntityLoadContext? context = null)
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, out TransformComponent xform, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return AllocEntity(out metadata);
return AllocEntity(out metadata, out xform);
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, context);
return CreateEntity(prototype, out metadata, out xform, context);
}
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected EntityUid CreateEntity(EntityPrototype prototype, out MetaDataComponent metadata, IEntityLoadContext? context = null)
private protected EntityUid CreateEntity(EntityPrototype prototype, out MetaDataComponent metadata, out TransformComponent xform, IEntityLoadContext? context = null)
{
var entity = AllocEntity(prototype, out metadata);
var entity = AllocEntity(prototype, out metadata, out xform);
try
{
EntityPrototype.LoadEntity(metadata.EntityPrototype, entity, ComponentFactory, this, _serManager, context);
LoadEntity(metadata.EntityPrototype, entity, context);
return entity;
}
catch (Exception e)
@@ -712,18 +695,105 @@ namespace Robust.Shared.GameObjects
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
{
EntityPrototype.LoadEntity(MetaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
LoadEntity(MetaQuery.GetComponent(entity).EntityPrototype, entity, context);
}
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
{
EntityPrototype.LoadEntity(prototype, entity, ComponentFactory, this, _serManager, context);
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);
}
}
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
{
try
{
// TODO: Pass this + transformcomp around
var meta = MetaQuery.GetComponent(entity);
InitializeEntity(entity, meta);
StartEntity(entity);
@@ -802,30 +872,10 @@ 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,9 +154,26 @@ 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?
{
EntityManager.Dirty(ent.Owner, ent.Comp, meta);
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);
}
/// <summary>
@@ -508,7 +525,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)
protected bool HasComp<T>(EntityUid uid) where T : IComponent
{
return EntityManager.HasComponent<T>(uid);
}
@@ -526,7 +543,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)
protected bool HasComp<T>([NotNullWhen(true)] EntityUid? uid) where T : IComponent
{
return EntityManager.HasComponent<T>(uid);
}
@@ -555,7 +572,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, overwrite);
EntityManager.AddComponent(uid, component);
}
/// <inheritdoc cref="IEntityManager.EnsureComponent&lt;T&gt;(EntityUid)"/>
@@ -597,13 +614,6 @@ 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)
@@ -646,13 +656,6 @@ 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)
@@ -1007,12 +1010,24 @@ 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}"/>
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
[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}"/>
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
bool logMissing = true)

View File

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

View File

@@ -1,4 +1,7 @@
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;
@@ -9,7 +12,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// This type contains a network identification number of an entity.
/// This type contains a local identification number of an entity.
/// This can be used by the EntityManager to access an entity
/// </summary>
[CopyByRef]
@@ -17,22 +20,40 @@ 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(0);
public static readonly EntityUid Invalid = new(-1 + ArchUidOffset, -1 + ArchVersionOffset);
/// <summary>
/// The first entity UID the entityManager should use when the manager is initialized.
/// </summary>
public static readonly EntityUid FirstUid = new(1);
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;
}
/// <summary>
/// Creates an instance of this structure, with the given network ID.
/// </summary>
public EntityUid(int id)
public EntityUid(int id, int version)
{
Id = id;
Version = version;
}
public bool Valid => IsValid();
@@ -40,16 +61,16 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Creates an entity UID by parsing a string number.
/// </summary>
public static EntityUid Parse(ReadOnlySpan<char> uid)
public static EntityUid Parse(ReadOnlySpan<char> uid, ReadOnlySpan<char> version)
{
return new EntityUid(int.Parse(uid));
return new EntityUid(int.Parse(uid), int.Parse(version));
}
public static bool TryParse(ReadOnlySpan<char> uid, out EntityUid entityUid)
public static bool TryParse(ReadOnlySpan<char> uid, ReadOnlySpan<char> version, out EntityUid entityUid)
{
try
{
entityUid = Parse(uid);
entityUid = Parse(uid, version);
return true;
}
catch (FormatException)
@@ -59,6 +80,15 @@ 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.
@@ -66,13 +96,13 @@ namespace Robust.Shared.GameObjects
[Pure]
public bool IsValid()
{
return Id > 0;
return Id > Invalid.Id;
}
/// <inheritdoc />
public bool Equals(EntityUid other)
{
return Id == other.Id;
return Id == other.Id && Version == other.Version;
}
/// <inheritdoc />
@@ -85,7 +115,10 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override int GetHashCode()
{
return Id;
unchecked
{
return Id.GetHashCode() * 397 ^ Version.GetHashCode();
}
}
/// <summary>
@@ -93,7 +126,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public static bool operator ==(EntityUid a, EntityUid b)
{
return a.Id == b.Id;
return a.Id == b.Id && a.Version == b.Version;
}
/// <summary>
@@ -113,6 +146,21 @@ 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, bool overwrite = false, MetaDataComponent? metadata = null) where T : IComponent;
void AddComponent<T>(EntityUid uid, T component, MetaDataComponent? metadata = null) where T : IComponent;
/// <summary>
/// Removes the component with the specified reference type,
@@ -136,14 +136,6 @@ 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>
@@ -164,7 +156,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);
bool HasComponent<T>(EntityUid uid) where T : IComponent;
/// <summary>
/// Checks if the entity has a component type.
@@ -172,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);
bool HasComponent<T>(EntityUid? uid) where T : IComponent;
/// <summary>
/// Checks if the entity has a component type.
@@ -281,7 +273,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);
bool TryGetComponent<T>(EntityUid uid, [NotNullWhen(true)] out T? component) where T : IComponent?;
/// <summary>
/// Returns the component of a specific type.
@@ -290,7 +282,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);
bool TryGetComponent<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component) where T : IComponent?;
/// <summary>
/// Returns the component of a specific type.
@@ -505,5 +497,7 @@ 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,6 +19,7 @@ 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);
@@ -52,7 +53,7 @@ public partial interface IEntityManager
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <summary>
@@ -64,7 +65,7 @@ public partial interface IEntityManager
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <summary>
@@ -84,8 +85,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,11 +171,16 @@ public sealed partial class EntityLookupSystem
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd)
{
var childEnumerator = _xformQuery.GetComponent(uid).ChildEnumerator;
if (!_xformQuery.TryGetComponent(uid, out var xform))
{
Log.Error($"Encountered deleted entity {uid} while performing entity lookup.");
return;
}
toAdd.Add(uid);
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
toAdd.Add(child.Value);
RecursiveAdd(child.Value, ref toAdd);
}
}
@@ -185,6 +190,11 @@ 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)
@@ -196,7 +206,6 @@ public sealed partial class EntityLookupSystem
{
foreach (var contained in con.ContainedEntities)
{
toAdd.Add(contained);
RecursiveAdd(contained, ref toAdd);
}
}

View File

@@ -6,7 +6,11 @@ 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;
@@ -15,18 +19,6 @@ 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,
@@ -88,6 +80,155 @@ 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,
@@ -270,7 +411,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, _metaQuery, _xformQuery)))
_container.IsEntityOrParentInContainer(uid, null, xform)))
{
continue;
}
@@ -308,15 +449,6 @@ 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));
@@ -332,7 +464,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, _metaQuery, _xformQuery)))
_container.IsEntityOrParentInContainer(uid, _metaQuery.GetComponent(uid), xform)))
{
continue;
}
@@ -366,14 +498,6 @@ 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;
@@ -416,14 +540,163 @@ public sealed partial class EntityLookupSystem
#endregion
#region EntityCoordinates
#region IPhysShape
public HashSet<T> GetComponentsInRange<T>(EntityCoordinates coordinates, float range) where T : IComponent
public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, HashSet<Entity<IComponent>> intersecting, LookupFlags flags = DefaultFlags)
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
return GetComponentsInRange<T>(mapPos, range);
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 void GetEntitiesInRange<T>(EntityCoordinates coordinates, float range, HashSet<Entity<T>> entities) where T : IComponent
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
@@ -441,35 +714,34 @@ 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.MapId, coordinates.Position, range, entities);
GetEntitiesInRange(type, coordinates, 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) where T : IComponent
public void GetEntitiesInRange<T>(MapCoordinates coordinates, float range, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities);
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities, flags);
}
public HashSet<Entity<T>> GetEntitiesInRange<T>(MapCoordinates coordinates, float range) where T : IComponent
public HashSet<Entity<T>> GetEntitiesInRange<T>(MapCoordinates coordinates, float range, LookupFlags flags = DefaultFlags) where T : IComponent
{
var entities = new HashSet<Entity<T>>();
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities);
GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities, flags);
return entities;
}
@@ -477,40 +749,15 @@ public sealed partial class EntityLookupSystem
#region MapId
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)
public void GetEntitiesInRange(Type type, MapId mapId, Vector2 worldPos, float range, HashSet<Entity<IComponent>> entities, LookupFlags flags = DefaultFlags)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
DebugTools.Assert(range > 0, "Range must be a positive float");
if (mapId == MapId.Nullspace) return;
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(worldPos - rangeVec, worldPos + rangeVec);
GetEntitiesIntersecting(type, mapId, worldAABB, entities);
var circle = new PhysShapeCircle(range, worldPos);
GetEntitiesIntersecting(type, mapId, circle, entities, flags);
}
[Obsolete]
@@ -521,18 +768,41 @@ 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) where T : IComponent
public void GetEntitiesInRange<T>(MapId mapId, Vector2 worldPos, float range, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
DebugTools.Assert(range > 0, "Range must be a positive float");
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");
if (mapId == MapId.Nullspace) return;
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(worldPos - rangeVec, worldPos + rangeVec);
GetEntitiesIntersecting(mapId, worldAABB, entities);
GetEntitiesIntersecting(mapId, shape, entities, flags);
}
#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,9 +11,11 @@ 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;
@@ -72,11 +74,14 @@ 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;
@@ -108,7 +113,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
SubscribeLocalEvent<BroadphaseComponent, EntityTerminatingEvent>(OnBroadphaseTerminating);
SubscribeLocalEvent<BroadphaseComponent, ComponentAdd>(OnBroadphaseAdd);
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<MapChangedEvent>(OnMapChange);
SubscribeLocalEvent<MoveEvent>(OnMove);
@@ -187,12 +191,6 @@ 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>(
@@ -368,7 +366,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
FixturesComponent manager,
EntityQuery<TransformComponent> xformQuery)
{
DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform, null, xformQuery));
DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static));
DebugTools.Assert(broadphase.Owner == broadUid);
@@ -434,7 +432,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(broadUid, default, false, staticBody);
xform.Broadphase ??= new BroadphaseData(broadUid, EntityUid.Invalid, false, staticBody);
(staticBody ? broadphase.StaticSundriesTree : broadphase.SundriesTree).AddOrUpdate(uid, aabb);
}
@@ -843,7 +841,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, null, _xformQuery))
if (xform.MapID == MapId.Nullspace || _container.IsEntityOrParentInContainer(xform.Owner, null, xform))
{
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(EyeComponent component)
public void UpdateEye(Entity<EyeComponent?> entity)
{
if (component._eye == null)
var component = entity.Comp;
if (!Resolve(entity, ref component))
return;
component._eye.Offset = component.Offset;
component._eye.DrawFov = component.DrawFov;
component._eye.Rotation = component.Rotation;
component._eye.Zoom = component.Zoom;
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;
}
public void SetOffset(EntityUid uid, Vector2 value, EyeComponent? eyeComponent = null)
@@ -31,10 +31,7 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.Offset = value;
if (eyeComponent._eye != null)
{
eyeComponent._eye.Offset = value;
}
eyeComponent.Eye.Offset = value;
Dirty(uid, eyeComponent);
}
@@ -47,13 +44,23 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.DrawFov = value;
if (eyeComponent._eye != null)
{
eyeComponent._eye.DrawFov = value;
}
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))
@@ -63,10 +70,7 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.Rotation = rotation;
if (eyeComponent._eye != null)
{
eyeComponent._eye.Rotation = rotation;
}
eyeComponent.Eye.Rotation = rotation;
}
public void SetTarget(EntityUid uid, EntityUid? value, EyeComponent? eyeComponent = null)
@@ -90,10 +94,7 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.Zoom = value;
if (eyeComponent._eye != null)
{
eyeComponent._eye.Zoom = value;
}
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, _mapManager.GetMapEntityIdOrThrow(xform.MapID));
_transform.SetParent(entity, xform, xform.MapUid!.Value);
var ev = new ChangedGridEvent(entity, oldGridId, null);
RaiseLocalEvent(entity, ref ev);
}

View File

@@ -429,8 +429,7 @@ public abstract partial class SharedMapSystem
private void OnGridInit(EntityUid uid, MapGridComponent component, ComponentInit args)
{
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
var xform = _xformQuery.GetComponent(uid);
// Force networkedmapmanager to send it due to non-ECS legacy code.
var curTick = _timing.CurTick;
@@ -443,7 +442,7 @@ public abstract partial class SharedMapSystem
component.LastTileModifiedTick = curTick;
if (xform.MapUid != null && xform.MapUid != uid)
_transform.SetParent(uid, xform, xform.MapUid.Value, xformQuery);
_transform.SetParent(uid, xform, xform.MapUid.Value);
if (!HasComp<MapComponent>(uid))
{
@@ -559,7 +558,10 @@ 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>();
@@ -622,6 +624,23 @@ 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)
@@ -669,9 +688,8 @@ public abstract partial class SharedMapSystem
public IEnumerable<TileRef> GetAllTiles(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
foreach (var kvChunk in grid.Chunks)
foreach (var chunk in grid.Chunks.Values)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
@@ -722,7 +740,7 @@ public abstract partial class SharedMapSystem
if (tiles.Count == 0)
return;
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
var modified = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
@@ -730,7 +748,7 @@ public abstract partial class SharedMapSystem
if (!grid.Chunks.TryGetValue(chunkIndex, out var chunk))
{
if (tile.IsEmpty)
return;
continue;
grid.Chunks[chunkIndex] = chunk = new MapChunk(chunkIndex.X, chunkIndex.Y, grid.ChunkSize)
{
@@ -739,24 +757,67 @@ public abstract partial class SharedMapSystem
}
var offset = chunk.GridTileToChunkTile(gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile);
if (SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile))
modified.Add(chunk);
}
foreach (var chunk in chunks)
foreach (var chunk in modified)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(uid, grid, chunks);
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;
}
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
return GetLocalTilesIntersecting(uid, grid, localAABB, ignoreEmpty, predicate);
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localAABB);
while (enumerator.MoveNext(out var tileRef))
{
yield return tileRef;
}
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea, bool ignoreEmpty = true,
@@ -765,9 +826,11 @@ public abstract partial class SharedMapSystem
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localArea);
while (enumerator.MoveNext(out var tileRef))
{
yield return tile;
yield return tileRef;
}
}
@@ -777,46 +840,11 @@ public abstract partial class SharedMapSystem
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
var enumerator = new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localArea);
while (enumerator.MoveNext(out var 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;
}
}
yield return tileRef;
}
}
@@ -966,12 +994,25 @@ 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);
@@ -988,22 +1029,32 @@ public abstract partial class SharedMapSystem
public IEnumerable<EntityUid> GetLocalAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localAABB, true, null))
var enumerator = new TilesEnumerator(this, true, null, uid, grid, localAABB);
while (enumerator.MoveNext(out var tileRef))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
var anchoredEnumerator = GetAnchoredEntitiesEnumerator(uid, grid, tileRef.GridIndices);
while (anchoredEnumerator.MoveNext(out var ent))
{
yield return ent;
yield return ent.Value;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
foreach (var tile in GetTilesIntersecting(uid, grid, 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 ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
var anchoredEnumerator = GetAnchoredEntitiesEnumerator(uid, grid, tileRef.GridIndices);
while (anchoredEnumerator.MoveNext(out var ent))
{
yield return ent;
yield return ent.Value;
}
}
}
@@ -1259,6 +1310,9 @@ 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);
@@ -1301,6 +1355,36 @@ 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);
@@ -1357,4 +1441,98 @@ 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);
}
}
}

View File

@@ -10,7 +10,6 @@ public abstract partial class SharedMapSystem
{
private void InitializeMap()
{
SubscribeLocalEvent<MapComponent, ComponentAdd>(OnMapAdd);
SubscribeLocalEvent<MapComponent, ComponentInit>(OnMapInit);
SubscribeLocalEvent<MapComponent, ComponentShutdown>(OnMapRemoved);
SubscribeLocalEvent<MapComponent, ComponentHandleState>(OnMapHandleState);
@@ -43,8 +42,6 @@ public abstract partial class SharedMapSystem
args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused);
}
protected abstract void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args);
private void OnMapInit(EntityUid uid, MapComponent component, ComponentInit args)
{
EnsureComp<GridTreeComponent>(uid);

View File

@@ -12,6 +12,7 @@ namespace Robust.Shared.GameObjects
{
public abstract partial class SharedMapSystem : EntitySystem
{
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] private readonly IMapManagerInternal _mapInternal = default!;

View File

@@ -1,11 +1,8 @@
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System;
using System.Linq;
@@ -65,12 +62,7 @@ public abstract partial class SharedTransformSystem
RaiseLocalEvent(uid, ref ev);
}
[Obsolete("Use overload that takes an explicit EntityUid for the grid instead.")]
public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridComponent grid, Vector2i tileIndices)
{
return AnchorEntity(uid, xform, grid.Owner, grid, tileIndices);
}
[Obsolete("Use Entity<T> variant")]
public bool AnchorEntity(
EntityUid uid,
TransformComponent xform,
@@ -78,12 +70,22 @@ public abstract partial class SharedTransformSystem
MapGridComponent grid,
Vector2i tileIndices)
{
if (!_map.AddToSnapGridCell(gridUid, grid, tileIndices, uid))
return AnchorEntity((uid, xform), (gridUid, grid), tileIndices);
}
public bool AnchorEntity(
Entity<TransformComponent> entity,
Entity<MapGridComponent> grid,
Vector2i tileIndices)
{
var (uid, xform) = entity;
if (!_map.AddToSnapGridCell(grid, grid, tileIndices, uid))
return false;
var wasAnchored = xform._anchored;
Dirty(uid, xform);
var wasAnchored = entity.Comp._anchored;
xform._anchored = true;
var meta = MetaData(uid);
Dirty(entity, meta);
// Mark as static before doing position changes, to avoid the velocity change on parent change.
_physics.TrySetBodyType(uid, BodyType.Static, xform: xform);
@@ -95,22 +97,36 @@ public abstract partial class SharedTransformSystem
}
// Anchor snapping. If there is a coordinate change, it will dirty the component for us.
var pos = new EntityCoordinates(gridUid, _map.GridTileToLocal(gridUid, grid, tileIndices).Position);
SetCoordinates(uid, xform, pos, unanchor: false);
var pos = new EntityCoordinates(grid, _map.GridTileToLocal(grid, grid, tileIndices).Position);
SetCoordinates((uid, xform, meta), pos, unanchor: false);
return true;
}
[Obsolete("Use Entity<T> variants")]
public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridComponent grid)
{
var tileIndices = _map.TileIndicesFor(grid.Owner, grid, xform.Coordinates);
return AnchorEntity(uid, xform, grid, tileIndices);
return AnchorEntity(uid, xform, grid.Owner, grid, tileIndices);
}
public bool AnchorEntity(EntityUid uid, TransformComponent xform)
{
return _mapManager.TryGetGrid(xform.GridUid, out var grid)
&& AnchorEntity(uid, xform, grid, _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates));
return AnchorEntity((uid, xform));
}
public bool AnchorEntity(Entity<TransformComponent> entity, Entity<MapGridComponent>? grid = null)
{
DebugTools.Assert(grid == null || grid.Value.Owner == entity.Comp.GridUid);
if (grid == null)
{
if (!TryComp(entity.Comp.GridUid, out MapGridComponent? gridComp))
return false;
grid = (entity.Comp.GridUid.Value, gridComp);
}
var tileIndices = _map.TileIndicesFor(grid.Value, grid.Value, entity.Comp.Coordinates);
return AnchorEntity(entity, grid.Value, tileIndices);
}
public void Unanchor(EntityUid uid, TransformComponent xform, bool setPhysics = true)
@@ -145,39 +161,23 @@ public abstract partial class SharedTransformSystem
#region Contains
/// <summary>
/// Returns whether the given entity is a child of this transform or one of its descendants.
/// Checks whether the first entity or one of it's children is the parent of some other entity.
/// </summary>
public bool ContainsEntity(TransformComponent xform, EntityUid entity)
public bool ContainsEntity(EntityUid parent, Entity<TransformComponent?> child)
{
return ContainsEntity(xform, entity, XformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
public bool ContainsEntity(TransformComponent xform, EntityUid entity, EntityQuery<TransformComponent> xformQuery)
{
return ContainsEntity(xform, xformQuery.GetComponent(entity), xformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform)
{
return ContainsEntity(xform, entityTransform, XformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform, EntityQuery<TransformComponent> xformQuery)
{
// Is the entity the scene root
if (!entityTransform.ParentUid.IsValid())
if (!Resolve(child.Owner, ref child.Comp))
return false;
// Is this the direct parent of the entity
if (xform.Owner == entityTransform.ParentUid)
if (!child.Comp.ParentUid.IsValid())
return false;
if (parent == child.Comp.ParentUid)
return true;
// Recursively search up the parents for this object
var parentXform = xformQuery.GetComponent(entityTransform.ParentUid);
return ContainsEntity(xform, parentXform, xformQuery);
if (!XformQuery.TryGetComponent(child.Comp.ParentUid, out var parentXform))
return false;
return ContainsEntity(parent, (child.Comp.ParentUid, parentXform));
}
#endregion
@@ -237,7 +237,7 @@ public abstract partial class SharedTransformSystem
{
var msg = $"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(component.ParentUid)}, new parent: {ToPrettyString(uid)}";
#if EXCEPTION_TOLERANCE
Logger.Error(msg);
Log.Error(msg);
Del(uid);
#else
throw new InvalidOperationException(msg);
@@ -432,7 +432,7 @@ public abstract partial class SharedTransformSystem
public void SetCoordinates(EntityUid uid, EntityCoordinates value)
{
SetCoordinates(uid, Transform(uid), value);
SetCoordinates((uid, Transform(uid), MetaData(uid)), value);
}
/// <summary>
@@ -443,8 +443,15 @@ public abstract partial class SharedTransformSystem
/// <param name="unanchor">Whether or not to unanchor the entity before moving. Note that this will still move the
/// entity even when false. If you set this to false, you need to manually manage the grid lookup changes and ensure
/// the final position is valid</param>
public void SetCoordinates(EntityUid uid, TransformComponent xform, EntityCoordinates value, Angle? rotation = null, bool unanchor = true, TransformComponent? newParent = null, TransformComponent? oldParent = null)
public void SetCoordinates(
Entity<TransformComponent, MetaDataComponent> entity,
EntityCoordinates value,
Angle? rotation = null,
bool unanchor = true,
TransformComponent? newParent = null,
TransformComponent? oldParent = null)
{
var (uid, xform, meta) = entity;
// NOTE: This setter must be callable from before initialize.
if (xform.ParentUid == value.EntityId
@@ -460,8 +467,23 @@ public abstract partial class SharedTransformSystem
if (xform.Anchored && unanchor)
Unanchor(uid, xform);
if (value.EntityId != xform.ParentUid && value.EntityId.IsValid())
{
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
{
Log.Error($"{ToPrettyString(uid)} is attempting to move while terminating. New parent: {ToPrettyString(value.EntityId)}. Trace: {Environment.StackTrace}");
return;
}
if (TerminatingOrDeleted(value.EntityId))
{
Log.Error($"{ToPrettyString(uid)} is attempting to attach itself to a terminating entity {ToPrettyString(value.EntityId)}. Trace: {Environment.StackTrace}");
return;
}
}
// Set new values
Dirty(uid, xform);
Dirty(uid, xform, meta);
xform.MatricesDirty = true;
xform._localPosition = value.Position;
@@ -583,6 +605,18 @@ public abstract partial class SharedTransformSystem
RaiseLocalEvent(uid, ref moveEvent, true);
}
public void SetCoordinates(
EntityUid uid,
TransformComponent xform,
EntityCoordinates value,
Angle? rotation = null,
bool unanchor = true,
TransformComponent? newParent = null,
TransformComponent? oldParent = null)
{
SetCoordinates((uid, xform, MetaData(uid)), value, rotation, unanchor, newParent, oldParent);
}
#endregion
#region Parent
@@ -827,6 +861,27 @@ public abstract partial class SharedTransformSystem
return GetWorldPosition(component);
}
[Pure]
public MapCoordinates GetMapCoordinates(EntityUid entity, TransformComponent? xform = null)
{
if (!XformQuery.Resolve(entity, ref xform))
return MapCoordinates.Nullspace;
return GetMapCoordinates(xform);
}
[Pure]
public MapCoordinates GetMapCoordinates(TransformComponent xform)
{
return new MapCoordinates(GetWorldPosition(xform), xform.MapID);
}
[Pure]
public MapCoordinates GetMapCoordinates(Entity<TransformComponent> entity)
{
return GetMapCoordinates(entity.Comp);
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
{

View File

@@ -49,21 +49,6 @@ namespace Robust.Shared.GameObjects
SubscribeLocalEvent<TransformComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<TransformComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<TransformComponent, GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
}
private void OnParentChange(ref EntParentChangedMessage ev)
{
// TODO: when PVS errors on live servers get fixed, wrap this whole subscription in an #if DEBUG block to speed up parent changes & entity deletion.
if (ev.Transform.ParentUid == EntityUid.Invalid)
return;
if (LifeStage(ev.Entity) >= EntityLifeStage.Terminating)
Log.Error($"Entity {ToPrettyString(ev.Entity)} is getting attached to a new parent while terminating. New parent: {ToPrettyString(ev.Transform.ParentUid)}. Trace: {Environment.StackTrace}");
if (LifeStage(ev.Transform.ParentUid) >= EntityLifeStage.Terminating)
Log.Error($"Entity {ToPrettyString(ev.Entity)} is attaching itself to a terminating entity {ToPrettyString(ev.Transform.ParentUid)}. Trace: {Environment.StackTrace}");
}
private void MapManagerOnTileChanged(ref TileChangedEvent e)
@@ -261,6 +246,39 @@ namespace Robust.Shared.GameObjects
// We're on a grid, need to convert the coordinates to grid tiles.
return _map.CoordinatesToTile(xform.GridUid.Value, Comp<MapGridComponent>(xform.GridUid.Value), xform.Coordinates);
}
/// <summary>
/// Helper method that returns the grid tile an entity is on.
/// </summary>
public Vector2i GetGridTilePositionOrDefault(Entity<TransformComponent?> entity, MapGridComponent? grid = null)
{
var xform = entity.Comp;
if(!Resolve(entity.Owner, ref xform) || xform.GridUid == null)
return Vector2i.Zero;
if (!Resolve(xform.GridUid.Value, ref grid))
return Vector2i.Zero;
return _map.CoordinatesToTile(xform.GridUid.Value, grid, xform.Coordinates);
}
/// <summary>
/// Helper method that returns the grid tile an entity is on.
/// </summary>
public bool TryGetGridTilePosition(Entity<TransformComponent?> entity, out Vector2i indices, MapGridComponent? grid = null)
{
indices = default;
var xform = entity.Comp;
if(!Resolve(entity.Owner, ref xform) || xform.GridUid == null)
return false;
if (!Resolve(xform.GridUid.Value, ref grid))
return false;
indices = _map.CoordinatesToTile(xform.GridUid.Value, grid, xform.Coordinates);
return true;
}
}
[ByRefEvent]

View File

@@ -1,10 +1,16 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Enums;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public abstract class SharedUserInterfaceSystem : EntitySystem
{
protected readonly Dictionary<ICommonSession, List<PlayerBoundUserInterface>> OpenInterfaces = new();
public override void Initialize()
{
base.Initialize();
@@ -99,6 +105,73 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
{
}
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>
/// Switches between closed and open for a specific client.
/// </summary>
public virtual 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);
}
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.Channel);
// Fun fact, clients needs to have BUIs open before they can receive the state.....
if (bui.LastStateMsg != null)
RaiseNetworkEvent(bui.LastStateMsg, session.Channel);
ActivateInterface(bui);
return true;
}
private void ActivateInterface(PlayerBoundUserInterface ui)
{
EnsureComp<ActiveUserInterfaceComponent>(ui.Owner).Interfaces.Add(ui);
}
internal bool TryCloseUi(ICommonSession? session, EntityUid uid, Enum uiKey, bool remoteCall = false, UserInterfaceComponent? uiComp = null)
{
if (!Resolve(uid, ref uiComp))
@@ -119,6 +192,27 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
return true;
}
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.Channel);
CloseShared(bui, session, activeUis);
return true;
}
/// <summary>
/// Raised by client-side UIs to send to server.
/// </summary>

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