Compare commits

...

109 Commits

Author SHA1 Message Date
Pieter-Jan Briers
de8c2c14bb Replace Lidgren encryption stuff with Libsodium (#2646) 2022-04-02 16:24:04 +02:00
mirrorcult
baebfff321 Merge pull request #2673 from ElectroJr/fix-description 2022-04-01 17:02:43 -07:00
Paul
24191404aa version 0.8.86 2022-04-02 01:28:12 +02:00
Paul Ritter
990842f5c2 fixes pvs dirty crash (#2674) 2022-04-02 06:09:14 +11:00
ElectroJr
2c2bd3f330 Version: 0.8.85 2022-04-02 00:38:07 +13:00
Leon Friedrich
1ae14c4bfa Add error tolerance to physics map (#2672) 2022-04-01 22:07:27 +11:00
metalgearsloth
61a1701dc9 Fix PVS take 3 (#2671) 2022-04-01 22:04:30 +11:00
ElectroJr
c6ea11346e fix entity description proxy method. 2022-04-02 00:02:58 +13:00
mirrorcult
ad7c871e28 Make effects properly clean up after itself (#2670) 2022-04-01 13:00:54 +11:00
metalgearsloth
c77b3f8022 Version: 0.8.84 2022-04-01 10:10:20 +11:00
metalgearsloth
b263f4a1df Fix PVS crash 2 (#2668)
Co-authored-by: Paul <ritter.paul1@googlemail.com>
2022-04-01 10:09:31 +11:00
Paul
1a11d41bba disable benchmarks workflow 2022-03-31 17:06:48 +02:00
Leon Friedrich
14d0a77644 Make directional sprite matrices static (#2661) 2022-04-01 00:11:17 +11:00
ElectroJr
b75045ce1a Version: 0.8.83 2022-03-31 15:25:31 +13:00
Leon Friedrich
1d8a8e15ef Add a is-In-container metadata flag. (#2585) 2022-03-31 13:23:33 +11:00
Leon Friedrich
28a087c267 Add GetEntitiesIntersectingBody (#2641) 2022-03-31 11:52:35 +11:00
Jane Lewis
8dc849b6bb Cache Sprite AABBs (#2622) 2022-03-31 11:52:00 +11:00
mirrorcult
7e56f46f35 Remove network component messages (#2659) 2022-03-31 10:24:27 +11:00
metalgearsloth
4453a23069 Version: 0.8.82 2022-03-30 23:34:50 +11:00
metalgearsloth
57be0bc845 Fix nullability (#2658) 2022-03-30 23:34:31 +11:00
Paul Ritter
430910ff36 sudo the benchmarks to elevate them to high prio 2022-03-30 14:10:13 +02:00
Paul Ritter
4c95807e2d how 2022-03-30 14:08:53 +02:00
metalgearsloth
92fd04552d Version: 0.8.81 2022-03-30 22:58:42 +11:00
Leon Friedrich
9c7e244821 ECS collision wake (#2656) 2022-03-30 22:57:57 +11:00
Vera Aguilera Puerto
41f64b5c3c Add overload to IntersectRayWithPredicate. (#2650) 2022-03-30 22:57:40 +11:00
Paul Ritter
4003003580 caching trees in pvs (#2652)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-03-30 22:57:31 +11:00
Paul Ritter
f49341b53c im a dummy 2022-03-30 11:16:09 +02:00
Leon Friedrich
61d9d9a832 Make the UI System TryGet functions not log failed resolves (#2657) 2022-03-30 16:37:34 +11:00
Paul
c0b7dd053f working on benchmarks workflow 2022-03-30 00:31:51 +02:00
Paul
f642e10acc versionbump to test benchmarks 2022-03-30 00:25:50 +02:00
Paul Ritter
2fb64480db benchmark workflow & storing it in db (#2653) 2022-03-29 21:43:32 +02:00
metalgearsloth
075b5ec203 Kill local comp messages (#2648) 2022-03-29 23:37:58 +11:00
metalgearsloth
21f2fe6b1b Minor spawn opt (#2651) 2022-03-29 12:34:42 +02:00
metalgearsloth
89080ef7c7 Version: 0.8.79 2022-03-29 18:40:32 +11:00
metalgearsloth
f5d36dc9ca Fix physics parenting bug (#2654) 2022-03-29 18:39:49 +11:00
wrexbe
ef87610b36 Map loader saving change (#2638)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-03-28 16:56:16 +11:00
mirrorcult
c89f1e4d31 Paramless ctor for joints (#2647)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-03-28 16:31:04 +11:00
metalgearsloth
c4805d89e0 Version: 0.8.78 2022-03-27 17:34:43 +11:00
Vera Aguilera Puerto
659ffb028a Dispose of SequencerEvents in MidiRenderer.
Oops!
2022-03-27 00:03:07 +01:00
metalgearsloth
a14c956ccc Nuke EntMapIdChangedMessage (#2621)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2022-03-26 18:14:56 +01:00
metalgearsloth
facb3914a4 Fix joints crash (#2609) 2022-03-26 18:13:56 +01:00
Vera Aguilera Puerto
aab8f7876b Benchmarks for EntityManager (#2578) 2022-03-26 17:55:00 +01:00
Pieter-Jan Briers
1f199ea71c Add ITaskManager.BlockWaitOnTask()
Smugleaf needed this.

Also changed RobustSynchronizationContext to use channels instead of ConcurrentQueue internally.
2022-03-26 17:02:05 +01:00
metalgearsloth
00b557b77a Struct enumerator for anchored entities (#2626) 2022-03-26 12:53:25 +01:00
metalgearsloth
eac536cffe overlay allocs (#2642) 2022-03-26 12:48:11 +01:00
DrSmugleaf
bf75c6f247 Add IEntitySystemManager.GetEntitySystemOrNull (#2643) 2022-03-26 16:35:09 +11:00
metalgearsloth
1518c79291 Struct enumerator for GetAllGrids (#2624) 2022-03-26 03:17:42 +11:00
metalgearsloth
aae2f72d1a PVS pooling 4 (#2640) 2022-03-25 11:51:59 +01:00
Jane Lewis
273aa3d6ea Show player rotation in debug overlay (#2631) 2022-03-25 18:12:53 +11:00
metalgearsloth
756c4510eb Reduce prediction alloc (#2639) 2022-03-25 18:10:31 +11:00
Paul
cd50e89aec v0.8.77 2022-03-24 23:51:55 +01:00
metalgearsloth
25b648b929 Reduce PVS allocs a bunch (again (again)) (#2630) 2022-03-24 23:48:37 +01:00
metalgearsloth
1dbbb08250 Reduce server allocs a bunch (#2625) 2022-03-24 23:39:52 +01:00
DrSmugleaf
5432f7311e Fix keyboard focused being released and grabbed again immediately afterwards (#2634) 2022-03-24 15:25:25 +01:00
Zoldorf
e662802b4c Update build-docfx.yml 2022-03-23 17:32:12 -06:00
Vera Aguilera Puerto
bae2c390bb Improve Color.FromHex exception message.
It now includes the invalid hexcode argument.
Something in content kept getting these exceptions on prod and knowing the invalid argument would be very helpful when debugging these in the future.
2022-03-23 16:00:28 +01:00
Leon Friedrich
7976926eaf fix sequence node Except() (#2610)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-03-23 23:34:21 +11:00
metalgearsloth
4aee730753 Version: 0.8.76 2022-03-23 20:48:42 +11:00
metalgearsloth
e87c342aa2 inline robusttree pool (#2628) 2022-03-23 20:47:53 +11:00
Paul
d335d74d8f adds maxpoolsize to treepool 2022-03-22 18:02:00 +01:00
metalgearsloth
bc68b0e3ec Version: 0.8.75 2022-03-22 13:57:31 +11:00
metalgearsloth
b1eadbc58f Fix PVS allocation issues (#2623) 2022-03-22 13:55:57 +11:00
Silver
2d3165a16f remove inheritedmembers div 2022-03-21 18:02:35 -06:00
Zoldorf
d99d25de37 Create build-docfx.yml 2022-03-21 17:22:46 -06:00
Zoldorf
be035ab002 :mistake: 2022-03-21 17:22:08 -06:00
Zoldorf
12c8883eea Update build-test.yml 2022-03-21 15:36:41 -06:00
Zoldorf
758c7c1869 Update build-test.yml 2022-03-21 15:28:12 -06:00
Zoldorf
72ab1f5a1a Update build-test.yml 2022-03-21 15:25:40 -06:00
Zoldorf
6b9c1369c0 Update build-test.yml 2022-03-21 15:22:32 -06:00
Zoldorf
fd1f1dc64a Delete build-docfx.yml 2022-03-21 15:12:24 -06:00
Zoldorf
e211368bc4 Update build-docfx.yml 2022-03-21 15:07:41 -06:00
Zoldorf
39307f916d build-docfx.yml 2022-03-21 15:04:24 -06:00
metalgearsloth
7378cc50a1 Version: 0.8.74 2022-03-21 20:09:16 +11:00
Silver
867b9ad932 Adds Docfx to RobustToolbox 2022-03-20 23:08:59 -06:00
Paul Ritter
03064255f6 Makes Robusttree Children nullable to save allocations (#2570) 2022-03-21 03:02:52 +11:00
Pieter-Jan Briers
c9ccd0b873 Fix file dialogs crashing on macOS 2022-03-20 16:47:20 +01:00
metalgearsloth
650eceea7e Remove IoC + GetComp for every tile intersection (#2617) 2022-03-20 16:54:05 +11:00
DrSmugleaf
452ad5a6e5 Version: 0.8.73 2022-03-19 19:41:22 +01:00
Leon Friedrich
7c292c75d4 Add more information to reflection errors. (#2613) 2022-03-20 00:11:42 +11:00
DrSmugleaf
cab4113697 Add myself to CODEOWNERS 2022-03-19 11:27:54 +01:00
metalgearsloth
f1a5de79b5 Change entitylookup bounds to method (#2611) 2022-03-19 16:14:24 +11:00
metalgearsloth
191e80c175 Add EntityQuery support to filters (#2608) 2022-03-19 15:54:13 +11:00
Leon Friedrich
241bce56c8 Add Resolve() to component queries (#2607) 2022-03-19 15:53:39 +11:00
metalgearsloth
b105639b31 More transform ECS methods (#2606) 2022-03-18 10:32:49 +11:00
ElectroJr
419e63ecd5 Version: 0.8.72 2022-03-16 15:32:19 +13:00
metalgearsloth
46e632bf9d Fix effect overlay crash (#2604) 2022-03-15 18:05:13 +11:00
Leon Friedrich
a8871f4ff4 Add an assert to positional audio (#2602) 2022-03-15 14:54:50 +11:00
Paul
65f28c023d also write + bugfix + ratio 2022-03-15 00:43:40 +01:00
Paul
e506f6aba2 error for invalid customtypeserializer 2022-03-15 00:36:05 +01:00
metalgearsloth
93ae74b5a7 Update nuget dependencies (#2581) 2022-03-14 08:57:20 +01:00
Kara D
7b25fa98ee Version: 0.8.71 2022-03-13 19:30:44 -07:00
mirrorcult
e320b7abb4 Merge pull request #2599 from Sanctuary-Station/staging 2022-03-13 19:26:16 -07:00
Acruid
6b9b56ca83 Grid Components Allocate Grids (#2597)
* Deleting a map is now routed through MapComponent deletion.

* Remove map and grid deletions from map networking, just delete the entity if you want to remove the map.

* Moved the chunkSize property of created grids from the networked MapData to the MapGridComponent state.

* Remove unused IMapManager.DefaultMap property.

* Removed MapCreationTick field from MapManager.MapCollection.

* Removed _maps hashset field from MapManager.

* Removed CreatedMaps array from network MapData.

* MapGrid.ParentMapId is now derived from the bound TransformComponent, and isn't required in the MapGrid ctor.
Removed MapGrid.CreatedTick, it can be found on MapGridComponent.CreationTick.

* Remove a bunch of ApplyGameStatePre code duplication.

* Completely refactored CreateGrid.

* Adds AddComponentUninitialized to the ECS system. This allows you to access a component before it is initialized in a using block.

* Use AddComponentUninitialized to allocate a grid after the component is allocated.

* MapLoader now creates the grids after creating the map entities.

* Chunksize and TileSize properties are now actually read out of the map yaml.
TileSize has a public Setter.

* Minor cleanup.

* Moved grid allocation onto the MapGridComponent.

* Final Cleanup.

* Merge Fail.

* Fixed test, grid was getting deleted because it was empty.

* Remove DeletedChunkDatum from grid networking.

* ApplyMapGridState moved from map manager to the MapGridComponent.
2022-03-13 18:28:59 -07:00
Jezithyr
4d19790b3d This should fix the font caching issue? 2022-03-13 16:51:09 -07:00
Jezithyr
2027863d4c Addressing PR Feedback 2022-03-13 15:59:32 -07:00
Vera Aguilera Puerto
4a4444a1ee Adds MemoryContentRoot. (#2590) 2022-03-13 23:52:34 +01:00
Jezithyr
f10316d384 Fixing missing semicolons 2022-03-13 15:23:45 -07:00
Jezithyr
778be40c86 Condensing code 2022-03-13 14:43:24 -07:00
Jesse Rougeau
2046323c71 Update Robust.Client/UserInterface/UserInterfaceManager.cs
More condensing

Co-authored-by: Paul Ritter <ritter.paul1@googlemail.com>
2022-03-13 13:06:02 -07:00
Jesse Rougeau
71bb5cd58e Update Robust.Client/UserInterface/UserInterfaceManager.cs
Condensing sizing logic

Co-authored-by: Paul Ritter <ritter.paul1@googlemail.com>
2022-03-13 13:05:30 -07:00
Jezithyr
71c6e437fa Removing debug and fixing indentation 2022-03-12 20:36:23 -07:00
Jezithyr
26e1474179 Implemented auto UI Scaling :D
- Implemented autoUI scaling, scaling supports down to 520 by 520 pixel screens(Idk why you'd even have this)
- UI scaling can be adjusted by overriding the window root class
2022-03-12 20:31:13 -07:00
Kara D
89a7459fda Update .NET version for build-test workflow
This should hopefully make it catch CS8983
2022-03-12 19:06:23 -07:00
Paul Ritter
a61adf5631 makes budgets clientside & replicated (#2593) 2022-03-13 00:06:40 +11:00
Leon Friedrich
97db1712f3 Fix grid traversal velocities (#2594)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-03-12 23:57:30 +11:00
Paul
ea7012d114 version 0.8.70 2022-03-09 20:58:17 +01:00
Moony
d3eaea5697 Add support for tile variants. (#2577) 2022-03-09 20:56:58 +01:00
metalgearsloth
4ca3fbe4bf Version: 0.8.69 2022-03-10 00:29:08 +11:00
Vera Aguilera Puerto
81fea7595a AttachToGridOrMap checks if the grid/map is being terminated/is deleted. (#2592) 2022-03-10 00:27:18 +11:00
169 changed files with 4744 additions and 1990 deletions

2
.github/CODEOWNERS vendored
View File

@@ -11,7 +11,7 @@
/Robust.Analyzers @PaulRitter
/Robust.*/GameStates @PaulRitter
/Robust.Shared/Analyzers @PaulRitter
/Robust.*/Serialization @PaulRitter
/Robust.*/Serialization @PaulRitter @DrSmugleaf
/Robust.*/Prototypes @PaulRitter
/Robust.Shared/GameObjects/ComponentDependencies @PaulRitter
/Robust.*/Containers @PaulRitter

33
.github/workflows/benchmarks.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Benchmarks
#on:
# push
#schedule:
# - cron: '0 5 * * *'
#push:
# tags:
# - 'v*'
env:
ROBUST_BENCHMARKS_ENABLE_SQL: 1
ROBUST_BENCHMARKS_SQL_ADDRESS: ${{ secrets.BENCHMARKS_WRITE_ADDRESS }}
ROBUST_BENCHMARKS_SQL_PORT: ${{ secrets.BENCHMARKS_WRITE_PORT }}
ROBUST_BENCHMARKS_SQL_USER: ${{ secrets.BENCHMARKS_WRITE_USER }}
ROBUST_BENCHMARKS_SQL_PASSWORD: ${{ secrets.BENCHMARKS_WRITE_PASSWORD }}
ROBUST_BENCHMARKS_SQL_DATABASE: benchmarks
jobs:
benchmark:
name: Run Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Install dependencies
run: dotnet restore
- name: Run benchmark
run: cd Robust.Benchmarks && sudo dotnet run --filter '*' --configuration Release

34
.github/workflows/build-docfx.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Build & Publish DocFX
on:
schedule:
- cron: "0 0 * * 0"
jobs:
docfx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Install dependencies
run: dotnet restore
- name: Build Project
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Build DocFX
uses: nikeee/docfx-action@v1.0.0
with:
args: Robust.Docfx/docfx.json
- name: Publish Docfx Documentation on GitHub Pages
uses: maxheld83/ghpages@master
env:
BUILD_DIR: Robust.Docfx/_robust-site
GH_PAT: ${{ secrets.GH_PAT }}

View File

@@ -22,10 +22,12 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
dotnet-version: 6.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Test Engine
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -v n

2
.gitignore vendored
View File

@@ -76,3 +76,5 @@ MSBuild/Robust.Custom.targets
release/
Robust.Docfx/*-site
Robust.Docfx/api

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -6,9 +6,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Globalization;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Filters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Validators;
using Robust.Benchmarks.Exporters;
namespace Robust.Benchmarks.Configs;
public sealed class DefaultSQLConfig : IConfig
{
public static readonly IConfig Instance = new DefaultSQLConfig();
private DefaultSQLConfig(){}
public IEnumerable<IExporter> GetExporters()
{
yield return SQLExporter.Default;
}
public IEnumerable<IColumnProvider> GetColumnProviders() => DefaultConfig.Instance.GetColumnProviders();
public IEnumerable<ILogger> GetLoggers() => DefaultConfig.Instance.GetLoggers();
public IEnumerable<IDiagnoser> GetDiagnosers() => DefaultConfig.Instance.GetDiagnosers();
public IEnumerable<IAnalyser> GetAnalysers() => DefaultConfig.Instance.GetAnalysers();
public IEnumerable<Job> GetJobs() => DefaultConfig.Instance.GetJobs();
public IEnumerable<IValidator> GetValidators() => DefaultConfig.Instance.GetValidators();
public IEnumerable<HardwareCounter> GetHardwareCounters() => DefaultConfig.Instance.GetHardwareCounters();
public IEnumerable<IFilter> GetFilters() => DefaultConfig.Instance.GetFilters();
public IEnumerable<BenchmarkLogicalGroupRule> GetLogicalGroupRules() => DefaultConfig.Instance.GetLogicalGroupRules();
public IOrderer Orderer => DefaultConfig.Instance.Orderer!;
public SummaryStyle SummaryStyle => DefaultConfig.Instance.SummaryStyle;
public ConfigUnionRule UnionRule => DefaultConfig.Instance.UnionRule;
public string ArtifactsPath => DefaultConfig.Instance.ArtifactsPath;
public CultureInfo CultureInfo => DefaultConfig.Instance.CultureInfo!;
public ConfigOptions Options => DefaultConfig.Instance.Options;
}

View File

@@ -0,0 +1,54 @@
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class AddRemoveComponentBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.RegisterComponents(f => f.RegisterClass<A>())
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{
_entityManager.SpawnEntity(null, coords);
}
}
[Benchmark]
public void AddRemoveComponent()
{
for (var i = 2; i <= N+1; i++)
{
var uid = new EntityUid(i);
_entityManager.AddComponent<A>(uid);
_entityManager.RemoveComponent<A>(uid);
}
}
[ComponentProtoName("A")]
public sealed class A : Component
{
}
}

View File

@@ -0,0 +1,61 @@
using System;
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class GetComponentBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N;
public A[] Comps = default!;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.RegisterComponents(f => f.RegisterClass<A>())
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
Comps = new A[N+2];
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{
var uid = _entityManager.SpawnEntity(null, coords);
_entityManager.AddComponent<A>(uid);
}
}
[Benchmark]
public A[] GetComponent()
{
for (var i = 2; i <= N+1; i++)
{
Comps[i] = _entityManager.GetComponent<A>(new EntityUid(i));
}
// Return something so the JIT doesn't optimize out all the GetComponent calls.
return Comps;
}
[ComponentProtoName("A")]
public sealed class A : Component
{
}
}

View File

@@ -0,0 +1,62 @@
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class SpawnDeleteEntityBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
private MapCoordinates _mapCoords = MapCoordinates.Nullspace;
private EntityCoordinates _entCoords = EntityCoordinates.Invalid;
[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.RegisterComponents(f => f.RegisterClass<A>())
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
_mapCoords = new MapCoordinates(0, 0, new MapId(1));
var uid = _simulation.AddMap(_mapCoords.MapId);
_entCoords = new EntityCoordinates(uid, 0, 0);
}
[Benchmark(Baseline = true)]
public void SpawnDeleteEntityMapCoords()
{
for (var i = 0; i < N; i++)
{
var uid = _entityManager.SpawnEntity(null, _mapCoords);
_entityManager.DeleteEntity(uid);
}
}
[Benchmark]
public void SpawnDeleteEntityEntCoords()
{
for (var i = 0; i < N; i++)
{
var uid = _entityManager.SpawnEntity(null, _entCoords);
_entityManager.DeleteEntity(uid);
}
}
[ComponentProtoName("A")]
public sealed class A : Component
{
}
}

View File

@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Mathematics;
using BenchmarkDotNet.Reports;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Npgsql;
namespace Robust.Benchmarks.Exporters;
public sealed class SQLExporter : IExporter
{
public static readonly IExporter Default = new SQLExporter();
private SQLExporter(){}
public void ExportToLog(Summary summary, ILogger logger)
{
Export(summary, logger);
}
public IEnumerable<string> ExportToFiles(Summary summary, ILogger consoleLogger)
{
Export(summary, consoleLogger);
return Array.Empty<string>();
}
private bool TryGetEnvironmentVariable(string name, ILogger logger, [NotNullWhen(true)] out string? value)
{
value = Environment.GetEnvironmentVariable(name);
if (value == null)
logger.WriteError($"ROBUST_BENCHMARKS_ENABLE_SQL is set, but {name} is missing.");
return value != null;
}
private void Export(Summary summary, ILogger logger)
{
if (!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_ADDRESS", logger, out var address) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_PORT", logger, out var rawPort) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_USER", logger, out var user) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_PASSWORD", logger, out var password) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_DATABASE", logger, out var db) ||
!TryGetEnvironmentVariable("GITHUB_SHA", logger, out var gitHash))
return;
if (!int.TryParse(rawPort, out var port))
{
logger.WriteError("Failed parsing ROBUST_BENCHMARKS_SQL_PORT to int.");
return;
}
var builder = new DbContextOptionsBuilder<BenchmarkContext>();
var connectionString = new NpgsqlConnectionStringBuilder
{
Host = address,
Port = port,
Database = db,
Username = user,
Password = password
}.ConnectionString;
builder.UseNpgsql(connectionString);
using var ctx = new BenchmarkContext(builder.Options);
try
{
ctx.Database.Migrate();
ctx.BenchmarkRuns.Add(BenchmarkRun.FromSummary(summary, gitHash));
ctx.SaveChanges();
}
finally
{
ctx.Dispose();
}
}
public string Name => "sql";
}
public sealed class DesignTimeContextFactoryPostgres : IDesignTimeDbContextFactory<BenchmarkContext>
{
public BenchmarkContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<BenchmarkContext>();
optionsBuilder.UseNpgsql("Server=localhost");
return new BenchmarkContext(optionsBuilder.Options);
}
}
public class BenchmarkContext : DbContext
{
public DbSet<BenchmarkRun> BenchmarkRuns { get; set; } = default!;
public BenchmarkContext() { }
public BenchmarkContext(DbContextOptions<BenchmarkContext> options) : base(options) { }
}
public class BenchmarkRun
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public ulong Id { get; set; }
public string GitHash { get; set; } = string.Empty;
[Column(TypeName = "timestamptz")]
public DateTime RunDate { get; set; }
public string Name { get; set; } = string.Empty;
[Column(TypeName = "jsonb")]
public BenchmarkRunReport[] Reports { get; set; } = Array.Empty<BenchmarkRunReport>();
public static BenchmarkRun FromSummary(Summary summary, string gitHash)
{
return new BenchmarkRun
{
Reports = summary.Reports.Select(r => new BenchmarkRunReport
{
Parameters = r.BenchmarkCase.Parameters.Items.Select(p => new BenchmarkRunParameter
{
Name = p.Name,
Value = p.Value
}).ToArray(),
Statistics = r.ResultStatistics
}).ToArray(),
Name = summary.BenchmarksCases.First().FolderInfo,
RunDate = DateTime.UtcNow,
GitHash = gitHash
};
}
}
public class BenchmarkRunReport
{
public BenchmarkRunParameter[] Parameters { get; set; } = Array.Empty<BenchmarkRunParameter>();
public Statistics Statistics { get; set; } = default!;
}
public class BenchmarkRunParameter
{
public string Name { get; set; } = string.Empty;
public object Value { get; set; } = default!;
}

View File

@@ -0,0 +1,55 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Robust.Benchmarks.Exporters;
#nullable disable
namespace Robust.Benchmarks.Migrations
{
[DbContext(typeof(BenchmarkContext))]
[Migration("20220328231938_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Robust.Benchmarks.Exporters.BenchmarkRun", b =>
{
b.Property<decimal>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)");
b.Property<string>("GitHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<BenchmarkRunReport[]>("Reports")
.IsRequired()
.HasColumnType("jsonb");
b.Property<DateTime>("RunDate")
.HasColumnType("Date");
b.HasKey("Id");
b.ToTable("BenchmarkRuns");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Robust.Benchmarks.Exporters;
#nullable disable
namespace Robust.Benchmarks.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BenchmarkRuns",
columns: table => new
{
Id = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
GitHash = table.Column<string>(type: "text", nullable: false),
RunDate = table.Column<DateTime>(type: "Date", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Reports = table.Column<BenchmarkRunReport[]>(type: "jsonb", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BenchmarkRuns", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BenchmarkRuns");
}
}
}

View File

@@ -0,0 +1,53 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Robust.Benchmarks.Exporters;
#nullable disable
namespace Robust.Benchmarks.Migrations
{
[DbContext(typeof(BenchmarkContext))]
partial class BenchmarkContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Robust.Benchmarks.Exporters.BenchmarkRun", b =>
{
b.Property<decimal>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)");
b.Property<string>("GitHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<BenchmarkRunReport[]>("Reports")
.IsRequired()
.HasColumnType("jsonb");
b.Property<DateTime>("RunDate")
.HasColumnType("Date");
b.HasKey("Id");
b.ToTable("BenchmarkRuns");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -9,6 +9,9 @@ namespace Robust.Benchmarks.NumericsHelpers
[Params(32, 128)]
public int N { get; set; }
[Params(1,2)]
public int T { get; set; }
private float[] _inputA = default!;
private float[] _inputB = default!;
private float[] _output = default!;

View File

@@ -1,6 +1,8 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using System;
using Robust.Benchmarks.Configs;
using Robust.Benchmarks.Exporters;
namespace Robust.Benchmarks
{
@@ -16,7 +18,8 @@ namespace Robust.Benchmarks
Console.WriteLine("THE DEBUG BUILD IS ONLY GOOD FOR FIXING A CRASHING BENCHMARK\n");
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig());
#else
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
var config = Environment.GetEnvironmentVariable("ROBUST_BENCHMARKS_ENABLE_SQL") != null ? DefaultSQLConfig.Instance : null;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
#endif
}
}

View File

@@ -10,9 +10,15 @@
<ItemGroup>
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.0-rc.2" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
</Project>

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env pwsh
param([String]$name)
if ($name -eq "")
{
Write-Error "must specify migration name"
exit
}
dotnet ef migrations add --context BenchmarkContext -o Migrations $name

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
if [ -z "$1" ] ; then
echo "Must specify migration name"
exit 1
fi
dotnet ef migrations add --context BenchmarkContext -o Migrations "$1"

View File

@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.0.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="Pidgin" Version="2.5.0" />
</ItemGroup>

View File

@@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>

View File

@@ -619,6 +619,7 @@ namespace Robust.Client.Audio.Midi
var seqEv = (SequencerEvent) midiEvent;
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
_sequencer.SendAt(seqEv, time, absolute);
seqEv.Dispose();
}
public void Dispose()

View File

@@ -27,7 +27,6 @@ namespace Robust.Client.GameObjects
public override void Initialize()
{
SetupNetworking();
ReceivedComponentMessage += (_, compMsg) => DispatchComponentMessage(compMsg);
ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg);
base.Initialize();
@@ -52,9 +51,6 @@ namespace Robust.Client.GameObjects
public override IEntityNetworkManager EntityNetManager => this;
/// <inheritdoc />
public event EventHandler<NetworkComponentMessage>? ReceivedComponentMessage;
/// <inheritdoc />
public event EventHandler<object>? ReceivedSystemMessage;
@@ -105,26 +101,6 @@ namespace Robust.Client.GameObjects
throw new NotSupportedException();
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, EntityUid entity, IComponent component, ComponentMessage message)
{
var componentType = component.GetType();
var netId = ComponentFactory.GetRegistration(componentType).NetID;
if (!netId.HasValue)
throw new ArgumentException($"Component {componentType} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity;
msg.NetId = netId.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;
_networkManager.ClientSendMessage(msg);
}
private void HandleEntityNetworkMessage(MsgEntity message)
{
if (message.SourceTick <= _gameStateManager.CurServerTick)
@@ -143,10 +119,6 @@ namespace Robust.Client.GameObjects
{
switch (message.Type)
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message));
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());

View File

@@ -25,6 +25,8 @@ namespace Robust.Client.GameObjects
[Animatable]
Vector2 Scale { get; set; }
Box2 Bounds { get; }
/// <summary>
/// A rotation applied to all layers.
/// </summary>

View File

@@ -35,6 +35,6 @@ namespace Robust.Client.GameObjects
/// Calculate layer bounding box in sprite local-space coordinates.
/// </summary>
/// <returns>Bounding box in sprite local-space coordinates.</returns>
Box2 CalculateBoundingBox(Angle worldAngle);
Box2 CalculateBoundingBox();
}
}

View File

@@ -19,6 +19,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using TerraFX.Interop.Windows;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using RSIDirection = Robust.Client.Graphics.RSI.State.Direction;
@@ -217,6 +218,10 @@ namespace Robust.Client.GameObjects
private bool _containerOccluded;
private Box2 _bounds;
public Box2 Bounds => _bounds;
[ViewVariables(VVAccess.ReadWrite)]
public bool TreeUpdateQueued { get; set; }
@@ -299,6 +304,7 @@ namespace Robust.Client.GameObjects
{
//deep copying things to avoid entanglement
_baseRsi = other._baseRsi;
_bounds = other._bounds;
_visible = other._visible;
_layerMapShared = other._layerMapShared;
color = other.color;
@@ -548,6 +554,7 @@ namespace Robust.Client.GameObjects
index = Layers.Count - 1;
}
RebuildBounds();
QueueUpdateIsInert();
return index;
}
@@ -575,6 +582,7 @@ namespace Robust.Client.GameObjects
}
}
RebuildBounds();
QueueUpdateIsInert();
}
@@ -590,6 +598,15 @@ namespace Robust.Client.GameObjects
RemoveLayer(layer);
}
private void RebuildBounds()
{
_bounds = new Box2();
foreach (var layer in Layers.Where(layer => layer.Visible))
{
_bounds = _bounds.Union(layer.CalculateBoundingBox());
}
}
/// <summary>
/// Fills in a layer's values using some <see cref="PrototypeLayerData"/>.
/// </summary>
@@ -713,6 +730,8 @@ namespace Robust.Client.GameObjects
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
RebuildBounds();
}
public void LayerSetData(object layerKey, PrototypeLayerData data)
@@ -799,6 +818,8 @@ namespace Robust.Client.GameObjects
default:
throw new NotImplementedException();
}
RebuildBounds();
}
public void LayerSetSprite(object layerKey, SpriteSpecifier specifier)
@@ -824,6 +845,7 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.SetTexture(texture);
RebuildBounds();
}
public void LayerSetTexture(object layerKey, Texture texture)
@@ -889,6 +911,7 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.SetState(stateId);
RebuildBounds();
}
public void LayerSetState(object layerKey, RSI.StateId stateId)
@@ -936,6 +959,8 @@ namespace Robust.Client.GameObjects
theLayer.Texture = null;
}
}
RebuildBounds();
}
public void LayerSetState(object layerKey, RSI.StateId stateId, RSI rsi)
@@ -993,6 +1018,7 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.SetRsi(rsi);
RebuildBounds();
}
public void LayerSetRSI(object layerKey, RSI rsi)
@@ -1050,6 +1076,7 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.Scale = scale;
RebuildBounds();
}
public void LayerSetScale(object layerKey, Vector2 scale)
@@ -1076,6 +1103,7 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.Rotation = rotation;
RebuildBounds();
}
public void LayerSetRotation(object layerKey, Angle rotation)
@@ -1125,6 +1153,8 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.Color = color;
RebuildBounds();
}
public void LayerSetColor(object layerKey, Color color)
@@ -1150,6 +1180,8 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.DirOffset = offset;
RebuildBounds();
}
public void LayerSetDirOffset(object layerKey, DirectionOffset offset)
@@ -1201,6 +1233,8 @@ namespace Robust.Client.GameObjects
}
Layers[layer].SetAutoAnimated(autoAnimated);
RebuildBounds();
}
public void LayerSetAutoAnimated(object layerKey, bool autoAnimated)
@@ -1226,6 +1260,8 @@ namespace Robust.Client.GameObjects
}
Layers[layer].Offset = layerOffset;
RebuildBounds();
}
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
@@ -1536,24 +1572,10 @@ namespace Robust.Client.GameObjects
eye ??= eyeManager.CurrentEye;
// Need relative angle on screen for determining the sprite rsi direction.
Angle relativeRotation = NoRotation
? Angle.Zero
: worldRotation + eye.Rotation;
// we need to calculate bounding box taking into account all nested layers
// because layers can have offsets, scale or rotation, we need to calculate a new BB
// based on lowest bottomLeft and highest topRight points from all layers
var box = Layers[0].CalculateBoundingBox(relativeRotation);
for (int i = 1; i < Layers.Count; i++)
{
var layer = Layers[i];
if (!layer.Visible) continue;
var layerBB = layer.CalculateBoundingBox(relativeRotation);
box = box.Union(layerBB);
}
var box = Bounds;
// Next, what we do is take the box2 and apply the sprite's transform, and then the entity's transform. We
// could do this via Matrix3.TransformBox, but that only yields bounding boxes. So instead we manually
@@ -1849,6 +1871,7 @@ namespace Robust.Client.GameObjects
Visible = value;
_parent.QueueUpdateIsInert();
_parent.RebuildBounds();
}
public void SetRsi(RSI? rsi)
@@ -1943,39 +1966,40 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox(Angle angle)
public Box2 CalculateBoundingBox()
{
// Other than some special cases for simple layers, this will basically just apply the matrix obtained
// via GetLayerDrawMatrix() to this layer's bounding box.
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
// If the parent has locked rotation and we don't have any rotation,
// we can take the quick path of just making a box the size of the texture.
if (_parent.NoRotation && _rotation != 0)
{
return Box2.CenteredAround(Offset, textureSize).Scale(_scale);
}
var rsiState = GetActualState();
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
? RSIDirection.South
: angle.ToRsiDirection(rsiState.Directions);
var longestSide = MathF.Max(textureSize.X, textureSize.Y);
var longestRotatedSide = Math.Max(longestSide, (textureSize.X + textureSize.Y) / MathF.Sqrt(2));
// special case for simple layers. The vast majority of layers are like this.
if (_rotation == Angle.Zero)
// Build the bounding box based on how many directions the sprite has
var box = (_rotation != 0, rsiState) switch
{
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
// If this layer has any form of arbitrary rotation, return a bounding box big enough to cover
// any possible rotation.
(true, _) => Box2.CenteredAround(Offset, new Vector2(longestRotatedSide, longestRotatedSide)),
// this switch block is basically an explicit version of the `rsiDirectionMatrix` in `GetLayerDrawMatrix()`.
var box = dir switch
{
// No rotation:
RSIDirection.South or RSIDirection.North => Box2.CenteredAround(Offset, textureSize),
// rotate 90 degrees:
RSIDirection.East or RSIDirection.West => Box2.CenteredAround(Offset, (textureSize.Y, textureSize.X)),
// rotated 45 degrees (any 45 degree rotated rectangle has a square bounding box with sides of length (x+y)/sqrt(2) )
_ => Box2.CenteredAround(Offset, Vector2.One * (textureSize.X + textureSize.Y) / MathF.Sqrt(2))
};
return _scale == Vector2.One ? box : box.Scale(_scale);
}
// Welp we have some non-zero _rotation, so lets just apply the generalized layer transform and get the bounding box from where;
GetLayerDrawMatrix(dir, out var layerDrawMatrix);
return layerDrawMatrix.TransformBox(Box2.CentredAroundZero(PixelSize / EyeManager.PixelsPerMeter));
// Otherwise...
// If we have only one direction or an invalid RSI state, create a simple bounding box with the size of the texture.
(_, {Directions: RSI.State.DirectionType.Dir1} or null) => Box2.CenteredAround(Offset, textureSize),
// If we have four cardinal directions, take the longest side of our texture and square it, then turn that into our bounding box.
// This accounts for all possible rotations.
(_, {Directions: RSI.State.DirectionType.Dir4}) => Box2.CenteredAround(Offset, new Vector2(longestSide, longestSide)),
// If we have eight directions, find the maximum length of the texture (accounting for rotation), then square it to make
// our bounding box.
(_, {Directions: RSI.State.DirectionType.Dir8}) => Box2.CenteredAround(Offset, new Vector2(longestRotatedSide, longestRotatedSide)),
};
return _scale == Vector2.One ? box : box.Scale(_scale);
}
internal RSI.State? GetActualState()
@@ -1999,15 +2023,27 @@ namespace Robust.Client.GameObjects
/// </summary>
public void GetLayerDrawMatrix(RSIDirection dir, out Matrix3 layerDrawMatrix)
{
if (_parent.NoRotation)
if (_parent.NoRotation || dir == RSIDirection.South)
layerDrawMatrix = LocalMatrix;
else
{
var rsiDirectionMatrix = Matrix3.CreateTransform(Vector2.Zero, -dir.Convert().ToAngle());
Matrix3.Multiply(ref rsiDirectionMatrix, ref LocalMatrix, out layerDrawMatrix);
Matrix3.Multiply(ref _rsiDirectionMatrices[(int)dir], ref LocalMatrix, out layerDrawMatrix);
}
}
private static Matrix3[] _rsiDirectionMatrices = new Matrix3[]
{
// array order chosen such that this array can be indexed by casing an RSI direction to an int
Matrix3.Identity, // should probably just avoid matrix multiplication altogether if the direction is south.
Matrix3.CreateRotation(-Direction.North.ToAngle()),
Matrix3.CreateRotation(-Direction.East.ToAngle()),
Matrix3.CreateRotation(-Direction.West.ToAngle()),
Matrix3.CreateRotation(-Direction.SouthEast.ToAngle()),
Matrix3.CreateRotation(-Direction.SouthWest.ToAngle()),
Matrix3.CreateRotation(-Direction.NorthEast.ToAngle()),
Matrix3.CreateRotation(-Direction.NorthWest.ToAngle())
};
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
{
if (!Visible)

View File

@@ -342,7 +342,6 @@ namespace Robust.Client.GameObjects
var map = _owner.eyeManager.CurrentMap;
var worldHandle = args.WorldHandle;
ShaderInstance? currentShader = null;
if (_playerManager.LocalPlayer?.ControlledEntity is not {} playerEnt)
return;
@@ -362,13 +361,8 @@ namespace Robust.Client.GameObjects
continue;
}
var newShader = effect.Shaded ? null : _unshadedShader;
if (newShader != currentShader)
{
worldHandle.UseShader(newShader);
currentShader = newShader;
}
if (!effect.Shaded)
worldHandle.UseShader(_unshadedShader);
// TODO: Should be doing matrix transformations
var effectSprite = effect.EffectSprite;
@@ -377,6 +371,9 @@ namespace Robust.Client.GameObjects
(attachedXform?.Coordinates ?? effect.Coordinates)
.Offset(effect.AttachedOffset);
// If we've never seen the entity before then can't resolve coordinates.
if (!coordinates.IsValid(_entityManager)) continue;
// ???
var rotation = attachedXform?.WorldRotation ?? _entityManager.GetComponent<TransformComponent>(coordinates.EntityId).WorldRotation;
@@ -386,6 +383,9 @@ namespace Robust.Client.GameObjects
var rotatedBox = new Box2Rotated(effectArea, effect.Rotation + rotation, effectOrigin);
worldHandle.DrawTextureRect(effectSprite, rotatedBox, ToColor(effect.Color));
if (!effect.Shaded)
worldHandle.UseShader(null);
}
}
}

View File

@@ -77,12 +77,10 @@ namespace Robust.Client.GameObjects
// Due to how recursion works, this must be done.
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
SubscribeLocalEvent<SpriteComponent, SpriteUpdateEvent>(HandleSpriteUpdate);
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
@@ -156,10 +154,6 @@ namespace Robust.Client.GameObjects
// Otherwise these will still have their past MapId and that's all we need..
#region SpriteHandlers
private void SpriteMapChanged(EntityUid uid, SpriteComponent component, EntMapIdChangedMessage args)
{
QueueSpriteUpdate(component);
}
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, ref EntParentChangedMessage args)
{
@@ -189,10 +183,6 @@ namespace Robust.Client.GameObjects
#endregion
#region LightHandlers
private void LightMapChanged(EntityUid uid, PointLightComponent component, EntMapIdChangedMessage args)
{
QueueLightUpdate(component);
}
private void LightParentChanged(EntityUid uid, PointLightComponent component, ref EntParentChangedMessage args)
{

View File

@@ -347,9 +347,7 @@ namespace Robust.Client.GameStates
// Check log level first to avoid the string alloc.
if (_sawmill.Level <= LogLevel.Debug)
{
_sawmill.Debug($"Entity {entity} was made dirty.");
}
if (!_processor.TryGetLastServerStates(entity, out var last))
{
@@ -367,7 +365,9 @@ namespace Robust.Client.GameStates
continue;
}
_sawmill.Debug($" And also its component {comp.GetType()}");
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" And also its component {comp.GetType()}");
// TODO: Handle interpolation.
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
@@ -421,7 +421,6 @@ namespace Robust.Client.GameStates
var createdEntities = ApplyEntityStates(curState.EntityStates.Span, curState.EntityDeletions.Span,
nextState != null ? nextState.EntityStates.Span : default);
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<PlayerState>());
_mapManager.ApplyGameStatePost(curState.MapData);
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
return createdEntities;

View File

@@ -15,6 +15,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Audio;
using Robust.Shared.Log;
using Vector2 = Robust.Shared.Maths.Vector2;
using Robust.Shared.Utility;
namespace Robust.Client.Graphics.Audio
{
@@ -193,8 +194,10 @@ namespace Robust.Client.Graphics.Audio
{
_didPositionWarning = true;
Logger.WarningS("clyde.oal",
"Attempting to set position on audio source with multiple audio channels! Stream: '{0}'",
"Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
_sourceStream.Name);
// warning isn't enough, people just ignore it :(
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
}
#endif

View File

@@ -1,7 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -101,13 +99,18 @@ namespace Robust.Client.Graphics.Clyde
if (tile.IsEmpty)
continue;
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.TypeId);
if (regionMaybe == null)
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
Box2 region;
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
{
continue;
region = _tileDefinitionManager.ErrorTileRegion;
}
else
{
region = regionMaybe[tile.Variant];
}
var region = regionMaybe.Value;
var gx = x + cScaled.X;
var gy = y + cScaled.Y;

View File

@@ -35,6 +35,8 @@ namespace Robust.Client.Graphics.Clyde
/// </summary>
public static float PostShadeScale = 1.25f;
private List<Overlay> _overlays = new();
public void Render()
{
CheckTransferringScreenshots();
@@ -152,19 +154,19 @@ namespace Robust.Client.Graphics.Clyde
private List<Overlay> GetOverlaysForSpace(OverlaySpace space)
{
var list = new List<Overlay>();
_overlays.Clear();
foreach (var overlay in _overlayManager.AllOverlays)
{
if ((overlay.Space & space) != 0)
{
list.Add(overlay);
_overlays.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
_overlays.Sort(OverlayComparer.Instance);
return list;
return _overlays;
}
private ClydeTexture? ScreenBufferTexture;

View File

@@ -861,7 +861,6 @@ namespace Robust.Client.Graphics.Clyde
// 3D geometry used during depth projection.
// 2D mask geometry used to apply wall bleed.
// TODO: This code probably does not work correctly with rotated camera.
// TODO: Yes this function throws and index exception if you reach maxOccluders.
const int maxOccluders = 2048;

View File

@@ -437,6 +437,13 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.WindowSetVisible(reg, visible);
}
public void RunOnWindowThread(Action a)
{
DebugTools.AssertNotNull(_windowing);
_windowing!.RunOnWindowThread(a);
}
private abstract class WindowReg
{
public bool IsDisposed;

View File

@@ -251,6 +251,11 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void RunOnWindowThread(Action action)
{
action();
}
private sealed class DummyCursor : ICursor
{
public void Dispose()

View File

@@ -118,6 +118,10 @@ namespace Robust.Client.Graphics.Clyde
case CmdWinCursorSet cmd:
WinThreadWinCursorSet(cmd);
break;
case CmdRunAction cmd:
cmd.Action();
break;
}
}
@@ -169,6 +173,11 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void RunOnWindowThread(Action action)
{
SendCmd(new CmdRunAction(action));
}
private abstract record CmdBase;
private sealed record CmdTerminate : CmdBase;
@@ -245,6 +254,10 @@ namespace Robust.Client.Graphics.Clyde
private sealed record CmdCursorDestroy(
ClydeHandle Cursor
) : CmdBase;
private sealed record CmdRunAction(
Action Action
) : CmdBase;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Robust.Client.Input;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
@@ -58,6 +59,9 @@ namespace Robust.Client.Graphics.Clyde
void GLMakeContextCurrent(WindowReg? reg);
void GLSwapInterval(int interval);
unsafe void* GLGetProcAddress(string procName);
// Misc
void RunOnWindowThread(Action a);
}
}
}

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Utility;
using SharpFont;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using TerraFX.Interop.Windows;
namespace Robust.Client.Graphics
{
@@ -58,6 +59,14 @@ namespace Robust.Client.Graphics
return instance;
}
public void ClearFontCache()
{
foreach (var fontInstance in _loadedInstances)
{
fontInstance.Value.ClearSizeData();
}
}
private ScaledFontData _generateScaledDatum(FontInstanceHandle instance, float scale)
{
var ftFace = instance.FaceHandle.Face;
@@ -246,6 +255,18 @@ namespace Robust.Client.Graphics
FaceHandle = faceHandle;
}
public void ClearSizeData()
{
foreach (var scaleData in _scaledData)
{
foreach (var ownedTexture in scaleData.Value.AtlasTextures)
{
ownedTexture.Dispose();
}
}
_scaledData.Clear();
}
public Texture? GetCharTexture(Rune codePoint, float scale)
{
var glyph = GetGlyph(codePoint);

View File

@@ -63,5 +63,7 @@ namespace Robust.Client.Graphics
uint? GetX11WindowId();
void RegisterGridEcsEvents();
void RunOnWindowThread(Action action);
}
}

View File

@@ -5,9 +5,8 @@ namespace Robust.Client.Graphics
{
public interface IFontManager
{
public void ClearFontCache();
}
internal interface IFontManagerInternal : IFontManager
{
IFontFaceHandle Load(Stream stream);

View File

@@ -152,9 +152,12 @@ namespace Robust.Client.Graphics
/// <summary>
/// Specifies a direction in an RSI state.
/// </summary>
/// <remarks>
/// Value of the enum here matches the index used to store it in the icons array. If this ever changes, then
/// <see cref="GameObjects.SpriteComponent.Layer._rsiDirectionMatrices"/> also needs to be updated.
/// </remarks>
public enum Direction : byte
{
// Value of the enum here matches the index used to store it in the icons array.
South = 0,
North = 1,
East = 2,

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -22,16 +22,18 @@ namespace Robust.Client.Map
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2> _tileRegions = new();
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
public Box2 ErrorTileRegion { get; private set; }
/// <inheritdoc />
public Box2? TileAtlasRegion(Tile tile)
public Box2[]? TileAtlasRegion(Tile tile)
{
return TileAtlasRegion(tile.TypeId);
}
/// <inheritdoc />
public Box2? TileAtlasRegion(ushort tileType)
public Box2[]? TileAtlasRegion(ushort tileType)
{
if (_tileRegions.TryGetValue(tileType, out var region))
{
@@ -58,39 +60,79 @@ namespace Robust.Client.Map
const int tileSize = EyeManager.PixelsPerMeter;
var dimensionX = (int) Math.Ceiling(Math.Sqrt(defList.Count));
var dimensionY = (int) Math.Ceiling((float) defList.Count / dimensionX);
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
var sheet = new Image<Rgba32>(dimensionX * tileSize, dimensionY * tileSize);
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
for (var i = 0; i < defList.Count; i++)
var imgWidth = dimensionX * tileSize;
var imgHeight = dimensionY * tileSize;
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
// Add in the missing tile texture sprite as tile texture 0.
{
var def = defList[i];
var column = i % dimensionX;
var row = i / dimensionX;
var w = (float) sheet.Width;
var h = (float) sheet.Height;
ErrorTileRegion = Box2.FromDimensions(
0, (h - EyeManager.PixelsPerMeter) / h,
tileSize / w, tileSize / h);
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead("/Textures/noTile.png"))
{
image = Image.Load<Rgba32>(stream);
}
image.Blit(new UIBox2i(0, 0, tileSize, tileSize), sheet, Vector2i.Zero);
}
if (imgWidth >= 2048 || imgHeight >= 2048)
{
// Sanity warning, some machines don't have textures larger than this and need multiple atlases.
Logger.WarningS("clyde",
$"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
}
var column = 1;
var row = 0;
foreach (var def in defList)
{
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
{
image = Image.Load<Rgba32>(stream);
}
if (image.Width != tileSize || image.Height != tileSize)
if (image.Width != (tileSize * def.Variants) || image.Height != tileSize)
{
throw new NotSupportedException("Unable to use tiles with a dimension other than 32x32.");
throw new NotSupportedException(
$"Unable to load {new ResourcePath(def.Path) / $"{def.SpriteName}.png"}, due to being unable to use tile texture with a dimension other than {tileSize}x({tileSize} * Variants).");
}
var point = new Vector2i(column * tileSize, row * tileSize);
var regionList = new Box2[def.Variants];
image.Blit(new UIBox2i(0, 0, image.Width, image.Height), sheet, point);
for (var j = 0; j < def.Variants; j++)
{
var point = new Vector2i(column * tileSize, row * tileSize);
var w = (float) sheet.Width;
var h = (float) sheet.Height;
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
image.Blit(box, sheet, point);
_tileRegions.Add(def.TileId,
Box2.FromDimensions(
var w = (float) sheet.Width;
var h = (float) sheet.Height;
regionList[j] = Box2.FromDimensions(
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
tileSize / w, tileSize / h));
tileSize / w, tileSize / h);
column++;
if (column >= dimensionX)
{
column = 0;
row++;
}
}
_tileRegions.Add(def.TileId, regionList);
}
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -9,6 +10,8 @@ namespace Robust.Client.Map
/// </summary>
internal interface IClydeTileDefinitionManager : ITileDefinitionManager
{
Box2 ErrorTileRegion { get; }
/// <summary>
/// The texture atlas containing all the tiles.
/// </summary>
@@ -18,12 +21,12 @@ namespace Robust.Client.Map
/// Gets the region inside the texture atlas to use to draw a tile.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2? TileAtlasRegion(Tile tile);
Box2[]? TileAtlasRegion(Tile tile);
/// <summary>
/// Gets the region inside the texture atlas to use to draw a tile type.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2? TileAtlasRegion(ushort tileType);
Box2[]? TileAtlasRegion(ushort tileType);
}
}

View File

@@ -13,8 +13,8 @@
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.3" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.2" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
@@ -25,9 +25,9 @@
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
</ItemGroup>
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
</ItemGroup>

View File

@@ -1,4 +1,6 @@
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
{
@@ -9,6 +11,30 @@ namespace Robust.Client.UserInterface.Controls
Window = window;
}
/// <summary>
/// Enable the UI autoscale system, this will scale down the UI for lower resolutions
/// </summary>
[ViewVariables]
public bool AutoScale { get; set; } = false;
/// <summary>
/// Minimum resolution to start clamping autoscale to 1
/// </summary>
[ViewVariables]
public Vector2i AutoScaleUpperCutoff { get; set; } = new Vector2i(1080, 720);
/// <summary>
/// Maximum resolution to start clamping autos scale to autoscale minimum
/// </summary>
[ViewVariables]
public Vector2i AutoScaleLowerCutoff { get; set; } = new Vector2i(520, 520);
/// <summary>
/// The minimum ui scale value that autoscale will scale to
/// </summary>
[ViewVariables]
public float AutoScaleMinimum { get; set; } = 0.5f;
public override float UIScale => UIScaleSet;
internal float UIScaleSet { get; set; }
public override IClydeWindow Window { get; }

View File

@@ -102,13 +102,18 @@ Mouse Pos:
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
var playerCoordinates = entityTransform.Coordinates;
var playerRotation = entityTransform.WorldRotation;
Angle gridRotation = _mapManager.TryGetGrid(entityTransform.GridID, out var grid) ? grid.WorldRotation : Angle.Zero;
stringBuilder.AppendFormat(@" Screen: {0}
{1}
{2}
EntId: {3}
GridID: {4}", playerScreen, playerWorldOffset, playerCoordinates, entityTransform.Owner,
entityTransform.GridID);
Rotation: {3:F2}°
EntId: {4}
GridID: {5}
Grid Rotation: {6:F2}°", playerScreen, playerWorldOffset, playerCoordinates, playerRotation.Degrees, entityTransform.Owner,
entityTransform.GridID, gridRotation.Degrees);
}
if (controlHovered != null)

View File

@@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -164,12 +165,11 @@ namespace Robust.Client.UserInterface
{
if (OperatingSystem.IsMacOS())
{
// macOS seems pretty annoying about having the file dialog opened from the main thread.
// So we are forced to execute this synchronously on the main thread.
// Also I'm calling RunOnMainThread here to provide safety in case this is ran from a different thread.
// nativefiledialog doesn't provide any form of async API, so this WILL lock up the client.
// macOS seems pretty annoying about having the file dialog opened from the main windowing thread.
// So we are forced to execute this synchronously on the main windowing thread.
// nativefiledialog doesn't provide any form of async API, so this WILL lock up half the client.
var tcs = new TaskCompletionSource<string?>();
_taskManager.RunOnMainThread(() => tcs.SetResult(action()));
_clyde.RunOnWindowThread(() => tcs.SetResult(action()));
return tcs.Task;
}
@@ -388,4 +388,17 @@ namespace Robust.Client.UserInterface
SW_NFD_CANCEL,
}
}
public sealed class OpenFileCommand : IConsoleCommand
{
public string Command => "testopenfile";
public string Description => "";
public string Help => "";
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
var stream = await IoCManager.Resolve<IFileDialogManager>().OpenFile();
stream?.Dispose();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Client.Graphics;
@@ -23,6 +23,7 @@ namespace Robust.Client.UserInterface
internal sealed class UserInterfaceManager : IUserInterfaceManagerInternal
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IFontManager _fontManager = default!;
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -371,16 +372,21 @@ namespace Robust.Client.UserInterface
}
}
ReleaseKeyboardFocus();
if (hit == null)
{
ReleaseKeyboardFocus();
hitData = null;
return false;
}
var (control, rel) = hit.Value;
if (control != KeyboardFocused)
{
ReleaseKeyboardFocus();
}
ControlFocused = control;
if (ControlFocused.CanKeyboardFocus && ControlFocused.KeyboardFocusOnClick)
@@ -951,14 +957,37 @@ namespace Robust.Client.UserInterface
private void WindowContentScaleChanged(WindowContentScaleEventArgs args)
{
if (_windowsToRoot.TryGetValue(args.Window.Id, out var root))
{
UpdateUIScale(root);
_fontManager.ClearFontCache();
}
}
private float CalculateAutoScale(WindowRoot root)
{
//Grab the OS UIScale or the value set through CVAR debug
var osScale = _configurationManager.GetCVar(CVars.DisplayUIScale);
osScale = osScale == 0f ? root.Window.ContentScale.X : osScale;
var windowSize = root.Window.RenderTarget.Size;
//Only run autoscale if it is enabled, otherwise default to just use OS UIScale
if (!root.AutoScale && (windowSize.X <= 0 || windowSize.Y <= 0)) return osScale;
var maxScaleRes = root.AutoScaleUpperCutoff;
var minScaleRes = root.AutoScaleLowerCutoff;
var autoScaleMin = root.AutoScaleMinimum;
float scaleRatioX;
float scaleRatioY;
//Calculate the scale ratios and clamp it between the maximums and minimums
scaleRatioX = Math.Clamp(((float) windowSize.X - minScaleRes.X) / (maxScaleRes.X - minScaleRes.X) * osScale, autoScaleMin, osScale);
scaleRatioY = Math.Clamp(((float) windowSize.Y - minScaleRes.Y) / (maxScaleRes.Y - minScaleRes.Y) * osScale, autoScaleMin, osScale);
//Take the smallest UIScale value and use it for UI scaling
return Math.Min(scaleRatioX, scaleRatioY);
}
private void UpdateUIScale(WindowRoot root)
{
var newVal = _configurationManager.GetCVar(CVars.DisplayUIScale);
root.UIScaleSet = newVal == 0f ? root.Window.ContentScale.X : newVal;
root.UIScaleSet = CalculateAutoScale(root);
_propagateUIScaleChanged(root);
root.InvalidateMeasure();
}
@@ -977,7 +1006,7 @@ namespace Robust.Client.UserInterface
{
if (!_windowsToRoot.TryGetValue(windowResizedEventArgs.Window.Id, out var root))
return;
UpdateUIScale(root);
root.InvalidateMeasure();
}

View File

@@ -0,0 +1 @@
# Add your introductions here!

View File

@@ -0,0 +1,2 @@
- name: Example-article
href: example-article.md

214
Robust.Docfx/docfx.json Normal file
View File

@@ -0,0 +1,214 @@
{
"metadata": [
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.UnitTesting"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.UnitTesting"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Shared"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Shared"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Shared.Maths"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Shared.Maths"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Shared.Scripting"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Shared.Scripting"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Client"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Client"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Client.WebView"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Client.WebView"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Server"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Server"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.LoaderApi"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.LoaderApi/Robust.LoaderApi"
}
],
"build": {
"content": [
{
"files": [
"api/**/**.yml"
]
},
{
"files": [
"articles/**.md",
"articles/**/toc.yml",
"toc.yml",
"*.md"
]
}
],
"resource": [
{
"files": [
"images/**",
"favicon.ico",
"icon.svg"
]
}
],
"overwrite": [
{
"files": [
"apidoc/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"dest": "_robust-site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"default",
"templates/darkfx"
],
"postProcessors": [],
"markdownEngineName": "markdig",
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false
}
}

6
Robust.Docfx/index.md Normal file
View File

@@ -0,0 +1,6 @@
# RobustToolbox DocFX
![](https://i.imgur.com/Sv3Swxg.png)
## Welcome to the RobustToolbox DocFX instance.
### Click one of the tabs above to see documentation for that particular project/namespace

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Steffen Wilke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.

View File

@@ -0,0 +1,40 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div class="hidden-sm col-md-2" role="complementary">
<div class="sideaffix">
{{^_disableContribution}}
<div class="contribution">
<ul class="nav">
{{#docurl}}
<li>
<a href="{{docurl}}" class="contribution-link">{{__global.improveThisDoc}}</a>
</li>
{{/docurl}}
{{#sourceurl}}
<li>
<a href="{{sourceurl}}" class="contribution-link">{{__global.viewSource}}</a>
</li>
{{/sourceurl}}
</ul>
</div>
{{/_disableContribution}}
<div class="toggle-mode">
<div class="icon">
<i aria-hidden="true">☀</i>
</div>
<label class="switch">
<input type="checkbox" id="switch-style">
<span class="slider round"></span>
</label>
<div class="icon">
<i aria-hidden="true">☾</i>
</div>
</div>
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<h5>{{__global.inThisArticle}}</h5>
<div></div>
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
</nav>
</div>
</div>

View File

@@ -0,0 +1,108 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<h1 id="{{id}}" data-uid="{{uid}}" class="text-break">{{>partials/title}}</h1>
<div class="markdown level0 summary">{{{summary}}}</div>
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
{{#inClass}}
<div class="inheritance">
<h5>{{__global.inheritance}}</h5>
{{#inheritance}}
<div class="level{{index}}">{{{specName.0.value}}}</div>
{{/inheritance}}
<div class="level{{level}}"><span class="xref">{{name.0.value}}</span></div>
{{#derivedClasses}}
<div class="level{{index}}">{{{specName.0.value}}}</div>
{{/derivedClasses}}
</div>
{{/inClass}}
{{#implements.0}}
<div classs="implements">
<h5>{{__global.implements}}</h5>
{{/implements.0}}
{{#implements}}
<div>{{{specName.0.value}}}</div>
{{/implements}}
{{#implements.0}}
</div>
{{/implements.0}}
{{#inheritedMembers.0}}
<div class="inheritedMembers">
</div>
{{/inheritedMembers.0}}
<h6><strong>{{__global.namespace}}</strong>: {{{namespace.specName.0.value}}}</h6>
<h6><strong>{{__global.assembly}}</strong>: {{assemblies.0}}.dll</h6>
<h5 id="{{id}}_syntax">{{__global.syntax}}</h5>
<div class="codewrapper">
<pre><code class="lang-{{_lang}} hljs">{{syntax.content.0.value}}</code></pre>
</div>
{{#syntax.parameters.0}}
<h5 class="parameters">{{__global.parameters}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.name}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
{{/syntax.parameters.0}}
{{#syntax.parameters}}
<tr>
<td>{{{type.specName.0.value}}}</td>
<td><span class="parametername">{{{id}}}</span></td>
<td>{{{description}}}</td>
</tr>
{{/syntax.parameters}}
{{#syntax.parameters.0}}
</tbody>
</table>
{{/syntax.parameters.0}}
{{#syntax.return}}
<h5 class="returns">{{__global.returns}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{{type.specName.0.value}}}</td>
<td>{{{description}}}</td>
</tr>
</tbody>
</table>
{{/syntax.return}}
{{#syntax.typeParameters.0}}
<h5 class="typeParameters">{{__global.typeParameters}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.name}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
{{/syntax.typeParameters.0}}
{{#syntax.typeParameters}}
<tr>
<td><span class="parametername">{{{id}}}</span></td>
<td>{{{description}}}</td>
</tr>
{{/syntax.typeParameters}}
{{#syntax.typeParameters.0}}
</tbody>
</table>
{{/syntax.typeParameters.0}}
{{#remarks}}
<h5 id="{{id}}_remarks"><strong>{{__global.remarks}}</strong></h5>
<div class="markdown level0 remarks">{{{remarks}}}</div>
{{/remarks}}
{{#example.0}}
<h5 id="{{id}}_examples"><strong>{{__global.examples}}</strong></h5>
{{/example.0}}
{{#example}}
{{{.}}}
{{/example}}

View File

@@ -0,0 +1,29 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<footer>
<div class="grad-bottom"></div>
<div class="footer">
<div class="container">
<span class="pull-right">
<a href="#top">Back to top</a>
</span>
<div class="pull-left">
{{{_appFooter}}}
{{^_appFooter}}<span>Generated by <strong>DocFX</strong></span>{{/_appFooter}}
</div>
<div class="toggle-mode pull-right visible-sm visible-xs">
<div class="icon">
<i aria-hidden="true">☀</i>
</div>
<label class="switch">
<input type="checkbox" id="switch-style-m">
<span class="slider round"></span>
</label>
<div class="icon">
<i aria-hidden="true">☾</i>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="{{_rel}}styles/toggle-theme.js"></script>
</footer>

View File

@@ -0,0 +1,20 @@
{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
<meta name="generator" content="docfx {{_docfxVersion}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<meta property="docfx:navrel" content="{{_navRel}}">
<meta property="docfx:tocrel" content="{{_tocRel}}">
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
</head>

View File

@@ -0,0 +1,470 @@
:root, body.dark-theme {
--color-foreground: #ccd5dc;
--color-navbar: #66666d;
--color-breadcrumb: #999;
--color-underline: #ddd;
--color-toc-hover: #fff;
--color-background: #2d2d30;
--color-background-subnav: #333337;
--color-background-dark: #1e1e1e;
--color-background-table-alt: #212123;
--color-background-quote: #69696e;
}
body.light-theme {
--color-foreground: #171717;
--color-breadcrumb: #4a4a4a;
--color-toc-hover: #4c4c4c;
--color-background: #ffffff;
--color-background-subnav: #f5f5f5;
--color-background-dark: #ddd;
--color-background-table-alt: #f9f9f9;
}
body {
color: var(--color-foreground);
line-height: 1.5;
font-size: 14px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
word-wrap: break-word;
background-color: var(--color-background);
}
.btn.focus, .btn:focus, .btn:hover {
color: var(--color-foreground);
}
h1 {
font-weight: 600;
font-size: 32px;
}
h2 {
font-weight: 600;
font-size: 24px;
line-height: 1.8;
}
h3 {
font-weight: 600;
font-size: 20px;
line-height: 1.8;
}
h5 {
font-size: 14px;
padding: 10px 0px;
}
article h1, article h2, article h3, article h4 {
margin-top: 35px;
margin-bottom: 15px;
}
article h4 {
padding-bottom: 8px;
border-bottom: 2px solid var(--color-underline);
}
.navbar-brand>img {
color: var(--color-background);
}
.navbar {
border: none;
}
.subnav {
border-top: 1px solid var(--color-underline);
background-color: var(--color-background-subnav);
}
.sidenav, .fixed_header, .toc {
background-color: var(--color-background);
}
.navbar-inverse {
background-color: var(--color-background-dark);
z-index: 100;
}
.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text {
color: var(--color-navbar);
background-color: var(--color-background-dark);
border-bottom: 3px solid transparent;
padding-bottom: 12px;
}
.navbar-inverse .navbar-nav>li>a:focus, .navbar-inverse .navbar-nav>li>a:hover {
color: var(--color-foreground);
background-color: var(--color-background-dark);
border-bottom: 3px solid var(--color-background-subnav);
transition: all ease 0.25s;
}
.navbar-inverse .navbar-nav>.active>a, .navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover {
color: var(--color-foreground);
background-color: var(--color-background-dark);
border-bottom: 3px solid var(--color-foreground);
transition: all ease 0.25s;
}
.navbar-form .form-control {
border: none;
border-radius: 0;
}
.light-theme .navbar-brand svg {
filter: brightness(20%);
}
.toc .level1>li {
font-weight: 400;
}
.toc .nav>li>a {
color: var(--color-foreground);
}
.sidefilter {
background-color: var(--color-background);
border-left: none;
border-right: none;
}
.sidefilter {
background-color: var(--color-background);
border-left: none;
border-right: none;
}
.toc-filter {
padding: 10px;
margin: 0;
background-color: var(--color-background);
}
.toc-filter>input {
border: none;
border-radius: unset;
background-color: var(--color-background-subnav);
padding: 5px 0 5px 20px;
font-size: 90%
}
.toc-filter>.clear-icon {
position: absolute;
top: 17px;
right: 15px;
}
.toc-filter>input:focus {
color: var(--color-foreground);
transition: all ease 0.25s;
}
.toc-filter>.filter-icon {
display: none;
}
.sidetoc>.toc {
background-color: var(--color-background);
overflow-x: hidden;
}
.sidetoc {
background-color: var(--color-background);
border: none;
}
.alert {
background-color: inherit;
border: none;
padding: 10px 0;
border-radius: 0;
}
.alert>p {
margin-bottom: 0;
padding: 5px 10px;
border-bottom: 1px solid;
background-color: var(--color-background-dark);
}
.alert>h5 {
padding: 10px 15px;
margin-top: 0;
margin-bottom: 0;
text-transform: uppercase;
font-weight: bold;
border-top: 2px solid;
background-color: var(--color-background-dark);
border-radius: none;
}
.alert>ul {
margin-bottom: 0;
padding: 5px 40px;
}
.alert-info {
color: #1976d2;
}
.alert-warning {
color: #f57f17;
}
.alert-danger {
color: #d32f2f;
}
pre {
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
word-break: break-all;
word-wrap: break-word;
background-color: var(--color-background-dark);
border-radius: 0;
border: none;
}
code {
background: var(--color-background-dark) !important;
border-radius: 2px;
}
.hljs {
color: var(--color-foreground);
}
.toc .nav>li.active>.expand-stub::before, .toc .nav>li.in>.expand-stub::before, .toc .nav>li.in.active>.expand-stub::before, .toc .nav>li.filtered>.expand-stub::before {
content: "▾";
}
.toc .nav>li>.expand-stub::before, .toc .nav>li.active>.expand-stub::before {
content: "▸";
}
.affix ul ul>li>a:before {
content: "|";
}
.breadcrumb {
background-color: var(--color-background-subnav);
}
.breadcrumb .label.label-primary {
background: #444;
border-radius: 0;
font-weight: normal;
font-size: 100%;
}
#breadcrumb .breadcrumb>li a {
border-radius: 0;
font-weight: normal;
font-size: 85%;
display: inline;
padding: 0 .6em 0;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
color: var(--color-breadcrumb);
}
#breadcrumb .breadcrumb>li a:hover {
color: var(--color-foreground);
transition: all ease 0.25s;
}
.breadcrumb>li+li:before {
content: "⯈";
font-size: 75%;
color: var(--color-background-dark);
padding: 0;
}
.light-theme .breadcrumb>li+li:before {
color: var(--color-foreground)
}
.toc .level1>li {
font-weight: 600;
font-size: 130%;
padding-left: 5px;
}
.footer {
border-top: none;
background-color: var(--color-background-dark);
padding: 15px 0;
font-size: 90%;
}
.toc .nav>li>a:hover, .toc .nav>li>a:focus {
color: var(--color-toc-hover);
transition: all ease 0.1s;
}
.form-control {
background-color: var(--color-background-subnav);
border: none;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.form-control:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
input#search-query:focus {
color: var(--color-foreground);
}
.table-bordered, .table-bordered>tbody>tr>td, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>td, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>thead>tr>th {
border: 1px solid var(--color-background-dark);
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: var(--color-background-table-alt);
}
blockquote {
padding: 10px 20px;
margin: 0 0 10px;
font-size: 110%;
border-left: 5px solid var(--color-background-quote);
color: var(--color-background-quote);
}
.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, .pagination>.disabled>span:focus, .pagination>.disabled>span:hover {
background-color: var(--color-background-subnav);
border-color: var(--color-background-subnav);
}
.breadcrumb>li, .pagination {
display: inline;
}
.tabGroup a[role="tab"] {
border-bottom: 2px solid var(--color-background-dark);
}
.tabGroup a[role="tab"][aria-selected="true"] {
color: var(--color-foreground);
}
.tabGroup section[role="tabpanel"] {
border: 1px solid var(--color-background-dark);
}
.sideaffix > div.contribution > ul > li > a.contribution-link:hover {
background-color: var(--color-background);
}
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 4px;
bottom: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #337ab7;
}
input:focus + .slider {
box-shadow: 0 0 1px #337ab7;
}
input:checked + .slider:before {
-webkit-transform: translateX(19px);
-ms-transform: translateX(19px);
transform: translateX(19px);
}
/* Rounded sliders */
.slider.round {
border-radius: 20px;
}
.slider.round:before {
border-radius: 50%;
}
.toggle-mode .icon {
display: inline-block;
}
.toggle-mode .icon i {
font-style: normal;
font-size: 17px;
display: inline-block;
padding-right: 7px;
padding-left: 7px;
vertical-align: middle;
}
@media (min-width: 1600px) {
.container {
width: 100%;
}
.sidefilter {
width: 18%;
}
.sidetoc {
width: 18%;
}
.article.grid-right {
margin-left: 19%;
}
.sideaffix {
width: 11.5%;
}
.affix ul>li.active>a {
white-space: initial;
}
.affix ul>li>a {
width: 99%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}

View File

@@ -0,0 +1,35 @@
const sw = document.getElementById("switch-style"), sw_mobile = document.getElementById("switch-style-m"), b = document.body;
if (b) {
function toggleTheme(target, dark) {
target.classList.toggle("dark-theme", dark)
target.classList.toggle("light-theme", !dark)
}
function switchEventListener() {
toggleTheme(b, this.checked);
if (window.localStorage) {
this.checked ? localStorage.setItem("theme", "dark-theme") : localStorage.setItem("theme", "light-theme")
}
}
var isDarkTheme = !window.localStorage || !window.localStorage.getItem("theme") || window.localStorage && localStorage.getItem("theme") === "dark-theme";
if(sw && sw_mobile){
sw.checked = isDarkTheme;
sw_mobile.checked = isDarkTheme;
sw.addEventListener("change", switchEventListener);
sw_mobile.addEventListener("change", switchEventListener);
// sync state between switches
sw.addEventListener("change", function() {
sw_mobile.checked = this.checked;
});
sw_mobile.addEventListener("change", function() {
sw.checked = this.checked;
});
}
toggleTheme(b, isDarkTheme);
}

29
Robust.Docfx/toc.yml Normal file
View File

@@ -0,0 +1,29 @@
- name: Articles
href: articles/
# Client
- name: Robust.Client
href: api/Robust.Client/
homepage: api/Robust.Client/Robust.Client.html
- name: Robust.Client.WebView
href: api/Robust.Client.WebView/
#Loader Api
- name: Robust.LoaderApi
href: api/Robust.LoaderApi/Robust.LoaderApi/
# Server
- name: Robust.Server
href: api/Robust.Server/
# Shared
- name: Robust.Shared
href: api/Robust.Shared/
- name: Robust.Shared.Maths
href: api/Robust.Shared.Maths/
- name: Robust.Shared.Scripting
href: api/Robust.Shared.Scripting/
# Unit testing
- name: Robust.UnitTesting
href: api/Robust.UnitTesting/

View File

@@ -176,7 +176,7 @@ namespace Robust.Server.GameObjects
public BoundUserInterface? GetUiOrNull(EntityUid uid, object uiKey, ServerUserInterfaceComponent? ui = null)
{
return TryGetUi(uid, uiKey, out var bui)
return TryGetUi(uid, uiKey, out var bui, ui)
? bui
: null;
}
@@ -185,12 +185,12 @@ namespace Robust.Server.GameObjects
{
bui = null;
return Resolve(uid, ref ui) && ui.TryGetBoundUserInterface(uiKey, out bui);
return Resolve(uid, ref ui, false) && ui.TryGetBoundUserInterface(uiKey, out bui);
}
public bool IsUiOpen(EntityUid uid, object uiKey, ServerUserInterfaceComponent? ui = null)
{
if (!Resolve(uid, ref ui))
if (!Resolve(uid, ref ui, false))
return false;
if (!TryGetUi(uid, uiKey, out var bui, ui))
@@ -201,7 +201,7 @@ namespace Robust.Server.GameObjects
public bool TrySetUiState(EntityUid uid, object uiKey, BoundUserInterfaceState state, IPlayerSession? session = null, ServerUserInterfaceComponent? ui = null)
{
if (!Resolve(uid, ref ui))
if (!Resolve(uid, ref ui, false))
return false;
if (!TryGetUi(uid, uiKey, out var bui, ui))

View File

@@ -37,7 +37,6 @@ namespace Robust.Server.GameObjects
public override void Initialize()
{
SetupNetworking();
ReceivedComponentMessage += (_, compMsg) => DispatchComponentMessage(compMsg);
ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg);
base.Initialize();
@@ -98,9 +97,6 @@ namespace Robust.Server.GameObjects
public override IEntityNetworkManager EntityNetManager => this;
/// <inheritdoc />
public event EventHandler<NetworkComponentMessage>? ReceivedComponentMessage;
/// <inheritdoc />
public event EventHandler<object>? ReceivedSystemMessage;
@@ -207,35 +203,6 @@ namespace Robust.Server.GameObjects
}
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, EntityUid entity, IComponent component,
ComponentMessage message)
{
if (_networkManager.IsClient)
return;
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
if (!netId.HasValue)
throw new ArgumentException($"Component {component.GetType()} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity;
msg.NetId = netId.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;
// Logger.DebugS("net.ent", "Sending: {0}", msg);
//Send the message
if (channel == null)
_networkManager.ServerSendToAll(msg);
else
_networkManager.ServerSendMessage(msg, channel);
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntityEventArgs message)
{
@@ -302,10 +269,6 @@ namespace Robust.Server.GameObjects
{
switch (message.Type)
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player));
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());

View File

@@ -8,6 +8,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Server.GameStates;
@@ -32,6 +33,13 @@ public interface IPVSCollection
/// </summary>
/// <param name="tick">The <see cref="GameTick"/> before which all deletions should be removed.</param>
public void CullDeletionHistoryUntil(GameTick tick);
public bool IsDirty(IChunkIndexLocation location);
public bool MarkDirty(IChunkIndexLocation location);
public void ClearDirty();
}
public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : IComparable<TIndex>, IEquatable<TIndex>
@@ -93,6 +101,16 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// </summary>
private readonly Dictionary<TIndex, GameTick> _removalBuffer = new();
/// <summary>
/// To avoid re-allocating the hashset every tick we'll just store it.
/// </summary>
private HashSet<TIndex> _changedIndices = new();
/// <summary>
/// A set of all chunks changed last tick
/// </summary>
private HashSet<IChunkIndexLocation> _dirtyChunks = new();
public PVSCollection()
{
IoCManager.InjectDependencies(this);
@@ -100,13 +118,17 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
public void Process()
{
var changedIndices = new HashSet<TIndex>(_locationChangeBuffer.Keys);
_changedIndices.EnsureCapacity(_locationChangeBuffer.Count);
foreach (var (key, loc) in _locationChangeBuffer)
{
_changedIndices.Add(key);
}
var changedChunkLocations = new HashSet<IIndexLocation>();
foreach (var (index, tick) in _removalBuffer)
{
//changes dont need to be computed if we are removing the index anyways
if (changedIndices.Remove(index) && !_indexLocations.ContainsKey(index))
if (_changedIndices.Remove(index) && !_indexLocations.ContainsKey(index))
{
//this index wasnt added yet, so we can safely just skip the deletion
continue;
@@ -114,37 +136,50 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
var location = RemoveIndexInternal(index);
if(location is GridChunkLocation or MapChunkLocation)
changedChunkLocations.Add(location);
_dirtyChunks.Add((IChunkIndexLocation) location);
_deletionHistory.Add((tick, index));
}
// remove empty chunk-subsets
foreach (var chunkLocation in changedChunkLocations)
foreach (var chunkLocation in _dirtyChunks)
{
switch (chunkLocation)
{
case GridChunkLocation gridChunkLocation:
if (_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices].Count == 0)
_gridChunkContents[gridChunkLocation.GridId].Remove(gridChunkLocation.ChunkIndices);
if(!_gridChunkContents.TryGetValue(gridChunkLocation.GridId, out var gridChunks)) continue;
if(!gridChunks.TryGetValue(gridChunkLocation.ChunkIndices, out var chunk)) continue;
if(chunk.Count == 0)
gridChunks.Remove(gridChunkLocation.ChunkIndices);
break;
case MapChunkLocation mapChunkLocation:
if (_mapChunkContents[mapChunkLocation.MapId][mapChunkLocation.ChunkIndices].Count == 0)
_mapChunkContents[mapChunkLocation.MapId].Remove(mapChunkLocation.ChunkIndices);
if(!_mapChunkContents.TryGetValue(mapChunkLocation.MapId, out var mapChunks)) continue;
if(!mapChunks.TryGetValue(mapChunkLocation.ChunkIndices, out chunk)) continue;
if(chunk.Count == 0)
mapChunks.Remove(mapChunkLocation.ChunkIndices);
break;
}
}
foreach (var index in changedIndices)
foreach (var index in _changedIndices)
{
RemoveIndexInternal(index);
var oldLoc = RemoveIndexInternal(index);
if(oldLoc is GridChunkLocation or MapChunkLocation)
_dirtyChunks.Add((IChunkIndexLocation) oldLoc);
AddIndexInternal(index, _locationChangeBuffer[index]);
AddIndexInternal(index, _locationChangeBuffer[index], _dirtyChunks);
}
_changedIndices.Clear();
_locationChangeBuffer.Clear();
_removalBuffer.Clear();
}
public bool IsDirty(IChunkIndexLocation location) => _dirtyChunks.Contains(location);
public bool MarkDirty(IChunkIndexLocation location) => _dirtyChunks.Add(location);
public void ClearDirty() => _dirtyChunks.Clear();
public bool TryGetChunk(MapId mapId, Vector2i chunkIndices, [NotNullWhen(true)] out HashSet<TIndex>? indices) =>
_mapChunkContents[mapId].TryGetValue(chunkIndices, out indices);
@@ -153,7 +188,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
public HashSet<TIndex>.Enumerator GetElementsForSession(ICommonSession session) => _localOverrides[session].GetEnumerator();
private void AddIndexInternal(TIndex index, IIndexLocation location)
private void AddIndexInternal(TIndex index, IIndexLocation location, HashSet<IChunkIndexLocation> dirtyChunks)
{
switch (location)
{
@@ -162,10 +197,10 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
break;
case GridChunkLocation gridChunkLocation:
// might be gone due to grid-deletions
if(!_gridChunkContents.ContainsKey(gridChunkLocation.GridId)) return;
if(!_gridChunkContents[gridChunkLocation.GridId].ContainsKey(gridChunkLocation.ChunkIndices))
_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices] = new();
_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices].Add(index);
if(!_gridChunkContents.TryGetValue(gridChunkLocation.GridId, out var gridChunk)) return;
var gridLoc = gridChunk.GetOrNew(gridChunkLocation.ChunkIndices);
gridLoc.Add(index);
dirtyChunks.Add(gridChunkLocation);
break;
case LocalOverride localOverride:
// might be gone due to disconnects
@@ -174,10 +209,10 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
break;
case MapChunkLocation mapChunkLocation:
// might be gone due to map-deletions
if(!_mapChunkContents.ContainsKey(mapChunkLocation.MapId)) return;
if(!_mapChunkContents[mapChunkLocation.MapId].ContainsKey(mapChunkLocation.ChunkIndices))
_mapChunkContents[mapChunkLocation.MapId][mapChunkLocation.ChunkIndices] = new();
_mapChunkContents[mapChunkLocation.MapId][mapChunkLocation.ChunkIndices].Add(index);
if(!_mapChunkContents.TryGetValue(mapChunkLocation.MapId, out var mapChunk)) return;
var mapLoc = mapChunk.GetOrNew(mapChunkLocation.ChunkIndices);
mapLoc.Add(index);
dirtyChunks.Add(mapChunkLocation);
break;
}
@@ -320,6 +355,9 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
if(!removeFromOverride && IsOverride(index))
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is GlobalOverride) return;
RegisterUpdate(index, new GlobalOverride());
}
@@ -334,6 +372,10 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
if(!removeFromOverride && IsOverride(index))
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is LocalOverride local &&
local.Session == session) return;
RegisterUpdate(index, new LocalOverride(session));
}
@@ -361,6 +403,20 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
UpdateIndex(index, mapCoordinates.MapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
}
public IChunkIndexLocation GetChunkIndex(EntityCoordinates coordinates)
{
var gridId = coordinates.GetGridId(_entityManager);
if (gridId != GridId.Invalid)
{
var gridIndices = GetChunkIndices(coordinates.Position);
return new GridChunkLocation(gridId, gridIndices);
}
var mapCoordinates = coordinates.ToMap(_entityManager);
var mapIndices = GetChunkIndices(coordinates.Position);
return new MapChunkLocation(mapCoordinates.MapId, mapIndices);
}
/// <summary>
/// Updates an <see cref="TIndex"/> using the provided <see cref="gridId"/> and <see cref="chunkIndices"/>.
/// </summary>
@@ -373,6 +429,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
if(!removeFromOverride && IsOverride(index))
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is GridChunkLocation oldGrid &&
oldGrid.ChunkIndices == chunkIndices &&
oldGrid.GridId == gridId) return;
RegisterUpdate(index, new GridChunkLocation(gridId, chunkIndices));
}
@@ -388,13 +449,16 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
if(!removeFromOverride && IsOverride(index))
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is MapChunkLocation oldMap &&
oldMap.ChunkIndices == chunkIndices &&
oldMap.MapId == mapId) return;
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
}
private void RegisterUpdate(TIndex index, IIndexLocation location)
{
if(_indexLocations.TryGetValue(index, out var oldLocation) && oldLocation == location) return;
_locationChangeBuffer[index] = location;
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Composition;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.ObjectPool;
using NetSerializer;
@@ -9,7 +8,6 @@ using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
@@ -22,12 +20,13 @@ namespace Robust.Server.GameStates;
internal sealed partial class PVSSystem : EntitySystem
{
[Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
[Shared.IoC.Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
[Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
[Shared.IoC.Dependency] private readonly IServerEntityManager _serverEntManager = default!;
[Shared.IoC.Dependency] private readonly IServerGameStateManager _stateManager = default!;
[Shared.IoC.Dependency] private readonly SharedTransformSystem _transform = default!;
[Shared.IoC.Dependency] private readonly INetConfigurationManager _netConfigManager = default!;
public const float ChunkSize = 8;
@@ -41,16 +40,6 @@ internal sealed partial class PVSSystem : EntitySystem
/// </summary>
public bool CullingEnabled { get; private set; }
/// <summary>
/// How many new entities we can send per tick (dont wanna nuke the clients mailbox).
/// </summary>
private int _newEntityBudget;
/// <summary>
/// How many entered entities can be sent per tick.
/// </summary>
private int _entityBudget;
/// <summary>
/// Size of the side of the view bounds square.
/// </summary>
@@ -90,7 +79,20 @@ internal sealed partial class PVSSystem : EntitySystem
new DefaultObjectPool<HashSet<int>>(new SetPolicy<int>(), MaxVisPoolSize);
private readonly ObjectPool<RobustTree<EntityUid>> _treePool =
new DefaultObjectPool<RobustTree<EntityUid>>(new TreePolicy<EntityUid>());
new DefaultObjectPool<RobustTree<EntityUid>>(new TreePolicy<EntityUid>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<MapChunkLocation, int>> _mapChunkPool =
new DefaultObjectPool<Dictionary<MapChunkLocation, int>>(
new ChunkPoolPolicy<MapChunkLocation>(), 256);
private readonly ObjectPool<Dictionary<GridChunkLocation, int>> _gridChunkPool =
new DefaultObjectPool<Dictionary<GridChunkLocation, int>>(
new ChunkPoolPolicy<GridChunkLocation>(), 256);
private readonly Dictionary<uint, Dictionary<MapChunkLocation, int>> _mapIndices = new(4);
private readonly Dictionary<uint, Dictionary<GridChunkLocation, int>> _gridIndices = new(4);
private readonly List<(uint, IChunkIndexLocation)> _chunkList = new(64);
private readonly List<MapGrid> _gridsPool = new(8);
public override void Initialize()
{
@@ -111,20 +113,26 @@ internal sealed partial class PVSSystem : EntitySystem
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
SubscribeLocalEvent<MoveEvent>(OnEntityMove);
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
SubscribeLocalEvent<TransformComponent, ComponentStartup>(OnTransformStartup);
EntityManager.EntityDeleted += OnEntityDeleted;
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
_configManager.OnValueChanged(CVars.NetPVSNewEntityBudget, OnNewEntityBudgetChanged, true);
_configManager.OnValueChanged(CVars.NetPVSEntityBudget, OnEntityBudgetChanged, true);
InitializeDirty();
}
private void OnEntityBudgetChanged(int obj)
private void OnParentChange(ref EntParentChangedMessage ev)
{
_entityBudget = obj;
if (_mapManager.IsGrid(ev.Entity) || _mapManager.IsMap(ev.Entity)) return;
// If parent changes then the RobustTree for that chunk will no longer be valid and we need to force it as dirty.
// Should still be at its old location as moveevent is called after.
var xform = Transform(ev.Entity);
var coordinates = _transform.GetMoverCoordinates(xform);
var index = _entityPvsCollection.GetChunkIndex(coordinates);
_entityPvsCollection.MarkDirty(index);
}
public override void Shutdown()
@@ -137,7 +145,6 @@ internal sealed partial class PVSSystem : EntitySystem
_configManager.UnsubValueChanged(CVars.NetPVS, SetPvs);
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged);
_configManager.UnsubValueChanged(CVars.NetPVSEntityBudget, OnEntityBudgetChanged);
ShutdownDirty();
}
@@ -152,10 +159,6 @@ internal sealed partial class PVSSystem : EntitySystem
CullingEnabled = value;
}
private void OnNewEntityBudgetChanged(int obj)
{
_newEntityBudget = obj;
}
public void ProcessCollections()
{
@@ -182,6 +185,11 @@ internal sealed partial class PVSSystem : EntitySystem
}
CleanupDirty(playerSessions);
foreach (var collection in _pvsCollections)
{
collection.ClearDirty();
}
}
public void CullDeletionHistory(GameTick oldestAck)
@@ -313,15 +321,24 @@ internal sealed partial class PVSSystem : EntitySystem
public (List<(uint, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(IPlayerSession[] sessions)
{
var chunkList = new List<(uint, IChunkIndexLocation)>();
var playerChunks = new HashSet<int>[sessions.Length];
var eyeQuery = EntityManager.GetEntityQuery<EyeComponent>();
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var viewerEntities = new EntityUid[sessions.Length][];
_chunkList.Clear();
// Keep track of the index of each chunk we use for a faster index lookup.
var mapIndices = new Dictionary<uint, Dictionary<MapChunkLocation, int>>(4);
var gridIndices = new Dictionary<uint, Dictionary<GridChunkLocation, int>>(4);
// Pool it because this will allocate a lot across ticks as we scale in players.
foreach (var (_, chunks) in _mapIndices)
_mapChunkPool.Return(chunks);
foreach (var (_, chunks) in _gridIndices)
_gridChunkPool.Return(chunks);
_mapIndices.Clear();
_gridIndices.Clear();
var xformQuery = GetEntityQuery<TransformComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
for (int i = 0; i < sessions.Length; i++)
{
@@ -329,8 +346,7 @@ internal sealed partial class PVSSystem : EntitySystem
playerChunks[i] = _playerChunkPool.Get();
var viewers = GetSessionViewers(session);
viewerEntities[i] = new EntityUid[viewers.Count];
viewers.CopyTo(viewerEntities[i]);
viewerEntities[i] = viewers;
foreach (var eyeEuid in viewers)
{
@@ -341,10 +357,10 @@ internal sealed partial class PVSSystem : EntitySystem
visMask = eyeComp.VisibilityMask;
// Get the nyoom dictionary for index lookups.
if (!mapIndices.TryGetValue(visMask, out var mapDict))
if (!_mapIndices.TryGetValue(visMask, out var mapDict))
{
mapDict = new Dictionary<MapChunkLocation, int>(32);
mapIndices[visMask] = mapDict;
mapDict = _mapChunkPool.Get();
_mapIndices[visMask] = mapDict;
}
var mapChunkEnumerator = new ChunkIndicesEnumerator(viewPos, range, ChunkSize);
@@ -360,21 +376,28 @@ internal sealed partial class PVSSystem : EntitySystem
}
else
{
playerChunks[i].Add(chunkList.Count);
mapDict.Add(chunkLocation, chunkList.Count);
chunkList.Add(entry);
playerChunks[i].Add(_chunkList.Count);
mapDict.Add(chunkLocation, _chunkList.Count);
_chunkList.Add(entry);
}
}
// Get the nyoom dictionary for index lookups.
if (!gridIndices.TryGetValue(visMask, out var gridDict))
if (!_gridIndices.TryGetValue(visMask, out var gridDict))
{
gridDict = new Dictionary<GridChunkLocation, int>(32);
gridIndices[visMask] = gridDict;
gridDict = _gridChunkPool.Get();
_gridIndices[visMask] = gridDict;
}
_mapManager.FindGridsIntersectingEnumerator(mapId, new Box2(viewPos - range, viewPos + range), out var gridEnumerator, true);
while (gridEnumerator.MoveNext(out var mapGrid))
_gridsPool.Clear();
foreach (var mapGrid in _mapManager.FindGridsIntersecting(
mapId,
new Box2(viewPos - range, viewPos + range),
_gridsPool,
xformQuery,
physicsQuery,
true))
{
var localPos = transformQuery.GetComponent(mapGrid.GridEntityId).InvWorldMatrix.Transform(viewPos);
@@ -392,22 +415,77 @@ internal sealed partial class PVSSystem : EntitySystem
}
else
{
playerChunks[i].Add(chunkList.Count);
gridDict.Add(chunkLocation, chunkList.Count);
chunkList.Add(entry);
playerChunks[i].Add(_chunkList.Count);
gridDict.Add(chunkLocation, _chunkList.Count);
_chunkList.Add(entry);
}
}
}
}
_uidSetPool.Return(viewers);
}
return (chunkList, playerChunks, viewerEntities);
return (_chunkList, playerChunks, viewerEntities);
}
public (Dictionary<EntityUid, MetaDataComponent> mData, RobustTree<EntityUid> tree)? CalculateChunk(IChunkIndexLocation chunkLocation, uint visMask, EntityQuery<TransformComponent> transform, EntityQuery<MetaDataComponent> metadata)
private Dictionary<(uint visMask, IChunkIndexLocation location), (Dictionary<EntityUid, MetaDataComponent> metadata,
RobustTree<EntityUid> tree)?> _previousTrees = new();
private HashSet<(uint visMask, IChunkIndexLocation location)> _reusedTrees = new();
public void RegisterNewPreviousChunkTrees(
List<(uint, IChunkIndexLocation)> chunks,
(Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] trees,
bool[] reuse)
{
// For any chunks able to re-used we'll chuck them in a dictionary for faster lookup.
for (var i = 0; i < chunks.Count; i++)
{
var canReuse = reuse[i];
if (!canReuse) continue;
_reusedTrees.Add(chunks[i]);
}
var previousIndices = _previousTrees.Keys.ToArray();
foreach (var index in previousIndices)
{
// ReSharper disable once InconsistentlySynchronizedField
if(_reusedTrees.Contains(index)) continue;
var chunk = _previousTrees[index];
if (chunk.HasValue)
{
_chunkCachePool.Return(chunk.Value.metadata);
_treePool.Return(chunk.Value.tree);
}
if (!chunks.Contains(index))
{
_previousTrees.Remove(index);
}
}
_previousTrees.EnsureCapacity(chunks.Count);
for (int i = 0; i < chunks.Count; i++)
{
//this is a redundant assign if the tree has been reused. the assumption is that this is cheaper than a .Contains call
_previousTrees[chunks[i]] = trees[i];
}
// ReSharper disable once InconsistentlySynchronizedField
_reusedTrees.Clear();
}
public bool TryCalculateChunk(
IChunkIndexLocation chunkLocation,
uint visMask,
EntityQuery<TransformComponent> transform,
EntityQuery<MetaDataComponent> metadata,
out (Dictionary<EntityUid, MetaDataComponent> mData, RobustTree<EntityUid> tree)? result)
{
if (!_entityPvsCollection.IsDirty(chunkLocation) && _previousTrees.TryGetValue((visMask, chunkLocation), out var previousTree))
{
result = previousTree;
return true;
}
var chunk = chunkLocation switch
{
GridChunkLocation gridChunkLocation => _entityPvsCollection.TryGetChunk(gridChunkLocation.GridId,
@@ -419,7 +497,11 @@ internal sealed partial class PVSSystem : EntitySystem
? mapChunk
: null
};
if (chunk == null) return null;
if (chunk == null)
{
result = null;
return false;
}
var chunkSet = _chunkCachePool.Get();
var tree = _treePool.Get();
foreach (var uid in chunk)
@@ -427,18 +509,12 @@ internal sealed partial class PVSSystem : EntitySystem
AddToChunkSetRecursively(in uid, visMask, tree, chunkSet, transform, metadata);
}
return (chunkSet, tree);
result = (chunkSet, tree);
return false;
}
public void ReturnToPool((Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] chunkCache, HashSet<int>[] playerChunks)
public void ReturnToPool(HashSet<int>[] playerChunks)
{
foreach (var chunk in chunkCache)
{
if(!chunk.HasValue) continue;
_chunkCachePool.Return(chunk.Value.metadata);
_treePool.Return(chunk.Value.tree);
}
foreach (var playerChunk in playerChunks)
{
_playerChunkPool.Return(playerChunk);
@@ -481,6 +557,8 @@ internal sealed partial class PVSSystem : EntitySystem
EntityUid[] viewerEntities)
{
DebugTools.Assert(session.Status == SessionStatus.InGame);
var newEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSNewEntityBudget);
var enteredEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSEntityBudget);
var newEntitiesSent = 0;
var entitiesSent = 0;
var playerVisibleSet = _playerVisibleSets[session];
@@ -492,13 +570,11 @@ internal sealed partial class PVSSystem : EntitySystem
{
var cache = chunkCache[i];
if(!cache.HasValue) continue;
var rootNodes = cache.Value.tree.GetRootNodes();
foreach (var rootNode in rootNodes)
foreach (var rootNode in cache.Value.tree.RootNodes)
{
RecursivelyAddTreeNode(in rootNode, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, cache.Value.metadata);
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, cache.Value.metadata, in enteredEntityBudget, in newEntityBudget);
}
cache.Value.tree.ReturnRootNodes(rootNodes);
}
var globalEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
@@ -506,7 +582,7 @@ internal sealed partial class PVSSystem : EntitySystem
{
var uid = globalEnumerator.Current;
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, tQuery);
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
}
globalEnumerator.Dispose();
@@ -515,14 +591,14 @@ internal sealed partial class PVSSystem : EntitySystem
{
var uid = localEnumerator.Current;
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, tQuery);
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
}
localEnumerator.Dispose();
foreach (var viewerEntity in viewerEntities)
{
RecursivelyAddOverride(in viewerEntity, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, tQuery);
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
}
var entityStates = new List<EntityState>();
@@ -567,14 +643,17 @@ internal sealed partial class PVSSystem : EntitySystem
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private void RecursivelyAddTreeNode(
in RobustTree<EntityUid>.TreeNode node,
in EntityUid nodeIndex,
RobustTree<EntityUid> tree,
HashSet<EntityUid> seenSet,
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
Dictionary<EntityUid, PVSEntityVisiblity> toSend,
GameTick fromTick,
ref int newEntitiesSent,
ref int totalEnteredEntities,
Dictionary<EntityUid, MetaDataComponent> metaDataCache)
Dictionary<EntityUid, MetaDataComponent> metaDataCache,
in int enteredEntityBudget,
in int newEntityBudget)
{
//are we valid?
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
@@ -582,22 +661,26 @@ internal sealed partial class PVSSystem : EntitySystem
// As every map is parented to uid 0 in the tree we still need to get their children, plus because we go top-down
// we may find duplicate parents with children we haven't encountered before
// on different chunks (this is especially common with direct grid children)
if (node.Value.IsValid() && !toSend.ContainsKey(node.Value))
if (nodeIndex.IsValid() && !toSend.ContainsKey(nodeIndex))
{
//are we new?
var (entered, budgetFail) = ProcessEntry(in node.Value, seenSet, previousVisibleEnts, ref newEntitiesSent,
ref totalEnteredEntities);
var (entered, budgetFail) = ProcessEntry(in nodeIndex, seenSet, previousVisibleEnts, ref newEntitiesSent,
ref totalEnteredEntities, in enteredEntityBudget, in newEntityBudget);
if (budgetFail) return;
AddToSendSet(in node.Value, metaDataCache[node.Value], toSend, fromTick, entered);
AddToSendSet(in nodeIndex, metaDataCache[nodeIndex], toSend, fromTick, entered);
}
var node = tree[nodeIndex];
//our children are important regardless! iterate them!
foreach (var child in node.Children)
if(node.Children != null)
{
RecursivelyAddTreeNode(in child, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent,
ref totalEnteredEntities, metaDataCache);
foreach (var child in node.Children)
{
RecursivelyAddTreeNode(in child, tree, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent,
ref totalEnteredEntities, metaDataCache, in enteredEntityBudget, in newEntityBudget);
}
}
}
@@ -610,7 +693,9 @@ internal sealed partial class PVSSystem : EntitySystem
ref int newEntitiesSent,
ref int totalEnteredEntities,
EntityQuery<MetaDataComponent> metaQuery,
EntityQuery<TransformComponent> transQuery)
EntityQuery<TransformComponent> transQuery,
in int enteredEntityBudget,
in int newEntityBudget)
{
//are we valid?
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
@@ -621,10 +706,11 @@ internal sealed partial class PVSSystem : EntitySystem
var parent = transQuery.GetComponent(uid).ParentUid;
if (parent.IsValid() && !RecursivelyAddOverride(in parent, seenSet, previousVisibleEnts, toSend, fromTick,
ref newEntitiesSent, ref totalEnteredEntities, metaQuery, transQuery))
ref newEntitiesSent, ref totalEnteredEntities, metaQuery, transQuery, in enteredEntityBudget, in newEntityBudget))
return false;
var (entered, _) = ProcessEntry(in uid, seenSet, previousVisibleEnts, ref newEntitiesSent, ref totalEnteredEntities);
var (entered, _) = ProcessEntry(in uid, seenSet, previousVisibleEnts, ref newEntitiesSent,
ref totalEnteredEntities, in enteredEntityBudget, in newEntityBudget);
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, entered);
return true;
@@ -633,14 +719,14 @@ internal sealed partial class PVSSystem : EntitySystem
private (bool entered, bool budgetFail) ProcessEntry(in EntityUid uid, HashSet<EntityUid> seenSet,
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
ref int newEntitiesSent,
ref int totalEnteredEntities)
ref int totalEnteredEntities, in int enteredEntityBudget, in int newEntityBudget)
{
var @new = !seenSet.Contains(uid);
var entered = @new | !previousVisibleEnts.Remove(uid);
if (entered)
{
if (totalEnteredEntities >= _entityBudget)
if (totalEnteredEntities >= enteredEntityBudget)
return (entered, true);
totalEnteredEntities++;
@@ -649,7 +735,7 @@ internal sealed partial class PVSSystem : EntitySystem
if (@new)
{
//we just entered pvs, do we still have enough budget to send us?
if(newEntitiesSent >= _newEntityBudget)
if(newEntitiesSent >= newEntityBudget)
return (entered, true);
newEntitiesSent++;
@@ -843,14 +929,24 @@ internal sealed partial class PVSSystem : EntitySystem
return new EntityState(entityUid, changed.ToArray());
}
private HashSet<EntityUid> GetSessionViewers(ICommonSession session)
private EntityUid[] GetSessionViewers(ICommonSession session)
{
var viewers = _uidSetPool.Get();
if (session.Status != SessionStatus.InGame)
return viewers;
return Array.Empty<EntityUid>();
var viewers = _uidSetPool.Get();
if (session.AttachedEntity != null)
{
// Fast path
if (session is IPlayerSession { ViewSubscriptionCount: 0 })
{
_uidSetPool.Return(viewers);
return new[] { session.AttachedEntity.Value };
}
viewers.Add(session.AttachedEntity.Value);
}
// This is awful, but we're not gonna add the list of view subscriptions to common session.
if (session is IPlayerSession playerSession)
@@ -861,7 +957,10 @@ internal sealed partial class PVSSystem : EntitySystem
}
}
return viewers;
var viewerArray = viewers.ToArray();
_uidSetPool.Return(viewers);
return viewerArray;
}
// Read Safe
@@ -885,20 +984,6 @@ internal sealed partial class PVSSystem : EntitySystem
}
}
public sealed class ListPolicy<T> : PooledObjectPolicy<List<T>>
{
public override List<T> Create()
{
return new();
}
public override bool Return(List<T> obj)
{
obj.Clear();
return true;
}
}
public sealed class DictPolicy<T1, T2> : PooledObjectPolicy<Dictionary<T1, T2>> where T1 : notnull
{
public override Dictionary<T1, T2> Create()
@@ -915,12 +1000,10 @@ internal sealed partial class PVSSystem : EntitySystem
public sealed class TreePolicy<T> : PooledObjectPolicy<RobustTree<T>> where T : notnull
{
private readonly ObjectPool<HashSet<RobustTree<T>.TreeNode>> _treeNodeSetPool =
new DefaultObjectPool<HashSet<RobustTree<T>.TreeNode>>(new SetPolicy<RobustTree<T>.TreeNode>());
public override RobustTree<T> Create()
{
return new RobustTree<T>(_treeNodeSetPool.Get, _treeNodeSetPool.Return);
var pool = new DefaultObjectPool<HashSet<T>>(new SetPolicy<T>(), MaxVisPoolSize);
return new RobustTree<T>(pool);
}
public override bool Return(RobustTree<T> obj)
@@ -929,6 +1012,20 @@ internal sealed partial class PVSSystem : EntitySystem
return true;
}
}
private sealed class ChunkPoolPolicy<T> : PooledObjectPolicy<Dictionary<T, int>> where T : notnull
{
public override Dictionary<T, int> Create()
{
return new Dictionary<T, int>(32);
}
public override bool Return(Dictionary<T, int> obj)
{
obj.Clear();
return true;
}
}
}
[ByRefEvent]

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.ObjectPool;
namespace Robust.Server.GameStates;
@@ -9,64 +10,77 @@ public sealed class RobustTree<T> where T : notnull
private Dictionary<T, TreeNode> _nodeIndex = new();
private Dictionary<T, T> _parents = new();
private HashSet<T> _rootNodes = new();
public readonly HashSet<T> RootNodes = new();
private Func<HashSet<TreeNode>> _setProvider;
private Action<HashSet<TreeNode>> _setConsumer;
private ObjectPool<HashSet<T>> _pool;
public RobustTree(Func<HashSet<TreeNode>>? setProvider = null, Action<HashSet<TreeNode>>? setConsumer = null)
public RobustTree(ObjectPool<HashSet<T>>? pool = null)
{
_setProvider = setProvider ?? (static () => new());
_setConsumer = setConsumer ?? (static (_) => {});
_pool = pool ?? new DefaultObjectPool<HashSet<T>>(new PVSSystem.SetPolicy<T>());
}
public void Clear()
{
// TODO: This is hella expensive
foreach (var value in _nodeIndex.Values)
{
_setConsumer(value.Children);
if(value.Children != null)
_pool.Return(value.Children);
}
_nodeIndex.Clear();
_parents.Clear();
_rootNodes.Clear();
RootNodes.Clear();
}
public TreeNode this[T index] => _nodeIndex[index];
public void Remove(T value, bool mend = false)
{
if (!_nodeIndex.TryGetValue(value, out var node))
throw new InvalidOperationException("Node doesnt exist.");
if (_rootNodes.Contains(value))
if (RootNodes.Contains(value))
{
foreach (var child in node.Children)
if (node.Children != null)
{
_parents.Remove(child.Value);
_rootNodes.Add(child.Value);
foreach (var child in node.Children)
{
_parents.Remove(child);
RootNodes.Add(child);
}
_pool.Return(node.Children);
}
_setConsumer(node.Children);
_rootNodes.Remove(value);
RootNodes.Remove(value);
_nodeIndex.Remove(value);
return;
}
if (_parents.TryGetValue(value, out var parent))
{
foreach (var child in node.Children)
if (node.Children != null)
{
if (mend)
foreach (var child in node.Children)
{
_parents[child.Value] = parent;
_nodeIndex[parent].Children.Add(child);
if (mend)
{
_parents[child] = parent;
var children = _nodeIndex[parent].Children;
if (children == null)
{
children = _pool.Get();
_nodeIndex[parent] = _nodeIndex[parent].WithChildren(children);
}
children.Add(child);
}
else
{
_parents.Remove(child);
RootNodes.Add(child);
}
}
else
{
_parents.Remove(child.Value);
_rootNodes.Add(child.Value);
}
}
_setConsumer(node.Children);
_pool.Return(node.Children);
}
_parents.Remove(value);
_nodeIndex.Remove(value);
}
@@ -79,14 +93,14 @@ public sealed class RobustTree<T> where T : notnull
//root node, for now
if (_nodeIndex.TryGetValue(rootNode, out var node))
{
if(!_rootNodes.Contains(rootNode))
if(!RootNodes.Contains(rootNode))
throw new InvalidOperationException("Node already exists as non-root node.");
return node;
}
node = new TreeNode(rootNode, _setProvider());
node = new TreeNode(rootNode);
_nodeIndex.Add(rootNode, node);
_rootNodes.Add(rootNode);
RootNodes.Add(rootNode);
return node;
}
@@ -95,12 +109,17 @@ public sealed class RobustTree<T> where T : notnull
if (!_nodeIndex.TryGetValue(parent, out var parentNode))
parentNode = Set(parent);
if (parentNode.Children == null)
{
_nodeIndex[parent] = parentNode = parentNode.WithChildren(_pool.Get());
}
if (_nodeIndex.TryGetValue(child, out var existingNode))
{
if (_rootNodes.Contains(child))
if (RootNodes.Contains(child))
{
parentNode.Children.Add(existingNode);
_rootNodes.Remove(child);
parentNode.Children!.Add(existingNode.Value);
RootNodes.Remove(child);
_parents.Add(child, parent);
return existingNode;
}
@@ -108,42 +127,25 @@ public sealed class RobustTree<T> where T : notnull
if (!_parents.TryGetValue(child, out var previousParent) || _nodeIndex.TryGetValue(previousParent, out var previousParentNode))
throw new InvalidOperationException("Could not find old parent for non-root node.");
previousParentNode.Children.Remove(existingNode);
parentNode.Children.Add(existingNode);
previousParentNode.Children?.Remove(existingNode.Value);
parentNode.Children!.Add(existingNode.Value);
_parents[child] = parent;
return existingNode;
}
existingNode = new TreeNode(child, _setProvider());
existingNode = new TreeNode(child);
_nodeIndex.Add(child, existingNode);
parentNode.Children.Add(existingNode);
parentNode.Children!.Add(existingNode.Value);
_parents.Add(child, parent);
return existingNode;
}
// todo paul optimize this maybe as its basically all this is used for.
public HashSet<TreeNode> GetRootNodes()
{
var nodes = _setProvider();
foreach (var node in _rootNodes)
{
nodes.Add(_nodeIndex[node]);
}
return nodes;
}
public void ReturnRootNodes(HashSet<TreeNode> rootNodes)
{
_setConsumer(rootNodes);
}
public readonly struct TreeNode : IEquatable<TreeNode>
{
public readonly T Value;
public readonly HashSet<TreeNode> Children;
public readonly HashSet<T>? Children;
public TreeNode(T value, HashSet<TreeNode> children)
public TreeNode(T value, HashSet<T>? children = null)
{
Value = value;
Children = children;
@@ -151,7 +153,7 @@ public sealed class RobustTree<T> where T : notnull
public bool Equals(TreeNode other)
{
return Value.Equals(other.Value) && Children.Equals(other.Children);
return Value.Equals(other.Value) && Children?.Equals(other.Children) == true;
}
public override bool Equals(object? obj)
@@ -163,5 +165,10 @@ public sealed class RobustTree<T> where T : notnull
{
return HashCode.Combine(Value, Children);
}
public TreeNode WithChildren(HashSet<T> children)
{
return new TreeNode(Value, children);
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -144,6 +145,10 @@ namespace Robust.Server.GameStates
var chunkBatches = (int) MathF.Ceiling((float) chunksCount / ChunkBatchSize);
chunkCache =
new (Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[chunksCount];
// Update the reused trees sequentially to avoid having to lock the dictionary per chunk.
var reuse = ArrayPool<bool>.Shared.Rent(chunksCount);
transformQuery = _entityManager.GetEntityQuery<TransformComponent>();
metadataQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
Parallel.For(0, chunkBatches, i =>
@@ -154,9 +159,13 @@ namespace Robust.Server.GameStates
for (var j = start; j < end; ++j)
{
var (visMask, chunkIndexLocation) = chunks[j];
chunkCache[j] = _pvs.CalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery);
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery, out var chunk);
chunkCache[j] = chunk;
}
});
_pvs.RegisterNewPreviousChunkTrees(chunks, chunkCache, reuse);
ArrayPool<bool>.Shared.Return(reuse);
}
const int BatchSize = 2;
@@ -209,8 +218,6 @@ namespace Robust.Server.GameStates
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);
DebugTools.Assert(state.MapData?.CreatedMaps is null || (state.MapData?.CreatedMaps is not null && state.EntityStates.HasContents), "Sending new maps, but no entity state.");
// actually send the state
var stateUpdateMessage = _networkManager.CreateNetMessage<MsgState>();
stateUpdateMessage.State = state;
@@ -232,7 +239,7 @@ namespace Robust.Server.GameStates
}
if(_pvs.CullingEnabled)
_pvs.ReturnToPool(chunkCache, playerChunks);
_pvs.ReturnToPool(playerChunks);
_pvs.Cleanup(_playerManager.ServerSessions);
var oldestAck = new GameTick(oldestAckValue);

View File

@@ -229,6 +229,7 @@ namespace Robust.Server.Maps
private readonly MapLoadOptions? _loadOptions;
private readonly Dictionary<GridId, int> GridIDMap = new();
public readonly List<MapGrid> Grids = new();
private readonly List<GridId> _readGridIndices = new();
private readonly Dictionary<EntityUid, int> EntityUidMap = new();
private readonly Dictionary<int, EntityUid> UidEntityMap = new();
@@ -314,9 +315,8 @@ namespace Robust.Server.Maps
// Create the new map.
AllocMap();
// Load grids.
ReadTileMapSection();
ReadGridSection();
// Maps grid section indices to GridIds, for deserializing GridIds on entities.
ReadGridSectionIndices();
// Entities are first allocated. This allows us to know the future UID of all entities on the map before
// even ExposeData is loaded. This allows us to resolve serialized EntityUid instances correctly.
@@ -325,8 +325,11 @@ namespace Robust.Server.Maps
// Actually instance components and run ExposeData on them.
FinishEntitiesLoad();
// Finish binding MapGrids to their respective MapGridComponents.
BindGridComponents();
// Load grids.
ReadTileMapSection();
// Reads the grid section, allocates MapGrids, and maps them to their respective MapGridComponents.
ReadGridSection();
// Clear the net tick numbers so that components from prototypes (not modified by map)
// aren't sent over the wire initially.
@@ -474,18 +477,20 @@ namespace Robust.Server.Maps
}
}
private void BindGridComponents()
private void ReadGridSection()
{
// There were no new grids, nothing to do here.
if(Grids.Count == 0)
if(_readGridIndices.Count == 0)
return;
// MapGrids already contain their assigned GridId from their ctor, and the MapComponents just got deserialized.
// Now we need to actually bind the MapGrids to their components so that you can resolve GridId -> EntityUid
// After doing this, it should be 100% safe to use the MapManager API like normal.
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
// get ents that the grids will bind to
var gridComps = new Dictionary<GridId, MapGridComponent>(Grids.Count);
var gridComps = new Dictionary<GridId, MapGridComponent>(_readGridIndices.Count);
// linear search for new grid comps
foreach (var tuple in _entitiesToDeserialize)
@@ -502,15 +507,54 @@ namespace Robust.Server.Maps
gridComps[gridComp.GridIndex] = gridComp;
}
// Actually bind them
foreach (var mapGrid in Grids)
for (var index = 0; index < _readGridIndices.Count; index++)
{
// Here is where the implicit index pairing magic happens from the yaml.
var gridIndex = _readGridIndices[index];
var yamlGrid = (YamlMappingNode)yamlGrids.Children[index];
// designed to throw if something is broken, every grid must map to an ent
var gridComp = gridComps[mapGrid.Index];
_mapManager.BindGrid(gridComp, mapGrid);
var gridComp = gridComps[gridIndex];
DebugTools.Assert(gridComp.GridIndex == gridIndex);
YamlMappingNode yamlGridInfo = (YamlMappingNode)yamlGrid["settings"];
YamlSequenceNode yamlGridChunks = (YamlSequenceNode)yamlGrid["chunks"];
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
foreach (var chunkNode in yamlGridChunks.Cast<YamlMappingNode>())
{
YamlGridSerializer.DeserializeChunk(_mapManager, grid, chunkNode, _tileMap!, _tileDefinitionManager);
}
Grids.Add(grid); // Grids are kept in index order
}
}
private static MapGrid AllocateMapGrid(MapGridComponent gridComp, YamlMappingNode yamlGridInfo)
{
// sane defaults
ushort csz = 16;
ushort tsz = 1;
foreach (var kvInfo in yamlGridInfo)
{
var key = kvInfo.Key.ToString();
var val = kvInfo.Value.ToString();
if (key == "chunksize")
csz = ushort.Parse(val);
else if (key == "tilesize")
tsz = ushort.Parse(val);
else if (key == "snapsize")
continue; // obsolete
}
var grid = gridComp.AllocMapGrid(csz, tsz);
return grid;
}
private void AttachMapEntities()
{
var mapEntity = _mapManager.GetMapEntityIdOrThrow(TargetMap);
@@ -577,21 +621,15 @@ namespace Robust.Server.Maps
}
}
private void ReadGridSection()
private void ReadGridSectionIndices()
{
// sets up the mapping so the serializer can properly deserialize GridIds.
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
foreach (var yamlGrid in yamlGrids)
for (var i = 0; i < yamlGrids.Children.Count; i++)
{
var grid = YamlGridSerializer.DeserializeGrid(
_mapManager, TargetMap,
(YamlMappingNode) yamlGrid["settings"],
(YamlSequenceNode) yamlGrid["chunks"],
_tileMap!,
_tileDefinitionManager
);
Grids.Add(grid);
_readGridIndices.Add(_mapManager.GenerateGridId(null));
}
}
@@ -748,54 +786,55 @@ namespace Robust.Server.Maps
private void PopulateEntityList()
{
var withUid = new List<MapSaveIdComponent>();
var withoutUid = new List<EntityUid>();
var takenIds = new HashSet<int>();
var withoutUid = new HashSet<EntityUid>();
var saveCompQuery = _serverEntityManager.GetEntityQuery<MapSaveIdComponent>();
var transformCompQuery = _serverEntityManager.GetEntityQuery<TransformComponent>();
var metaCompQuery = _serverEntityManager.GetEntityQuery<MetaDataComponent>();
foreach (var entity in _serverEntityManager.GetEntities())
{
if (IsMapSavable(entity))
{
Entities.Add(entity);
if (_serverEntityManager.TryGetComponent(entity, out MapSaveIdComponent? mapSaveId))
{
withUid.Add(mapSaveId);
}
else
{
withoutUid.Add(entity);
}
}
}
var currentTransform = transformCompQuery.GetComponent(entity);
if (!GridIDMap.ContainsKey(currentTransform.GridID)) continue;
// Go over entities with a MapSaveIdComponent and assign those.
var currentEntity = entity;
foreach (var mapIdComp in withUid)
{
var uid = mapIdComp.Uid;
if (takenIds.Contains(uid))
// Don't serialize things parented to un savable things.
// For example clothes inside a person.
while (currentEntity.IsValid())
{
// Duplicate ID. Just pretend it doesn't have an ID and use the without path.
withoutUid.Add(mapIdComp.Owner);
if (metaCompQuery.GetComponent(currentEntity).EntityPrototype?.MapSavable == false) break;
currentEntity = transformCompQuery.GetComponent(currentEntity).ParentUid;
}
else
if (currentEntity.IsValid()) continue;
Entities.Add(entity);
if (!saveCompQuery.TryGetComponent(entity, out var mapSaveComp) ||
!UidEntityMap.TryAdd(mapSaveComp.Uid, entity))
{
EntityUidMap.Add(mapIdComp.Owner, uid);
takenIds.Add(uid);
// If the id was already saved before, or has no save component we need to find a new id for this entity
withoutUid.Add(entity);
}
}
var uidCounter = 0;
foreach (var entity in withoutUid)
{
while (takenIds.Contains(uidCounter))
while (UidEntityMap.ContainsKey(uidCounter))
{
// Find next available UID.
uidCounter += 1;
}
EntityUidMap.Add(entity, uidCounter);
takenIds.Add(uidCounter);
UidEntityMap.Add(uidCounter, entity);
uidCounter += 1;
}
// Build a reverse lookup
EntityUidMap.EnsureCapacity(UidEntityMap.Count);
foreach(var (saveId, mapId) in UidEntityMap)
{
EntityUidMap.Add(mapId, saveId);
}
}
@@ -803,19 +842,20 @@ namespace Robust.Server.Maps
{
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var metaQuery = _serverEntityManager.GetEntityQuery<MetaDataComponent>();
var entities = new YamlSequenceNode();
RootNode.Add("entities", entities);
var prototypeCompCache = new Dictionary<string, Dictionary<string, MappingDataNode>>();
foreach (var entity in Entities.OrderBy(e => EntityUidMap[e]))
foreach (var (saveId, entityUid) in UidEntityMap.OrderBy(e=>e.Key))
{
CurrentWritingEntity = entity;
CurrentWritingEntity = entityUid;
var mapping = new YamlMappingNode
{
{"uid", EntityUidMap[entity].ToString(CultureInfo.InvariantCulture)}
{"uid", saveId.ToString(CultureInfo.InvariantCulture)}
};
var md = _serverEntityManager.GetComponent<MetaDataComponent>(entity);
var md = metaQuery.GetComponent(entityUid);
if (md.EntityPrototype is {} prototype)
{
@@ -833,7 +873,7 @@ namespace Robust.Server.Maps
var components = new YamlSequenceNode();
// See engine#636 for why the Distinct() call.
foreach (var component in _serverEntityManager.GetComponents(entity))
foreach (var component in _serverEntityManager.GetComponents(entityUid))
{
if (component is MapSaveIdComponent)
continue;
@@ -900,27 +940,11 @@ namespace Robust.Server.Maps
return CurrentReadingEntityComponents!.Keys;
}
private bool IsMapSavable(EntityUid entity)
[Virtual]
public class MapLoadException : Exception
{
if (_serverEntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype?.MapSavable == false || !GridIDMap.ContainsKey(_serverEntityManager.GetComponent<TransformComponent>(entity).GridID))
{
return false;
}
// Don't serialize things parented to un savable things.
// For example clothes inside a person.
var current = _serverEntityManager.GetComponent<TransformComponent>(entity);
while (current.Parent != null)
{
if (_serverEntityManager.GetComponent<MetaDataComponent>(current.Parent.Owner).EntityPrototype?.MapSavable == false)
{
return false;
}
current = current.Parent;
}
return true;
public MapLoadException(string? message)
: base(message) { }
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
@@ -931,19 +955,18 @@ namespace Robust.Server.Maps
// This is the code that deserializes the Grids index into the GridId. This has to happen between Grid allocation
// and when grids are bound to their entities.
if (node.Value == "null") return new DeserializedValue<GridId>(GridId.Invalid);
if (node.Value == "null")
{
throw new MapLoadException($"Error in map file: found local grid ID '{node.Value}' which does not exist.");
}
var val = int.Parse(node.Value);
if (val >= Grids.Count)
if (val >= _readGridIndices.Count)
{
Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
}
else
{
return new DeserializedValue<GridId>(Grids[val].Index);
throw new MapLoadException($"Error in map file: found local grid ID '{val}' which does not exist.");
}
return new DeserializedValue<GridId>(GridId.Invalid);
return new DeserializedValue<GridId>(_readGridIndices[val]);
}
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,

View File

@@ -68,7 +68,8 @@ namespace Robust.Server.Maps
{
var tile = chunk.GetTile(x, y);
writer.Write(tile.TypeId);
writer.Write(tile.Data);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
}
}
}
@@ -76,38 +77,10 @@ namespace Robust.Server.Maps
return Convert.ToBase64String(barr);
}
public static MapGrid DeserializeGrid(IMapManagerInternal mapMan, MapId mapId, YamlMappingNode info,
YamlSequenceNode chunks, IReadOnlyDictionary<ushort, string> tileDefMapping,
public static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid,
YamlMappingNode chunkData,
IReadOnlyDictionary<ushort, string> tileDefMapping,
ITileDefinitionManager tileDefinitionManager)
{
ushort csz = 0;
ushort tsz = 0;
float sgsz = 0.0f;
foreach (var kvInfo in info)
{
var key = kvInfo.Key.ToString();
var val = kvInfo.Value.ToString();
if (key == "chunksize")
csz = ushort.Parse(val);
else if (key == "tilesize")
tsz = ushort.Parse(val);
else if (key == "snapsize")
sgsz = float.Parse(val, CultureInfo.InvariantCulture);
}
//TODO: Pass in options
var grid = mapMan.CreateUnboundGrid(mapId);
foreach (var chunkNode in chunks.Cast<YamlMappingNode>())
{
DeserializeChunk(mapMan, grid, chunkNode, tileDefMapping, tileDefinitionManager);
}
return grid;
}
private static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid, YamlMappingNode chunkData, IReadOnlyDictionary<ushort, string> tileDefMapping, ITileDefinitionManager tileDefinitionManager)
{
var indNode = chunkData["ind"];
var tileNode = chunkData["tiles"];
@@ -129,12 +102,13 @@ namespace Robust.Server.Maps
for (ushort x = 0; x < grid.ChunkSize; x++)
{
var id = reader.ReadUInt16();
var data = reader.ReadUInt16();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
var defName = tileDefMapping[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, data);
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
}
}

View File

@@ -44,6 +44,8 @@ namespace Robust.Server.Player
IReadOnlySet<EntityUid> ViewSubscriptions { get; }
int ViewSubscriptionCount { get; }
/// <summary>
/// Persistent data for this player.
/// </summary>

View File

@@ -39,6 +39,7 @@ namespace Robust.Server.Player
}
[ViewVariables] public IReadOnlySet<EntityUid> ViewSubscriptions => _viewSubscriptions;
public int ViewSubscriptionCount => _viewSubscriptions.Count;
[ViewVariables] public INetChannel ConnectedClient { get; }

View File

@@ -10,12 +10,12 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<!-- -->
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="6.0.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -87,8 +87,8 @@ namespace Robust.Server.ServerStatus
var authInfo = new JsonObject
{
["mode"] = _netManager.Auth.ToString(),
["public_key"] = _netManager.RsaPublicKey != null
? Convert.ToBase64String(_netManager.RsaPublicKey)
["public_key"] = _netManager.CryptoPublicKey != null
? Convert.ToBase64String(_netManager.CryptoPublicKey)
: null
};

View File

@@ -880,7 +880,7 @@ namespace Robust.Shared.Maths
return color.Value;
if (fallback.HasValue)
return fallback.Value;
throw new ArgumentException("Invalid color code and no fallback provided.", nameof(hexColor));
throw new ArgumentException($"Invalid color code \"{new string(hexColor)}\" and no fallback provided.", nameof(hexColor));
}
public static Color FromXaml(string name)

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
</ItemGroup>
</Project>

View File

@@ -10,10 +10,10 @@
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Robust.Shared.Exceptions;
namespace Robust.Shared.Asynchronous
@@ -14,10 +15,19 @@ namespace Robust.Shared.Asynchronous
public RobustSynchronizationContext(IRuntimeLog runtimeLog)
{
_runtimeLog = runtimeLog;
var channel = Channel.CreateUnbounded<Mail>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = false
});
_channelReader = channel.Reader;
_channelWriter = channel.Writer;
}
private readonly ConcurrentQueue<(SendOrPostCallback d, object? state)> _pending
= new();
private readonly ChannelReader<Mail> _channelReader;
private readonly ChannelWriter<Mail> _channelWriter;
public override void Send(SendOrPostCallback d, object? state)
{
@@ -34,18 +44,18 @@ namespace Robust.Shared.Asynchronous
public override void Post(SendOrPostCallback d, object? state)
{
_pending.Enqueue((d, state));
_channelWriter.TryWrite(new Mail(d, state));
}
public void ProcessPendingTasks()
{
while (_pending.TryDequeue(out var task))
while (_channelReader.TryRead(out var task))
{
#if EXCEPTION_TOLERANCE
try
#endif
{
task.d(task.state);
task.Callback(task.State);
}
#if EXCEPTION_TOLERANCE
catch (Exception e)
@@ -55,5 +65,12 @@ namespace Robust.Shared.Asynchronous
#endif
}
}
public ValueTask<bool> WaitOnPendingTasks()
{
return _channelReader.WaitToReadAsync();
}
private record struct Mail(SendOrPostCallback Callback, object? State);
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
@@ -32,10 +33,21 @@ namespace Robust.Shared.Asynchronous
_mainThreadContext.Post(_runCallback, callback);
}
private static readonly SendOrPostCallback _runCallback = o =>
public void BlockWaitOnTask(Task task)
{
((Action?)o)?.Invoke();
};
// NOTE: This code should be re-entry safe.
while (true)
{
var waitTask = _mainThreadContext.WaitOnPendingTasks().AsTask();
var idx = Task.WaitAny(task, waitTask);
if (idx == 0)
return;
_mainThreadContext.ProcessPendingTasks();
}
}
private static readonly SendOrPostCallback _runCallback = o => { ((Action?)o)?.Invoke(); };
}
public interface ITaskManager
@@ -52,5 +64,14 @@ namespace Robust.Shared.Asynchronous
/// </remarks>
/// <param name="callback">The callback that will be invoked on the main thread.</param>
void RunOnMainThread(Action callback);
/// <summary>
/// Synchronously wait for a main-thread task to complete.
/// This is effectively what you need to safely .Result a task on the main thread.
/// </summary>
/// <remarks>
/// Use of this method is only ever recommended in rare scenarios like shutdown. For most other scenarios you should really avoid blocking the main thread and use proper async instead.
/// </remarks>
void BlockWaitOnTask(Task task);
}
}

View File

@@ -128,13 +128,13 @@ namespace Robust.Shared
/// The amount of new entities that can be sent to a client in a single game state, under PVS.
/// </summary>
public static readonly CVarDef<int> NetPVSNewEntityBudget =
CVarDef.Create("net.pvs_new_budget", 20, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
CVarDef.Create("net.pvs_new_budget", 20, CVar.ARCHIVE | CVar.REPLICATED);
/// <summary>
/// The amount of entity updates that can be sent to a client in a single game state, under PVS.
/// The amount of entered entities that can be sent to a client in a single game state, under PVS.
/// </summary>
public static readonly CVarDef<int> NetPVSEntityBudget =
CVarDef.Create("net.pvs_budget", 50, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
CVarDef.Create("net.pvs_budget", 50, CVar.ARCHIVE | CVar.REPLICATED);
/// <summary>
/// Log late input messages from clients.

View File

@@ -20,6 +20,16 @@ namespace Robust.Shared.Configuration
/// </summary>
void SetupNetworking();
/// <summary>
/// Get a replicated client CVar for a specific client.
/// </summary>
/// <typeparam name="T">CVar type.</typeparam>
/// <param name="channel">channel of the connected client.</param>
/// <param name="definition">The CVar.</param>
/// <returns>Replicated CVar of the client.</returns>
public T GetClientCVar<T>(INetChannel channel, CVarDef<T> definition) where T : notnull =>
GetClientCVar<T>(channel, definition.Name);
/// <summary>
/// Get a replicated client CVar for a specific client.
/// </summary>

View File

@@ -55,24 +55,27 @@ namespace Robust.Shared.Containers
protected BaseContainer() { }
/// <inheritdoc />
public bool Insert(EntityUid toinsert, IEntityManager? entMan = null)
public bool Insert(EntityUid toinsert, IEntityManager? entMan = null, TransformComponent? transform = null, TransformComponent? ownerTransform = null, MetaDataComponent? meta = null)
{
DebugTools.Assert(!Deleted);
DebugTools.Assert(transform == null || transform.Owner == toinsert);
DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner);
IoCManager.Resolve(ref entMan);
//Verify we can insert into this container
if (!CanInsert(toinsert, entMan))
return false;
var transform = entMan.GetComponent<TransformComponent>(toinsert);
transform ??= entMan.GetComponent<TransformComponent>(toinsert);
// CanInsert already checks nullability of Parent (or container forgot to call base that does)
if (toinsert.TryGetContainerMan(out var containerManager, entMan) && !containerManager.Remove(toinsert))
return false; // Can't remove from existing container, can't insert.
// Attach to parent first so we can check IsInContainer more easily.
transform.AttachParent(entMan.GetComponent<TransformComponent>(Owner));
InternalInsert(toinsert, entMan);
ownerTransform ??= entMan.GetComponent<TransformComponent>(Owner);
transform.AttachParent(ownerTransform);
InternalInsert(toinsert, entMan, meta);
// This is an edge case where the parent grid is the container being inserted into, so AttachParent would not unanchor.
if (transform.Anchored)
@@ -123,23 +126,25 @@ namespace Robust.Shared.Containers
}
/// <inheritdoc />
public bool Remove(EntityUid toremove, IEntityManager? entMan = null)
public bool Remove(EntityUid toremove, IEntityManager? entMan = null, TransformComponent? xform = null, MetaDataComponent? meta = null)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
IoCManager.Resolve(ref entMan);
DebugTools.Assert(entMan.EntityExists(toremove));
DebugTools.Assert(xform == null || xform.Owner == toremove);
if (!CanRemove(toremove, entMan)) return false;
InternalRemove(toremove, entMan);
InternalRemove(toremove, entMan, meta);
entMan.GetComponent<TransformComponent>(toremove).AttachParentToContainerOrGrid(entMan);
xform ??= entMan.GetComponent<TransformComponent>(toremove);
xform.AttachParentToContainerOrGrid(entMan);
return true;
}
/// <inheritdoc />
public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null)
public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
@@ -147,7 +152,7 @@ namespace Robust.Shared.Containers
IoCManager.Resolve(ref entMan);
DebugTools.Assert(entMan.EntityExists(toRemove));
InternalRemove(toRemove, entMan);
InternalRemove(toRemove, entMan, meta);
}
/// <inheritdoc />
@@ -189,10 +194,13 @@ namespace Robust.Shared.Containers
/// </summary>
/// <param name="toinsert"></param>
/// <param name="entMan"></param>
protected virtual void InternalInsert(EntityUid toinsert, IEntityManager entMan)
protected virtual void InternalInsert(EntityUid toinsert, IEntityManager entMan, MetaDataComponent? meta = null)
{
DebugTools.Assert(!Deleted);
DebugTools.Assert(meta == null || meta.Owner == toinsert);
meta ??= entMan.GetComponent<MetaDataComponent>(toinsert);
meta.Flags |= MetaDataFlags.InContainer;
entMan.EventBus.RaiseLocalEvent(Owner, new EntInsertedIntoContainerMessage(toinsert, this));
entMan.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toinsert));
Manager.Dirty(entMan);
@@ -203,13 +211,16 @@ namespace Robust.Shared.Containers
/// </summary>
/// <param name="toremove"></param>
/// <param name="entMan"></param>
protected virtual void InternalRemove(EntityUid toremove, IEntityManager entMan)
protected virtual void InternalRemove(EntityUid toremove, IEntityManager entMan, MetaDataComponent? meta = null)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
DebugTools.Assert(entMan.EntityExists(toremove));
DebugTools.Assert(meta == null || meta.Owner == toremove);
meta ??= entMan.GetComponent<MetaDataComponent>(toremove);
meta.Flags &= ~MetaDataFlags.InContainer;
entMan.EventBus.RaiseLocalEvent(Owner, new EntRemovedFromContainerMessage(toremove, this));
entMan.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toremove));
Manager.Dirty(entMan);

View File

@@ -36,17 +36,17 @@ namespace Robust.Shared.Containers
public override string ContainerType => ClassName;
/// <inheritdoc />
protected override void InternalInsert(EntityUid toinsert, IEntityManager entMan)
protected override void InternalInsert(EntityUid toinsert, IEntityManager entMan, MetaDataComponent? meta = null)
{
_containerList.Add(toinsert);
base.InternalInsert(toinsert, entMan);
base.InternalInsert(toinsert, entMan, meta);
}
/// <inheritdoc />
protected override void InternalRemove(EntityUid toremove, IEntityManager entMan)
protected override void InternalRemove(EntityUid toremove, IEntityManager entMan, MetaDataComponent? meta = null)
{
_containerList.Remove(toremove);
base.InternalRemove(toremove, entMan);
base.InternalRemove(toremove, entMan, meta);
}
/// <inheritdoc />

View File

@@ -16,24 +16,17 @@ namespace Robust.Shared.Containers
public static class ContainerHelpers
{
/// <summary>
/// Am I inside a container?
/// Am I inside a container? Only checks the direct parent. To see if the entity, or any parent entity, is
/// inside a container, use <see cref="ContainerSystem.IsEntityOrParentInContainer"/>
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <returns>If the entity is inside of a container.</returns>
public static bool IsInContainer(this EntityUid entity, IEntityManager? entMan = null)
[Obsolete("Use ContainerSystem.IsEntityInContainer() instead")]
public static bool IsInContainer(this EntityUid entity,
IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
DebugTools.Assert(entMan.EntityExists(entity));
// Notice the recursion starts at the Owner of the passed in entity, this
// allows containers inside containers (toolboxes in lockers).
if (entMan.GetComponent<TransformComponent>(entity).ParentUid is not EntityUid { Valid: true} parent)
return false;
if (TryGetManagerComp(parent, out var containerComp, entMan))
return containerComp.ContainsEntity(entity);
return false;
return (entMan.GetComponent<MetaDataComponent>(entity).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer;
}
/// <summary>
@@ -61,6 +54,7 @@ namespace Robust.Shared.Containers
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="container">The container that this entity is inside of.</param>
/// <returns>If a container was found.</returns>
[Obsolete("Use ContainerSystem.TryGetContainingContainer() instead")]
public static bool TryGetContainer(this EntityUid entity, [NotNullWhen(true)] out IContainer? container, IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
@@ -195,6 +189,7 @@ namespace Robust.Shared.Containers
/// <returns>The new container.</returns>
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID.</exception>
/// <seealso cref="IContainerManager.MakeContainer{T}(string)" />
[Obsolete("Use ContainerSystem.MakeContainer() instead")]
public static T CreateContainer<T>(this EntityUid entity, string containerId, IEntityManager? entMan = null)
where T : IContainer
{
@@ -203,6 +198,7 @@ namespace Robust.Shared.Containers
return containermanager.MakeContainer<T>(containerId);
}
[Obsolete("Use ContainerSystem.EnsureContainer() instead")]
public static T EnsureContainer<T>(this EntityUid entity, string containerId, IEntityManager? entMan = null)
where T : IContainer
{
@@ -210,6 +206,7 @@ namespace Robust.Shared.Containers
return EnsureContainer<T>(entity, containerId, out _, entMan);
}
[Obsolete("Use ContainerSystem.EnsureContainer() instead")]
public static T EnsureContainer<T>(this EntityUid entity, string containerId, out bool alreadyExisted, IEntityManager? entMan = null)
where T : IContainer
{

View File

@@ -79,17 +79,17 @@ namespace Robust.Shared.Containers
}
/// <inheritdoc />
protected override void InternalInsert(EntityUid toinsert, IEntityManager entMan)
protected override void InternalInsert(EntityUid toinsert, IEntityManager entMan, MetaDataComponent? meta = null)
{
ContainedEntity = toinsert;
base.InternalInsert(toinsert, entMan);
base.InternalInsert(toinsert, entMan, meta);
}
/// <inheritdoc />
protected override void InternalRemove(EntityUid toremove, IEntityManager entMan)
protected override void InternalRemove(EntityUid toremove, IEntityManager entMan, MetaDataComponent? meta = null)
{
ContainedEntity = null;
base.InternalRemove(toremove, entMan);
base.InternalRemove(toremove, entMan, meta);
}
/// <inheritdoc />

View File

@@ -94,7 +94,7 @@ namespace Robust.Shared.Containers
/// Thrown if this container is a child of the entity,
/// which would cause infinite loops.
/// </exception>
bool Insert(EntityUid toinsert, IEntityManager? entMan = null);
bool Insert(EntityUid toinsert, IEntityManager? entMan = null, TransformComponent? transform = null, TransformComponent? ownerTransform = null, MetaDataComponent? meta = null);
/// <summary>
/// Checks if the entity can be removed from this container.
@@ -110,7 +110,7 @@ namespace Robust.Shared.Containers
/// <param name="toremove">The entity to attempt to remove.</param>
/// <param name="entMan"></param>
/// <returns>True if the entity was removed, false otherwise.</returns>
bool Remove(EntityUid toremove, IEntityManager? entMan = null);
bool Remove(EntityUid toremove, IEntityManager? entMan = null, TransformComponent? xform = null, MetaDataComponent? meta = null);
/// <summary>
/// Forcefully removes an entity from the container. Normally you would want to use <see cref="Remove" />,
@@ -118,7 +118,7 @@ namespace Robust.Shared.Containers
/// </summary>
/// <param name="toRemove">The entity to attempt to remove.</param>
/// <param name="entMan"></param>
void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null);
void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null);
/// <summary>
/// Checks if the entity is contained in this container.

View File

@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
{
@@ -29,18 +28,32 @@ namespace Robust.Shared.Containers
return containerManager.MakeContainer<T>(id);
}
public T EnsureContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
public T EnsureContainer<T>(EntityUid uid, string id, out bool alreadyExisted, ContainerManagerComponent? containerManager = null)
where T : IContainer
{
if (!Resolve(uid, ref containerManager, false))
containerManager = EntityManager.AddComponent<ContainerManagerComponent>(uid);
if (TryGetContainer(uid, id, out var container, containerManager))
return (T)container;
{
alreadyExisted = true;
if (container is T cast)
return cast;
throw new InvalidOperationException(
$"The container exists but is of a different type: {container.GetType()}");
}
alreadyExisted = false;
return MakeContainer<T>(uid, id, containerManager);
}
public T EnsureContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
where T : IContainer
{
return EnsureContainer<T>(uid, id, out _, containerManager);
}
public IContainer GetContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager))
@@ -66,9 +79,9 @@ namespace Robust.Shared.Containers
return false;
}
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null)
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false)
{
if (Resolve(uid, ref containerManager, false) && EntityManager.EntityExists(containedUid))
if (Resolve(uid, ref containerManager, false) && (skipExistCheck || EntityManager.EntityExists(containedUid)))
return containerManager.TryGetContainer(containedUid, out container);
container = null;
@@ -106,21 +119,69 @@ namespace Robust.Shared.Containers
#region Container Helpers
public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out IContainer? container, TransformComponent? transform = null)
public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out IContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null)
{
container = null;
if (!Resolve(uid, ref meta, false))
return false;
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
return false;
if (!Resolve(uid, ref transform, false))
return false;
if (!transform.ParentUid.IsValid())
return false;
return TryGetContainingContainer(transform.ParentUid, uid, out container);
return TryGetContainingContainer(transform.ParentUid, uid, out container, skipExistCheck: true);
}
public bool IsEntityInContainer(EntityUid uid, TransformComponent? transform = null)
/// <summary>
/// Checks whether the given entity is inside of a container. This will only check if this entity's direct
/// parent is containing it. To recursively if the entity, or any parent, is inside a container, use <see
/// cref="IsEntityOrParentInContainer"/>
/// </summary>
/// <returns>If the entity is inside of a container.</returns>
public bool IsEntityInContainer(EntityUid uid, MetaDataComponent? meta = null)
{
return TryGetContainingContainer(uid, out _, transform);
if (!Resolve(uid, ref meta, false))
return false;
return (meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer;
}
/// <summary>
/// Recursively if the entity, or any parent entity, is inside of a container.
/// </summary>
/// <returns>If the entity is inside of a container.</returns>
public bool IsEntityOrParentInContainer(
EntityUid uid,
MetaDataComponent? meta = null,
TransformComponent? xform = null,
EntityQuery<MetaDataComponent>? metas = null,
EntityQuery<TransformComponent>? xforms = null)
{
DebugTools.Assert(meta == null || meta.Owner == uid);
DebugTools.Assert(xform == null || xform.Owner == uid);
if (meta == null)
{
metas ??= EntityManager.GetEntityQuery<MetaDataComponent>();
meta = metas.Value.GetComponent(uid);
}
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer)
return true;
if (xform == null)
{
xforms ??= EntityManager.GetEntityQuery<TransformComponent>();
xform = xforms.Value.GetComponent(uid);
}
if (!xform.ParentUid.Valid)
return false;
return IsEntityOrParentInContainer(xform.ParentUid, metas: metas, xforms: xforms);
}
/// <summary>
@@ -227,12 +288,14 @@ namespace Robust.Shared.Containers
return false;
var conQuery = EntityManager.GetEntityQuery<ContainerManagerComponent>();
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
var child = uid;
var parent = xform.ParentUid;
while (parent.IsValid())
{
if (conQuery.TryGetComponent(parent, out var conManager) &&
if (((metaQuery.GetComponent(child).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer) &&
conQuery.TryGetComponent(parent, out var conManager) &&
conManager.TryGetContainer(child, out var parentContainer))
{
container = parentContainer;

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack;
/// <summary>
/// A content root stored in memory, backed by a dictionary.
/// </summary>
public sealed class MemoryContentRoot : IContentRoot, IDisposable
{
private readonly Dictionary<ResourcePath, byte[]> _files = new();
private readonly ReaderWriterLockSlim _lock = new();
/// <summary>
/// Adds a file to the content root, or updates it if that path already exists.
/// </summary>
/// <param name="relPath">The relative path of the file.</param>
/// <param name="data">The data byte array to store in the content root. Stored as is, without being copied or cloned.</param>
public void AddOrUpdateFile(ResourcePath relPath, byte[] data)
{
// Just in case, we ensure it's a clean relative path.
relPath = relPath.Clean().ToRelativePath();
_lock.EnterWriteLock();
try
{
_files[relPath] = data;
}
finally
{
_lock.ExitWriteLock();
}
}
/// <summary>
/// Remove a file from this content root.
/// </summary>
/// <param name="relPath">The relative path to the file.</param>
/// <returns></returns>
public bool RemoveFile(ResourcePath relPath)
{
_lock.EnterWriteLock();
try
{
return _files.Remove(relPath);
}
finally
{
_lock.ExitWriteLock();
}
}
/// <inheritdoc />
public bool TryGetFile(ResourcePath relPath, [NotNullWhen(true)] out Stream? stream)
{
_lock.EnterReadLock();
try
{
if (!_files.TryGetValue(relPath, out var data))
{
stream = null;
return false;
}
// Non-writable stream, as this needs to be thread-safe.
stream = new MemoryStream(data, false);
return true;
}
finally
{
_lock.ExitReadLock();
}
}
/// <inheritdoc />
public IEnumerable<ResourcePath> FindFiles(ResourcePath path)
{
_lock.EnterReadLock();
try
{
foreach (var (file, _) in _files)
{
if (file.TryRelativeTo(path, out _))
yield return file;
}
}
finally
{
_lock.ExitReadLock();
}
}
/// <inheritdoc />
public IEnumerable<string> GetRelativeFilePaths()
{
_lock.EnterReadLock();
try
{
foreach (var (file, _) in _files)
{
yield return file.ToString();
}
}
finally
{
_lock.ExitReadLock();
}
}
/// <summary>
/// Enumerates all files and their resource paths on this content root.
/// </summary>
/// <remarks>Do not modify or keep around the returned byte array, it's meant to be read-only.</remarks>
public IEnumerable<(ResourcePath relPath, byte[] data)> GetAllFiles()
{
_lock.EnterReadLock();
try
{
foreach (var (p, d) in _files)
{
yield return (p, d);
}
}
finally
{
_lock.ExitReadLock();
}
}
/// <inheritdoc />
public void Mount()
{
// Nada. We don't need to perform any special logic here.
}
/// <inheritdoc />
public void Dispose()
{
_lock.Dispose();
}
}

View File

@@ -219,42 +219,6 @@ namespace Robust.Shared.GameObjects
entManager.Dirty(this);
}
/// <summary>
/// Sends a message to all other components in this entity.
/// This is an alias of 'Owner.SendMessage(this, message);'
/// </summary>
/// <param name="message">Message to send.</param>
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
protected void SendMessage(ComponentMessage message)
{
var components = IoCManager.Resolve<IEntityManager>().GetComponents(Owner);
foreach (var component in components)
{
if (this != component)
component.HandleMessage(message, this);
}
}
/// <summary>
/// Sends a message over the network to all other components on the networked entity. This works both ways.
/// This is an alias of 'Owner.SendNetworkMessage(this, message);'
/// </summary>
/// <param name="message">Message to send.</param>
/// <param name="channel">Network channel to send the message over. If null, broadcast to all channels.</param>
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
protected void SendNetworkMessage(ComponentMessage message, INetChannel? channel = null)
{
IoCManager.Resolve<IEntityManager>().EntityNetManager?.SendComponentNetworkMessage(channel, Owner, this, message);
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public virtual void HandleMessage(ComponentMessage message, IComponent? component) { }
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public virtual void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) { }
private static readonly ComponentState DefaultComponentState = new();
/// <inheritdoc />

View File

@@ -1,24 +0,0 @@
using System;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// A message containing info to send through the component message system.
/// </summary>
[Serializable, NetSerializable]
[Obsolete("Component messages are deprecated. Use directed local events instead.")]
public abstract class ComponentMessage
{
/// <summary>
/// Was this message raised from a remote location over the network?
/// </summary>
public bool Remote { get; internal set; }
/// <summary>
/// If this is a remote message, will it only be sent to the corresponding component?
/// If this is not a remote message, this flag does nothing.
/// </summary>
public bool Directed { get; protected set; }
}
}

View File

@@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
@@ -46,6 +47,7 @@ namespace Robust.Shared.GameObjects
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks, ILookupWorldBox2Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[DataField("status", readOnly: true)]
private BodyStatus _bodyStatus = BodyStatus.OnGround;
@@ -119,7 +121,7 @@ namespace Robust.Shared.GameObjects
Force = Vector2.Zero;
Torque = 0.0f;
EntitySystem.Get<SharedBroadphaseSystem>().RegenerateContacts(this);
_sysMan.GetEntitySystem<SharedBroadphaseSystem>().RegenerateContacts(this);
_entMan.EventBus.RaiseLocalEvent(Owner, new PhysicsBodyTypeChangedEvent(_bodyType, oldType), false);
}
@@ -445,6 +447,7 @@ namespace Robust.Shared.GameObjects
/// AKA Sweep.LocalCenter in Box2D.
/// Not currently in use as this is set after mass data gets set (when fixtures update).
/// </remarks>
[ViewVariables]
public Vector2 LocalCenter
{
get => _localCenter;
@@ -547,36 +550,14 @@ namespace Robust.Shared.GameObjects
[DataField("angularDamping")]
private float _angularDamping = 0.2f;
/// <summary>
/// Get the linear and angular velocities at the same time.
/// </summary>
public (Vector2 Linear, float Angular) MapVelocities
{
get
{
var linearVelocity = _linearVelocity;
var angularVelocity = _angularVelocity;
var entMan = _entMan;
var parent = entMan.GetComponent<TransformComponent>(Owner).Parent;
while (parent != null)
{
if (entMan.TryGetComponent(parent.Owner, out PhysicsComponent? body))
{
linearVelocity += body.LinearVelocity;
angularVelocity += body.AngularVelocity;
}
parent = parent.Parent;
}
return (linearVelocity, angularVelocity);
}
}
/// <summary>
/// Current linear velocity of the entity in meters per second.
/// </summary>
/// <remarks>
/// This is the velocity relative to the parent, but is defined in terms of map coordinates. I.e., if the
/// entity's parents are all stationary, this is the rate of change of this entity's world position (not
/// local position).
/// </remarks>
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 LinearVelocity
{
@@ -602,44 +583,6 @@ namespace Robust.Shared.GameObjects
internal Vector2 _linearVelocity;
/// <summary>
/// Get the body's LinearVelocity in map terms.
/// </summary>
/// <remarks>
/// Consider using <see cref="MapVelocities"/> if you need linear and angular at the same time.
/// </remarks>
[ViewVariables]
public Vector2 MapLinearVelocity
{
get
{
var entManager = IoCManager.Resolve<IEntityManager>();
var physicsSystem = EntitySystem.Get<SharedPhysicsSystem>();
var xforms = entManager.GetEntityQuery<TransformComponent>();
var physics = entManager.GetEntityQuery<PhysicsComponent>();
var xform = xforms.GetComponent(Owner);
var parent = xform.ParentUid;
var localPos = xform.LocalPosition;
var velocity = _linearVelocity;
while (parent.IsValid())
{
var parentXform = xforms.GetComponent(parent);
if (physics.TryGetComponent(parent, out var body))
{
velocity += physicsSystem.GetLinearVelocityFromLocalPoint(body, localPos);
}
velocity = parentXform.LocalRotation.RotateVec(velocity);
parent = parentXform.ParentUid;
}
return velocity;
}
}
/// <summary>
/// Current angular velocity of the entity in radians per sec.
/// </summary>
@@ -671,35 +614,6 @@ namespace Robust.Shared.GameObjects
private float _angularVelocity;
/// <summary>
/// Get the body's AngularVelocity in map terms.
/// </summary>
/// <remarks>
/// Consider using <see cref="MapVelocities"/> if you need linear and angular at the same time.
/// </remarks>
[ViewVariables]
public float MapAngularVelocity
{
get
{
var velocity = _angularVelocity;
var entMan = _entMan;
var parent = entMan.GetComponent<TransformComponent>(Owner).Parent;
while (parent != null)
{
if (entMan.TryGetComponent(parent.Owner, out PhysicsComponent? body))
{
velocity += body.AngularVelocity;
}
parent = parent.Parent;
}
return velocity;
}
}
/// <summary>
/// Current momentum of the entity in kilogram meters per second
/// </summary>
@@ -738,7 +652,7 @@ namespace Robust.Shared.GameObjects
public IEnumerable<PhysicsComponent> GetBodiesIntersecting()
{
foreach (var entity in EntitySystem.Get<SharedPhysicsSystem>().GetCollidingEntities(_entMan.GetComponent<TransformComponent>(Owner).MapID, GetWorldAABB()))
foreach (var entity in _sysMan.GetEntitySystem<SharedPhysicsSystem>().GetCollidingEntities(_entMan.GetComponent<TransformComponent>(Owner).MapID, GetWorldAABB()))
{
yield return entity;
}
@@ -837,7 +751,7 @@ namespace Robust.Shared.GameObjects
IEnumerable<IPhysBody> IPhysBody.GetCollidingEntities(Vector2 offset, bool approx)
{
return EntitySystem.Get<SharedPhysicsSystem>().GetCollidingEntities(this, offset, approx);
return _sysMan.GetEntitySystem<SharedPhysicsSystem>().GetCollidingEntities(this, offset, approx);
}
public void ResetMassData(FixturesComponent? fixtures = null)
@@ -856,7 +770,7 @@ namespace Robust.Shared.GameObjects
// Temporary until ECS don't @ me.
fixtures ??= IoCManager.Resolve<IEntityManager>().GetComponent<FixturesComponent>(Owner);
var localCenter = Vector2.Zero;
var shapeManager = EntitySystem.Get<FixtureSystem>();
var shapeManager = _sysMan.GetEntitySystem<FixtureSystem>();
foreach (var (_, fixture) in fixtures.Fixtures)
{
@@ -961,6 +875,12 @@ namespace Robust.Shared.GameObjects
return true;
}
// View variables conveniences properties.
[ViewVariables]
private Vector2 _mapLinearVelocity => _sysMan.GetEntitySystem<SharedPhysicsSystem>().GetMapLinearVelocity(Owner, this);
[ViewVariables]
private float _mapAngularVelocity => _sysMan.GetEntitySystem<SharedPhysicsSystem>().GetMapAngularVelocity(Owner, this);
}
/// <summary>

View File

@@ -3,7 +3,6 @@ using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
@@ -11,43 +10,11 @@ namespace Robust.Shared.GameObjects
/// An optimisation component for stuff that should be set as collidable when it's awake and non-collidable when asleep.
/// </summary>
[NetworkedComponent()]
[Friend(typeof(CollisionWakeSystem))]
public sealed class CollisionWakeComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
[DataField("enabled")]
private bool _enabled = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled
{
get => _enabled;
set
{
if (value == _enabled) return;
_enabled = value;
Dirty();
RaiseStateChange();
}
}
internal void RaiseStateChange()
{
_entMan.EventBus.RaiseLocalEvent(Owner, new CollisionWakeStateMessage(), false);
}
public override ComponentState GetComponentState()
{
return new CollisionWakeState(Enabled);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not CollisionWakeState state) return;
Enabled = state.Enabled;
}
public bool Enabled = true;
[Serializable, NetSerializable]
public sealed class CollisionWakeState : ComponentState

View File

@@ -59,6 +59,15 @@ namespace Robust.Shared.GameObjects
set => this.MapPreInit = value;
}
/// <inheritdoc />
protected override void OnRemove()
{
base.OnRemove();
var mapMan = IoCManager.Resolve<IMapManagerInternal>();
mapMan.TrueDeleteMap(_mapIndex);
}
/// <inheritdoc />
public override ComponentState GetComponentState()
{

View File

@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -30,6 +33,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IEntityManager _entMan = default!;
// This field is used for deserialization internally in the map loader.
// If you want to remove this, you would have to restructure the map save file.
[ViewVariables(VVAccess.ReadOnly)]
[DataField("index")]
private GridId _gridIndex = GridId.Invalid;
@@ -43,12 +47,15 @@ namespace Robust.Shared.GameObjects
internal set => _gridIndex = value;
}
[DataField("chunkSize")]
private ushort _chunkSize = 16;
/// <inheritdoc />
[ViewVariables]
public IMapGrid Grid
{
get => _mapGrid ?? throw new InvalidOperationException();
set => _mapGrid = value;
private set => _mapGrid = value;
}
protected override void Initialize()
@@ -115,7 +122,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override ComponentState GetComponentState()
{
return new MapGridComponentState(_gridIndex);
return new MapGridComponentState(_gridIndex, _chunkSize);
}
/// <inheritdoc />
@@ -127,6 +134,71 @@ namespace Robust.Shared.GameObjects
return;
_gridIndex = state.GridIndex;
_chunkSize = state.ChunkSize;
}
public MapGrid AllocMapGrid(ushort chunkSize, ushort tileSize)
{
DebugTools.Assert(LifeStage == ComponentLifeStage.Added);
var grid = new MapGrid(_mapManager, _entMan, GridIndex, chunkSize);
grid.TileSize = tileSize;
Grid = grid;
grid.GridEntityId = Owner;
_mapManager.OnGridAllocated(this, grid);
return grid;
}
public static void ApplyMapGridState(NetworkedMapManager networkedMapManager, IMapGridComponent gridComp, GameStateMapData.ChunkDatum[] chunkUpdates)
{
var grid = (MapGrid)gridComp.Grid;
networkedMapManager.SuppressOnTileChanged = true;
var modified = new List<(Vector2i position, Tile tile)>();
foreach (var chunkData in chunkUpdates)
{
if (chunkData.IsDeleted())
continue;
var chunk = grid.GetChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == grid.ChunkSize * grid.ChunkSize);
var counter = 0;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = chunkData.TileData[counter++];
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
modified.Add((new Vector2i(chunk.X * grid.ChunkSize + x, chunk.Y * grid.ChunkSize + y), tile));
}
}
}
if (modified.Count != 0)
{
MapManager.InvokeGridChanged(networkedMapManager, grid, modified);
}
foreach (var chunkData in chunkUpdates)
{
if (chunkData.IsDeleted())
{
grid.RemoveChunk(chunkData.Index);
continue;
}
var chunk = grid.GetChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
grid.RegenerateCollision(chunk);
}
networkedMapManager.SuppressOnTileChanged = false;
}
}
@@ -141,13 +213,20 @@ namespace Robust.Shared.GameObjects
/// </summary>
public GridId GridIndex { get; }
/// <summary>
/// The size of the chunks in the map grid.
/// </summary>
public ushort ChunkSize { get; }
/// <summary>
/// Constructs a new instance of <see cref="MapGridComponentState"/>.
/// </summary>
/// <param name="gridIndex">Index of the grid this component is linked to.</param>
public MapGridComponentState(GridId gridIndex)
/// <param name="chunkSize">The size of the chunks in the map grid.</param>
public MapGridComponentState(GridId gridIndex, ushort chunkSize)
{
GridIndex = gridIndex;
ChunkSize = chunkSize;
}
}
}

View File

@@ -182,5 +182,9 @@ namespace Robust.Shared.GameObjects
/// Whether the entity has states specific to a particular player.
/// </summary>
EntitySpecific = 1 << 0,
/// <summary>
/// Whether the entity is currently inside of a container.
/// </summary>
InContainer = 1 << 1,
}
}

View File

@@ -110,7 +110,7 @@ namespace Robust.Shared.GameObjects
LocalRotation = Angle.Zero;
_noLocalRotation = value;
Dirty();
Dirty(_entMan);
}
}
@@ -135,7 +135,7 @@ namespace Robust.Shared.GameObjects
// Set _nextRotation to null to break any active lerps if this is a client side prediction.
_nextRotation = null;
_localRotation = value;
Dirty();
Dirty(_entMan);
if (!DeferUpdates)
{
@@ -355,12 +355,17 @@ namespace Robust.Shared.GameObjects
// offset position from world to parent
_parent = value.EntityId;
var oldMapId = MapID;
ChangeMapId(newParent.MapID, xformQuery);
// preserve world rotation
if (LifeStage == ComponentLifeStage.Running)
LocalRotation += (oldParent?.WorldRotation ?? Angle.Zero) - newParent.WorldRotation;
// Cache new GridID before raising the event.
GridID = GetGridIndex(xformQuery);
var entParentChangedMessage = new EntParentChangedMessage(Owner, oldParent?.Owner);
var entParentChangedMessage = new EntParentChangedMessage(Owner, oldParent?.Owner, oldMapId);
_entMan.EventBus.RaiseLocalEvent(Owner, ref entParentChangedMessage);
}
@@ -377,7 +382,7 @@ namespace Robust.Shared.GameObjects
//TODO: This is a hack, look into WHY we can't call GridPosition before the comp is Running
if (Running)
{
if(!oldPosition.Position.Equals(Coordinates.Position))
if (!oldPosition.Position.Equals(Coordinates.Position))
{
var moveEvent = new MoveEvent(Owner, oldPosition, Coordinates, this);
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
@@ -420,7 +425,7 @@ namespace Robust.Shared.GameObjects
var oldGridPos = Coordinates;
_localPosition = value;
Dirty();
Dirty(_entMan);
if (!DeferUpdates)
{
@@ -653,6 +658,12 @@ namespace Robust.Shared.GameObjects
/// </summary>
public void AttachToGridOrMap()
{
bool TerminatingOrDeleted(EntityUid uid)
{
return !_entMan.TryGetComponent(uid, out MetaDataComponent? meta)
|| meta.EntityLifeStage >= EntityLifeStage.Terminating;
}
// nothing to do
var oldParent = Parent;
if (oldParent == null)
@@ -663,11 +674,13 @@ namespace Robust.Shared.GameObjects
var mapPos = MapPosition;
EntityUid newMapEntity;
if (_mapManager.TryFindGridAt(mapPos, out var mapGrid))
if (_mapManager.TryFindGridAt(mapPos, out var mapGrid) && !TerminatingOrDeleted(mapGrid.GridEntityId))
{
newMapEntity = mapGrid.GridEntityId;
}
else if (_mapManager.HasMapEntity(mapPos.MapId))
else if (_mapManager.HasMapEntity(mapPos.MapId)
&& _mapManager.GetMapEntityIdOrThrow(mapPos.MapId) is var mapEnt
&& !TerminatingOrDeleted(mapEnt))
{
newMapEntity = _mapManager.GetMapEntityIdOrThrow(mapPos.MapId);
}
@@ -691,7 +704,7 @@ namespace Robust.Shared.GameObjects
WorldPosition = mapPos.Position;
DeferUpdates = false;
Dirty();
Dirty(_entMan);
}
public void DetachParentToNull()
@@ -721,16 +734,14 @@ namespace Robust.Shared.GameObjects
oldConcrete._children.Remove(uid);
_parent = EntityUid.Invalid;
var oldMapId = MapID;
var entParentChangedMessage = new EntParentChangedMessage(Owner, oldParent, MapID);
MapID = MapId.Nullspace;
var entParentChangedMessage = new EntParentChangedMessage(Owner, oldParent);
_entMan.EventBus.RaiseLocalEvent(Owner, ref entParentChangedMessage);
// Does it even make sense to call these since this is called purely from OnRemove right now?
// > FWIW, also called pre-entity-delete and when moved outside of PVS range.
RebuildMatrices();
MapIdChanged(oldMapId);
Dirty();
Dirty(_entMan);
}
/// <summary>
@@ -761,14 +772,12 @@ namespace Robust.Shared.GameObjects
//Set Paused state
var mapPaused = _mapManager.IsMapPaused(newMapId);
var metaData = _entMan.GetComponent<MetaDataComponent>(Owner);
var metaEnts = _entMan.GetEntityQuery<MetaDataComponent>();
var metaData = metaEnts.GetComponent(Owner);
metaData.EntityPaused = mapPaused;
MapID = newMapId;
MapIdChanged(oldMapId);
var xforms = _entMan.GetEntityQuery<TransformComponent>();
var metaEnts = _entMan.GetEntityQuery<MetaDataComponent>();
UpdateChildMapIdsRecursive(MapID, mapPaused, xforms, metaEnts);
UpdateChildMapIdsRecursive(MapID, mapPaused, xformQuery, metaEnts);
}
private void UpdateChildMapIdsRecursive(MapId newMapId, bool mapPaused, EntityQuery<TransformComponent> xformQuery, EntityQuery<MetaDataComponent> metaQuery)
@@ -785,7 +794,6 @@ namespace Robust.Shared.GameObjects
var old = concrete.MapID;
concrete.MapID = newMapId;
concrete.MapIdChanged(old);
if (concrete.ChildCount != 0)
{
@@ -794,11 +802,6 @@ namespace Robust.Shared.GameObjects
}
}
private void MapIdChanged(MapId oldId)
{
_entMan.EventBus.RaiseLocalEvent(Owner, new EntMapIdChangedMessage(Owner, oldId));
}
public void AttachParent(EntityUid parent)
{
var transform = _entMan.GetComponent<TransformComponent>(parent);
@@ -929,7 +932,7 @@ namespace Robust.Shared.GameObjects
}
/// <summary>
/// Returns whether the entity of this transform contains the entity argument
/// Returns whether the given entity is a child of this transform or one of its descendants.
/// </summary>
public bool ContainsEntity(TransformComponent entityTransform)
{
@@ -938,7 +941,7 @@ namespace Robust.Shared.GameObjects
return false;
}
if (this == entityTransform.Parent) //Is this the direct container of the entity
if (this == entityTransform.Parent) //Is this the direct parent of the entity
{
return true;
}
@@ -946,7 +949,7 @@ namespace Robust.Shared.GameObjects
{
return
ContainsEntity(entityTransform
.Parent); //Recursively search up the entities containers for this object
.Parent); //Recursively search up the parents for this object
}
}
@@ -1028,7 +1031,7 @@ namespace Robust.Shared.GameObjects
RebuildMatrices();
}
Dirty();
Dirty(_entMan);
}
if (nextState is TransformComponentState nextTransform)

View File

@@ -12,6 +12,8 @@ using Robust.Shared.Utility;
using System.Runtime.CompilerServices;
using System.Threading;
using Robust.Shared.Maths;
using Robust.Shared.Log;
using System.Diagnostics;
#if EXCEPTION_TOLERANCE
using Robust.Shared.Exceptions;
#endif
@@ -192,6 +194,57 @@ namespace Robust.Shared.GameObjects
return newComponent;
}
public readonly struct CompInitializeHandle<T> : IDisposable
where T : Component
{
private readonly IEntityManager _entMan;
public readonly T Comp;
public CompInitializeHandle(IEntityManager entityManager, T comp)
{
_entMan = entityManager;
Comp = comp;
}
public void Dispose()
{
var metadata = _entMan.GetComponent<MetaDataComponent>(Comp.Owner);
if (!metadata.EntityInitialized && !metadata.EntityInitializing)
return;
if (!Comp.Initialized)
Comp.LifeInitialize(_entMan);
if (metadata.EntityInitialized && !Comp.Running)
Comp.LifeStartup(_entMan);
}
public static implicit operator T(CompInitializeHandle<T> handle)
{
return handle.Comp;
}
}
/// <inheritdoc />
public CompInitializeHandle<T> AddComponentUninitialized<T>(EntityUid uid) where T : Component, new()
{
var newComponent = _componentFactory.GetComponent<T>();
newComponent.Owner = uid;
if (!uid.IsValid() || !EntityExists(uid))
throw new ArgumentException("Entity is not valid.", nameof(uid));
if (newComponent == null) throw new ArgumentNullException(nameof(newComponent));
if (newComponent.Owner != uid) throw new InvalidOperationException("Component is not owned by entity.");
AddComponentInternal(uid, newComponent, false, true);
return new CompInitializeHandle<T>(this, newComponent);
}
/// <inheritdoc />
public void AddComponent<T>(EntityUid uid, T component, bool overwrite = false) where T : Component
{
if (!uid.IsValid() || !EntityExists(uid))
@@ -201,10 +254,10 @@ namespace Robust.Shared.GameObjects
if (component.Owner != uid) throw new InvalidOperationException("Component is not owned by entity.");
AddComponentInternal(uid, component, overwrite);
AddComponentInternal(uid, component, overwrite, false);
}
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite = false) where T : Component
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit) where T : Component
{
// get interface aliases for mapping
var reg = _componentFactory.GetRegistration(component);
@@ -256,6 +309,9 @@ namespace Robust.Shared.GameObjects
component.LifeAddToEntity(this);
if (skipInit)
return;
var metadata = GetComponent<MetaDataComponent>(uid);
if (!metadata.EntityInitialized && !metadata.EntityInitializing)
@@ -1046,5 +1102,26 @@ namespace Robust.Shared.GameObjects
{
return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
{
if (component != null)
{
DebugTools.Assert(uid == component.Owner, "Specified Entity is not the component's Owner!");
return true;
}
if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted)
{
component = (TComp1)comp;
return true;
}
if (logMissing)
Logger.ErrorS("resolve", $"Can't resolve \"{typeof(TComp1)}\" on entity {uid}!\n{new StackTrace(1, true)}");
return false;
}
}
}

View File

@@ -2,8 +2,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Prometheus;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
@@ -16,10 +18,11 @@ namespace Robust.Shared.GameObjects
{
#region Dependencies
[IoC.Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ISerializationManager _serManager = default!;
#endregion Dependencies
@@ -29,7 +32,7 @@ namespace Robust.Shared.GameObjects
IComponentFactory IEntityManager.ComponentFactory => ComponentFactory;
/// <inheritdoc />
public IEntitySystemManager EntitySysManager => EntitySystemManager;
public IEntitySystemManager EntitySysManager => _entitySystemManager;
/// <inheritdoc />
public virtual IEntityNetworkManager? EntityNetManager => null;
@@ -83,7 +86,7 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException("Startup() called multiple times");
// TODO: Probably better to call this on its own given it's so infrequent.
EntitySystemManager.Initialize();
_entitySystemManager.Initialize();
Started = true;
}
@@ -91,7 +94,7 @@ namespace Robust.Shared.GameObjects
{
FlushEntities();
_eventBus.ClearEventTables();
EntitySystemManager.Shutdown();
_entitySystemManager.Shutdown();
ClearComponents();
Initialized = false;
Started = false;
@@ -101,7 +104,7 @@ namespace Robust.Shared.GameObjects
{
QueuedDeletions.Clear();
QueuedDeletionsSet.Clear();
EntitySystemManager.Clear();
_entitySystemManager.Clear();
Entities.Clear();
_eventBus.Dispose();
_eventBus = null!;
@@ -115,7 +118,7 @@ namespace Robust.Shared.GameObjects
{
using (histogram?.WithLabels("EntitySystems").NewTimer())
{
EntitySystemManager.TickUpdate(frameTime, noPredictions);
_entitySystemManager.TickUpdate(frameTime, noPredictions);
}
using (histogram?.WithLabels("EntityEventBus").NewTimer())
@@ -141,7 +144,7 @@ namespace Robust.Shared.GameObjects
public virtual void FrameUpdate(float frameTime)
{
EntitySystemManager.FrameUpdate(frameTime);
_entitySystemManager.FrameUpdate(frameTime);
}
#region Entity Management
@@ -395,7 +398,7 @@ namespace Robust.Shared.GameObjects
Entities.Add(uid);
// add the required MetaDataComponent directly.
AddComponentInternal(uid, metadata);
AddComponentInternal(uid, metadata, false, false);
// allocate the required TransformComponent
AddComponent<TransformComponent>(uid);
@@ -414,7 +417,7 @@ namespace Robust.Shared.GameObjects
var entity = AllocEntity(prototypeName, uid);
try
{
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, null);
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, null);
return entity;
}
catch (Exception e)
@@ -428,7 +431,7 @@ namespace Robust.Shared.GameObjects
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
{
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, context);
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
}
private void InitializeAndStartEntity(EntityUid entity, MapId mapId)
@@ -475,31 +478,7 @@ namespace Robust.Shared.GameObjects
#endregion Entity Management
protected void DispatchComponentMessage(NetworkComponentMessage netMsg)
{
var compMsg = netMsg.Message;
var compChannel = netMsg.Channel;
var session = netMsg.Session;
compMsg.Remote = true;
#pragma warning disable 618
var uid = netMsg.EntityUid;
if (compMsg.Directed)
{
if (TryGetComponent(uid, (ushort) netMsg.NetId, out var component))
component.HandleNetworkMessage(compMsg, compChannel, session);
}
else
{
foreach (var component in GetComponents(uid))
{
component.HandleNetworkMessage(compMsg, compChannel, session);
}
}
#pragma warning restore 618
}
/// <summary>
/// <summary>
/// Factory for generating a new EntityUid for an entity currently being created.
/// </summary>
/// <inheritdoc />
@@ -512,7 +491,6 @@ namespace Robust.Shared.GameObjects
public enum EntityMessageType : byte
{
Error = 0,
ComponentMessage,
SystemMessage
}
}

View File

@@ -198,7 +198,7 @@ public partial class EntitySystem
if(!Resolve(uid, ref metaData, false))
throw CompNotFound<MetaDataComponent>(uid);
return metaData.EntityName;
return metaData.EntityDescription;
}
/// <summary>

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