mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
84 Commits
v0.8.72
...
reactjs-su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de8c2c14bb | ||
|
|
baebfff321 | ||
|
|
24191404aa | ||
|
|
990842f5c2 | ||
|
|
2c2bd3f330 | ||
|
|
1ae14c4bfa | ||
|
|
61a1701dc9 | ||
|
|
c6ea11346e | ||
|
|
ad7c871e28 | ||
|
|
c77b3f8022 | ||
|
|
b263f4a1df | ||
|
|
1a11d41bba | ||
|
|
14d0a77644 | ||
|
|
b75045ce1a | ||
|
|
1d8a8e15ef | ||
|
|
28a087c267 | ||
|
|
8dc849b6bb | ||
|
|
7e56f46f35 | ||
|
|
4453a23069 | ||
|
|
57be0bc845 | ||
|
|
430910ff36 | ||
|
|
4c95807e2d | ||
|
|
92fd04552d | ||
|
|
9c7e244821 | ||
|
|
41f64b5c3c | ||
|
|
4003003580 | ||
|
|
f49341b53c | ||
|
|
61d9d9a832 | ||
|
|
c0b7dd053f | ||
|
|
f642e10acc | ||
|
|
2fb64480db | ||
|
|
075b5ec203 | ||
|
|
21f2fe6b1b | ||
|
|
89080ef7c7 | ||
|
|
f5d36dc9ca | ||
|
|
ef87610b36 | ||
|
|
c89f1e4d31 | ||
|
|
c4805d89e0 | ||
|
|
659ffb028a | ||
|
|
a14c956ccc | ||
|
|
facb3914a4 | ||
|
|
aab8f7876b | ||
|
|
1f199ea71c | ||
|
|
00b557b77a | ||
|
|
eac536cffe | ||
|
|
bf75c6f247 | ||
|
|
1518c79291 | ||
|
|
aae2f72d1a | ||
|
|
273aa3d6ea | ||
|
|
756c4510eb | ||
|
|
cd50e89aec | ||
|
|
25b648b929 | ||
|
|
1dbbb08250 | ||
|
|
5432f7311e | ||
|
|
e662802b4c | ||
|
|
bae2c390bb | ||
|
|
7976926eaf | ||
|
|
4aee730753 | ||
|
|
e87c342aa2 | ||
|
|
d335d74d8f | ||
|
|
bc68b0e3ec | ||
|
|
b1eadbc58f | ||
|
|
2d3165a16f | ||
|
|
d99d25de37 | ||
|
|
be035ab002 | ||
|
|
12c8883eea | ||
|
|
758c7c1869 | ||
|
|
72ab1f5a1a | ||
|
|
6b9c1369c0 | ||
|
|
fd1f1dc64a | ||
|
|
e211368bc4 | ||
|
|
39307f916d | ||
|
|
7378cc50a1 | ||
|
|
867b9ad932 | ||
|
|
03064255f6 | ||
|
|
c9ccd0b873 | ||
|
|
650eceea7e | ||
|
|
452ad5a6e5 | ||
|
|
7c292c75d4 | ||
|
|
cab4113697 | ||
|
|
f1a5de79b5 | ||
|
|
191e80c175 | ||
|
|
241bce56c8 | ||
|
|
b105639b31 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -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
33
.github/workflows/benchmarks.yml
vendored
Normal 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
34
.github/workflows/build-docfx.yml
vendored
Normal 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 }}
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -29,3 +29,5 @@ jobs:
|
||||
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
2
.gitignore
vendored
@@ -76,3 +76,5 @@ MSBuild/Robust.Custom.targets
|
||||
|
||||
|
||||
release/
|
||||
Robust.Docfx/*-site
|
||||
Robust.Docfx/api
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 1dd5c1f333...b723fc532e
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<PropertyGroup><Version>0.8.72</Version></PropertyGroup>
|
||||
<PropertyGroup><Version>0.8.86</Version></PropertyGroup>
|
||||
</Project>
|
||||
|
||||
53
Robust.Benchmarks/Configs/DefaultSQLConfig.cs
Normal file
53
Robust.Benchmarks/Configs/DefaultSQLConfig.cs
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
61
Robust.Benchmarks/EntityManager/GetComponentBenchmark.cs
Normal file
61
Robust.Benchmarks/EntityManager/GetComponentBenchmark.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
143
Robust.Benchmarks/Exporters/SQLExporter.cs
Normal file
143
Robust.Benchmarks/Exporters/SQLExporter.cs
Normal 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!;
|
||||
}
|
||||
55
Robust.Benchmarks/Migrations/20220328231938_InitialCreate.Designer.cs
generated
Normal file
55
Robust.Benchmarks/Migrations/20220328231938_InitialCreate.Designer.cs
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Robust.Benchmarks/Migrations/20220328231938_InitialCreate.cs
Normal file
35
Robust.Benchmarks/Migrations/20220328231938_InitialCreate.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
11
Robust.Benchmarks/add-migration.ps1
Normal file
11
Robust.Benchmarks/add-migration.ps1
Normal 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
|
||||
8
Robust.Benchmarks/add-migration.sh
Normal file
8
Robust.Benchmarks/add-migration.sh
Normal 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"
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Robust.Client.GameObjects
|
||||
[Animatable]
|
||||
Vector2 Scale { get; set; }
|
||||
|
||||
Box2 Bounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A rotation applied to all layers.
|
||||
/// </summary>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -389,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -251,6 +251,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void RunOnWindowThread(Action action)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
private sealed class DummyCursor : ICursor
|
||||
{
|
||||
public void Dispose()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,5 +63,7 @@ namespace Robust.Client.Graphics
|
||||
uint? GetX11WindowId();
|
||||
|
||||
void RegisterGridEcsEvents();
|
||||
|
||||
void RunOnWindowThread(Action action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
@@ -13,7 +12,6 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
@@ -374,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)
|
||||
|
||||
1
Robust.Docfx/articles/example-article.md
Normal file
1
Robust.Docfx/articles/example-article.md
Normal file
@@ -0,0 +1 @@
|
||||
# Add your introductions here!
|
||||
2
Robust.Docfx/articles/toc.yml
Normal file
2
Robust.Docfx/articles/toc.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
- name: Example-article
|
||||
href: example-article.md
|
||||
214
Robust.Docfx/docfx.json
Normal file
214
Robust.Docfx/docfx.json
Normal 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
6
Robust.Docfx/index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# RobustToolbox DocFX
|
||||

|
||||
|
||||
## Welcome to the RobustToolbox DocFX instance.
|
||||
### Click one of the tabs above to see documentation for that particular project/namespace
|
||||
|
||||
21
Robust.Docfx/templates/darkfx/LICENSE.txt
Normal file
21
Robust.Docfx/templates/darkfx/LICENSE.txt
Normal 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.
|
||||
40
Robust.Docfx/templates/darkfx/partials/affix.tmpl.partial
Normal file
40
Robust.Docfx/templates/darkfx/partials/affix.tmpl.partial
Normal 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>
|
||||
108
Robust.Docfx/templates/darkfx/partials/class.header.tmpl.partial
Normal file
108
Robust.Docfx/templates/darkfx/partials/class.header.tmpl.partial
Normal 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}}
|
||||
29
Robust.Docfx/templates/darkfx/partials/footer.tmpl.partial
Normal file
29
Robust.Docfx/templates/darkfx/partials/footer.tmpl.partial
Normal 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>
|
||||
20
Robust.Docfx/templates/darkfx/partials/head.tmpl.partial
Normal file
20
Robust.Docfx/templates/darkfx/partials/head.tmpl.partial
Normal 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>
|
||||
470
Robust.Docfx/templates/darkfx/styles/main.css
Normal file
470
Robust.Docfx/templates/darkfx/styles/main.css
Normal 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;
|
||||
}
|
||||
}
|
||||
35
Robust.Docfx/templates/darkfx/styles/toggle-theme.js
Normal file
35
Robust.Docfx/templates/darkfx/styles/toggle-theme.js
Normal 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
29
Robust.Docfx/toc.yml
Normal 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/
|
||||
@@ -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))
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +20,7 @@ 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!;
|
||||
@@ -81,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()
|
||||
{
|
||||
@@ -102,6 +113,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
SubscribeLocalEvent<MoveEvent>(OnEntityMove);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<TransformComponent, ComponentStartup>(OnTransformStartup);
|
||||
EntityManager.EntityDeleted += OnEntityDeleted;
|
||||
|
||||
@@ -111,6 +123,17 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
InitializeDirty();
|
||||
}
|
||||
|
||||
private void OnParentChange(ref EntParentChangedMessage ev)
|
||||
{
|
||||
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()
|
||||
{
|
||||
@@ -162,6 +185,11 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
|
||||
CleanupDirty(playerSessions);
|
||||
|
||||
foreach (var collection in _pvsCollections)
|
||||
{
|
||||
collection.ClearDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void CullDeletionHistory(GameTick oldestAck)
|
||||
@@ -293,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++)
|
||||
{
|
||||
@@ -309,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)
|
||||
{
|
||||
@@ -321,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);
|
||||
@@ -340,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);
|
||||
|
||||
@@ -372,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,
|
||||
@@ -399,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)
|
||||
@@ -407,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);
|
||||
@@ -474,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,
|
||||
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;
|
||||
@@ -549,7 +643,8 @@ 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,
|
||||
@@ -566,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,
|
||||
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, in enteredEntityBudget, in newEntityBudget);
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
RecursivelyAddTreeNode(in child, tree, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent,
|
||||
ref totalEnteredEntities, metaDataCache, in enteredEntityBudget, in newEntityBudget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,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)
|
||||
@@ -848,7 +957,10 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
return viewers;
|
||||
var viewerArray = viewers.ToArray();
|
||||
|
||||
_uidSetPool.Return(viewers);
|
||||
return viewerArray;
|
||||
}
|
||||
|
||||
// Read Safe
|
||||
@@ -872,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()
|
||||
@@ -902,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)
|
||||
@@ -916,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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -230,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);
|
||||
|
||||
|
||||
@@ -786,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,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)
|
||||
{
|
||||
@@ -871,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;
|
||||
@@ -938,29 +940,6 @@ namespace Robust.Server.Maps
|
||||
return CurrentReadingEntityComponents!.Keys;
|
||||
}
|
||||
|
||||
private bool IsMapSavable(EntityUid entity)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class MapLoadException : Exception
|
||||
{
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace Robust.Server.Player
|
||||
|
||||
IReadOnlySet<EntityUid> ViewSubscriptions { get; }
|
||||
|
||||
int ViewSubscriptionCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Persistent data for this player.
|
||||
/// </summary>
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Robust.Server.Player
|
||||
}
|
||||
|
||||
[ViewVariables] public IReadOnlySet<EntityUid> ViewSubscriptions => _viewSubscriptions;
|
||||
public int ViewSubscriptionCount => _viewSubscriptions.Count;
|
||||
|
||||
[ViewVariables] public INetChannel ConnectedClient { get; }
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +355,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// offset position from world to parent
|
||||
_parent = value.EntityId;
|
||||
var oldMapId = MapID;
|
||||
ChangeMapId(newParent.MapID, xformQuery);
|
||||
|
||||
// preserve world rotation
|
||||
@@ -364,7 +365,7 @@ namespace Robust.Shared.GameObjects
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -381,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);
|
||||
@@ -733,15 +734,13 @@ 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(_entMan);
|
||||
}
|
||||
|
||||
@@ -778,7 +777,6 @@ namespace Robust.Shared.GameObjects
|
||||
metaData.EntityPaused = mapPaused;
|
||||
|
||||
MapID = newMapId;
|
||||
MapIdChanged(oldMapId);
|
||||
UpdateChildMapIdsRecursive(MapID, mapPaused, xformQuery, metaEnts);
|
||||
}
|
||||
|
||||
@@ -796,7 +794,6 @@ namespace Robust.Shared.GameObjects
|
||||
var old = concrete.MapID;
|
||||
|
||||
concrete.MapID = newMapId;
|
||||
concrete.MapIdChanged(old);
|
||||
|
||||
if (concrete.ChildCount != 0)
|
||||
{
|
||||
@@ -805,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);
|
||||
@@ -940,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)
|
||||
{
|
||||
@@ -949,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;
|
||||
}
|
||||
@@ -957,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -1100,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
@@ -18,9 +19,10 @@ namespace Robust.Shared.GameObjects
|
||||
#region Dependencies
|
||||
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly IEntitySystemManager EntitySystemManager = 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
|
||||
|
||||
@@ -30,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;
|
||||
@@ -84,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;
|
||||
}
|
||||
|
||||
@@ -92,7 +94,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
FlushEntities();
|
||||
_eventBus.ClearEventTables();
|
||||
EntitySystemManager.Shutdown();
|
||||
_entitySystemManager.Shutdown();
|
||||
ClearComponents();
|
||||
Initialized = false;
|
||||
Started = false;
|
||||
@@ -102,7 +104,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
QueuedDeletions.Clear();
|
||||
QueuedDeletionsSet.Clear();
|
||||
EntitySystemManager.Clear();
|
||||
_entitySystemManager.Clear();
|
||||
Entities.Clear();
|
||||
_eventBus.Dispose();
|
||||
_eventBus = null!;
|
||||
@@ -116,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())
|
||||
@@ -142,7 +144,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public virtual void FrameUpdate(float frameTime)
|
||||
{
|
||||
EntitySystemManager.FrameUpdate(frameTime);
|
||||
_entitySystemManager.FrameUpdate(frameTime);
|
||||
}
|
||||
|
||||
#region Entity Management
|
||||
@@ -415,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)
|
||||
@@ -429,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)
|
||||
@@ -476,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 />
|
||||
@@ -513,7 +491,6 @@ namespace Robust.Shared.GameObjects
|
||||
public enum EntityMessageType : byte
|
||||
{
|
||||
Error = 0,
|
||||
ComponentMessage,
|
||||
SystemMessage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntitySystemManager : IEntitySystemManager
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[IoC.Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
@@ -63,6 +63,12 @@ namespace Robust.Shared.GameObjects
|
||||
return _systemDependencyCollection.Resolve<T>();
|
||||
}
|
||||
|
||||
public T? GetEntitySystemOrNull<T>() where T : IEntitySystem
|
||||
{
|
||||
_systemDependencyCollection.TryResolveType<T>(out var system);
|
||||
return system;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||
public void Resolve<T>([NotNull] ref T? instance)
|
||||
where T : IEntitySystem
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntMapIdChangedMessage : EntityEventArgs
|
||||
{
|
||||
public EntMapIdChangedMessage(EntityUid entity, MapId oldMapId)
|
||||
{
|
||||
Entity = entity;
|
||||
OldMapId = oldMapId;
|
||||
}
|
||||
|
||||
public EntityUid Entity { get; }
|
||||
public MapId OldMapId { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when an entity parent is changed.
|
||||
@@ -16,15 +18,26 @@
|
||||
/// </summary>
|
||||
public EntityUid? OldParent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The map Id that the entity was on before its parent changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the old parent was detached to null without manually updating the map ID of its children, then this
|
||||
/// is required as we cannot simply use the old parent's map ID. Also avoids having to fetch the old
|
||||
/// parent's transform component.
|
||||
/// </remarks>
|
||||
public MapId OldMapId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="EntParentChangedMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="oldParent"></param>
|
||||
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent)
|
||||
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent, MapId oldMapId)
|
||||
{
|
||||
Entity = entity;
|
||||
OldParent = oldParent;
|
||||
OldMapId = oldMapId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,23 +69,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
GameTick LastModifiedTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles a local incoming component message.
|
||||
/// </summary>
|
||||
/// <param name="message">Incoming event message.</param>
|
||||
/// <param name="component">The local component that sent the message.</param>
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
void HandleMessage(ComponentMessage message, IComponent? component);
|
||||
|
||||
/// <summary>
|
||||
/// Handles an incoming component message from the server.
|
||||
/// </summary>
|
||||
/// <param name="message">Incoming event message.</param>
|
||||
/// <param name="netChannel">The channel of the remote client that sent the message.</param>
|
||||
/// <param name="session">The session data for the player who sent this message. Null if this is a client.</param>
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get the component's state for replicating on the client.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,12 +9,7 @@ namespace Robust.Shared.GameObjects
|
||||
public interface IEntityNetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// This event is raised when a component message comes in from the network.
|
||||
/// </summary>
|
||||
event EventHandler<NetworkComponentMessage> ReceivedComponentMessage;
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when a component message comes in from the network.
|
||||
/// This event is raised when a system message comes in from the network.
|
||||
/// </summary>
|
||||
event EventHandler<object> ReceivedSystemMessage;
|
||||
|
||||
@@ -23,21 +18,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
void SetupNetworking();
|
||||
|
||||
/// <summary>
|
||||
/// Allows a component owned by this entity to send a message to a counterpart component on the
|
||||
/// counterpart entities on clients.
|
||||
/// </summary>
|
||||
/// <param name="channel">
|
||||
/// Intended recipient of the message. On Server, if this is null, broadcast the message to all clients.
|
||||
/// On clients, this should always be null.
|
||||
/// </param>
|
||||
/// <param name="entity">Entity sending the message (also entity to send to).</param>
|
||||
/// <param name="component">Component that sent the message.</param>
|
||||
/// <param name="message">Message to send.</param>
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
void SendComponentNetworkMessage(INetChannel? channel, EntityUid entity, IComponent component,
|
||||
ComponentMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends an Entity System Message to relevant System(s).
|
||||
/// Client: Sends the message to the relevant server side System(s).
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
|
||||
@@ -40,6 +39,13 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>The <see cref="IEntitySystem"/> instance matching the specified type.</returns>
|
||||
T GetEntitySystem<T>() where T : IEntitySystem;
|
||||
|
||||
/// <summary>
|
||||
/// Get an entity system of the specified type, or null if it is not registered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of entity system to find.</typeparam>
|
||||
/// <returns>The <see cref="IEntitySystem"/> instance matching the specified type, or null.</returns>
|
||||
T? GetEntitySystemOrNull<T>() where T : IEntitySystem;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves an entity system.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// A container that holds a component message and network info.
|
||||
/// </summary>
|
||||
public readonly struct NetworkComponentMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Network channel this message came from.
|
||||
/// </summary>
|
||||
public readonly INetChannel Channel;
|
||||
|
||||
/// <summary>
|
||||
/// Entity Uid this message is associated with.
|
||||
/// </summary>
|
||||
public readonly EntityUid EntityUid;
|
||||
|
||||
/// <summary>
|
||||
/// If the Message is Directed, Component net Uid this message is being sent to.
|
||||
/// </summary>
|
||||
public readonly uint NetId;
|
||||
|
||||
/// <summary>
|
||||
/// The message payload.
|
||||
/// </summary>
|
||||
#pragma warning disable 618
|
||||
public readonly ComponentMessage Message;
|
||||
#pragma warning restore 618
|
||||
|
||||
/// <summary>
|
||||
/// If the message is from the client, the client's session.
|
||||
/// </summary>
|
||||
public readonly ICommonSession? Session;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="NetworkComponentMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="netMsg">Raw network message containing the component message.</param>
|
||||
public NetworkComponentMessage(MsgEntity netMsg, ICommonSession? session = null)
|
||||
{
|
||||
DebugTools.Assert(netMsg.Type == EntityMessageType.ComponentMessage);
|
||||
|
||||
Channel = netMsg.MsgChannel;
|
||||
EntityUid = netMsg.EntityUid;
|
||||
NetId = netMsg.NetId;
|
||||
Message = netMsg.ComponentMessage;
|
||||
Session = session;
|
||||
}
|
||||
}
|
||||
|
||||
#nullable restore
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
@@ -11,9 +12,11 @@ namespace Robust.Shared.GameObjects
|
||||
SubscribeLocalEvent<CollisionWakeComponent, EntityInitializedMessage>(HandleInitialize);
|
||||
SubscribeLocalEvent<CollisionWakeComponent, ComponentRemove>(HandleRemove);
|
||||
|
||||
SubscribeLocalEvent<CollisionWakeComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<CollisionWakeComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
SubscribeLocalEvent<CollisionWakeComponent, PhysicsWakeMessage>(HandleWake);
|
||||
SubscribeLocalEvent<CollisionWakeComponent, PhysicsSleepMessage>(HandleSleep);
|
||||
SubscribeLocalEvent<CollisionWakeComponent, CollisionWakeStateMessage>(HandleCollisionWakeState);
|
||||
|
||||
SubscribeLocalEvent<CollisionWakeComponent, JointAddedEvent>(HandleJointAdd);
|
||||
SubscribeLocalEvent<CollisionWakeComponent, JointRemovedEvent>(HandleJointRemove);
|
||||
@@ -21,73 +24,94 @@ namespace Robust.Shared.GameObjects
|
||||
SubscribeLocalEvent<CollisionWakeComponent, EntParentChangedMessage>(HandleParentChange);
|
||||
}
|
||||
|
||||
public void SetEnabled(EntityUid uid, bool enabled, CollisionWakeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || component.Enabled == enabled)
|
||||
return;
|
||||
|
||||
component.Enabled = enabled;
|
||||
|
||||
if (component.Enabled)
|
||||
UpdateCanCollide(uid, component);
|
||||
else if (TryComp(uid, out PhysicsComponent? physics))
|
||||
physics.CanCollide = true;
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, CollisionWakeComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is CollisionWakeComponent.CollisionWakeState state)
|
||||
component.Enabled = state.Enabled;
|
||||
|
||||
// Note, this explicitly does not update PhysicsComponent.CanCollide. The physics component should perform
|
||||
// its own state-handling logic. Additionally, if we wanted to set it you would have to ensure that things
|
||||
// like the join-component and physics component have already handled their states, otherwise CanCollide may
|
||||
// be set incorrectly and leave the client with a bad state.
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, CollisionWakeComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new CollisionWakeComponent.CollisionWakeState(component.Enabled);
|
||||
}
|
||||
|
||||
private void HandleRemove(EntityUid uid, CollisionWakeComponent component, ComponentRemove args)
|
||||
{
|
||||
if (!Terminating(uid) && TryComp(uid, out PhysicsComponent? physics))
|
||||
if (component.Enabled
|
||||
&& !Terminating(uid)
|
||||
&& TryComp(uid, out PhysicsComponent? physics))
|
||||
{
|
||||
physics.CanCollide = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanRaiseEvent(EntityUid uid)
|
||||
{
|
||||
var meta = MetaData(uid);
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Terminating) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleParentChange(EntityUid uid, CollisionWakeComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (!CanRaiseEvent(uid)) return;
|
||||
|
||||
component.RaiseStateChange();
|
||||
UpdateCanCollide(uid, component);
|
||||
}
|
||||
|
||||
private void HandleInitialize(EntityUid uid, CollisionWakeComponent component, EntityInitializedMessage args)
|
||||
{
|
||||
component.RaiseStateChange();
|
||||
UpdateCanCollide(uid, component, checkTerminating: false);
|
||||
}
|
||||
|
||||
private void HandleJointRemove(EntityUid uid, CollisionWakeComponent component, JointRemovedEvent args)
|
||||
{
|
||||
if (!CanRaiseEvent(uid)) return;
|
||||
|
||||
component.RaiseStateChange();
|
||||
UpdateCanCollide(uid, component, args.OurBody);
|
||||
}
|
||||
|
||||
private void HandleJointAdd(EntityUid uid, CollisionWakeComponent component, JointAddedEvent args)
|
||||
{
|
||||
component.RaiseStateChange();
|
||||
// Bypass UpdateCanCollide() as joint count will always be bigger than 0:
|
||||
if (component.Enabled)
|
||||
args.OurBody.CanCollide = true;
|
||||
}
|
||||
|
||||
private void HandleWake(EntityUid uid, CollisionWakeComponent component, PhysicsWakeMessage args)
|
||||
{
|
||||
component.RaiseStateChange();
|
||||
UpdateCanCollide(uid, component, args.Body, false);
|
||||
}
|
||||
|
||||
private void HandleSleep(EntityUid uid, CollisionWakeComponent component, PhysicsSleepMessage args)
|
||||
{
|
||||
if (!CanRaiseEvent(uid)) return;
|
||||
|
||||
component.RaiseStateChange();
|
||||
UpdateCanCollide(uid, component, args.Body);
|
||||
}
|
||||
|
||||
private void HandleCollisionWakeState(EntityUid uid, CollisionWakeComponent component, CollisionWakeStateMessage args)
|
||||
private void UpdateCanCollide(EntityUid uid, CollisionWakeComponent component, IPhysBody? body = null, bool checkTerminating = true)
|
||||
{
|
||||
// If you really wanted you could optimise for each use case above and save some calls but
|
||||
// these are called pretty infrequently so I'm fine with this for now.
|
||||
if (!component.Enabled)
|
||||
return;
|
||||
|
||||
// If we just got put into a container don't want to mess with our collision state.
|
||||
if (!EntityManager.TryGetComponent<PhysicsComponent>(uid, out var body)) return;
|
||||
if (checkTerminating && Terminating(uid))
|
||||
return;
|
||||
|
||||
if (!Resolve(uid, ref body, false))
|
||||
return;
|
||||
|
||||
// If we're attached to the map we'll also just never disable collision due to how grid movement works.
|
||||
body.CanCollide = !component.Enabled ||
|
||||
body.Awake ||
|
||||
(EntityManager.TryGetComponent(uid, out JointComponent? jointComponent) && jointComponent.JointCount > 0) ||
|
||||
EntityManager.GetComponent<TransformComponent>(uid).GridID == GridId.Invalid;
|
||||
body.CanCollide = body.Awake ||
|
||||
(TryComp(uid, out JointComponent? jointComponent) && jointComponent.JointCount > 0) ||
|
||||
Transform(uid).GridID == GridId.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CollisionWakeStateMessage : EntityEventArgs { }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -50,7 +51,6 @@ namespace Robust.Shared.GameObjects
|
||||
SubscribeLocalEvent<RotateEvent>(OnRotate);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<AnchorStateChangedEvent>(OnAnchored);
|
||||
SubscribeLocalEvent<UpdateLookupBoundsEvent>(OnBoundsUpdate);
|
||||
|
||||
SubscribeLocalEvent<EntityLookupComponent, ComponentAdd>(OnLookupAdd);
|
||||
SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(OnLookupShutdown);
|
||||
@@ -68,6 +68,33 @@ namespace Robust.Shared.GameObjects
|
||||
EntityManager.EntityInitialized -= OnEntityInit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the entity's AABB. Uses <see cref="ILookupWorldBox2Component"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public void UpdateBounds(EntityUid uid, TransformComponent? xform = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_container.IsEntityInContainer(uid, meta))
|
||||
return;
|
||||
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!xformQuery.Resolve(uid, ref xform) || xform.Anchored)
|
||||
return;
|
||||
|
||||
var lookup = GetLookup(uid, xform, xformQuery);
|
||||
|
||||
if (lookup == null) return;
|
||||
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
// If we're contained then LocalRotation should be 0 anyway.
|
||||
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
|
||||
|
||||
// TODO: Only container children need updating so could manually do this slightly better.
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery);
|
||||
}
|
||||
|
||||
private void OnAnchored(ref AnchorStateChangedEvent args)
|
||||
{
|
||||
// This event needs to be handled immediately as anchoring is handled immediately
|
||||
@@ -198,7 +225,7 @@ namespace Robust.Shared.GameObjects
|
||||
private void UpdatePosition(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
// Even if the entity is contained it may have children that aren't so we still need to update.
|
||||
if (!CanMoveUpdate(uid, xform)) return;
|
||||
if (!CanMoveUpdate(uid)) return;
|
||||
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var lookup = GetLookup(uid, xform, xformQuery);
|
||||
@@ -211,11 +238,11 @@ namespace Robust.Shared.GameObjects
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery);
|
||||
}
|
||||
|
||||
private bool CanMoveUpdate(EntityUid uid, TransformComponent xform)
|
||||
private bool CanMoveUpdate(EntityUid uid)
|
||||
{
|
||||
return !_mapManager.IsMap(uid) &&
|
||||
!_mapManager.IsGrid(uid) &&
|
||||
!_container.IsEntityInContainer(uid, xform);
|
||||
!_container.IsEntityInContainer(uid);
|
||||
}
|
||||
|
||||
private void OnParentChange(ref EntParentChangedMessage args)
|
||||
@@ -244,26 +271,6 @@ namespace Robust.Shared.GameObjects
|
||||
AddToEntityTree(newLookup, xform, xformQuery);
|
||||
}
|
||||
|
||||
private void OnBoundsUpdate(ref UpdateLookupBoundsEvent ev)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xform = xformQuery.GetComponent(ev.Uid);
|
||||
|
||||
if (xform.Anchored || _container.IsEntityInContainer(ev.Uid, xform)) return;
|
||||
|
||||
var lookup = GetLookup(ev.Uid, xform, xformQuery);
|
||||
|
||||
if (lookup == null) return;
|
||||
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
// If we're contained then LocalRotation should be 0 anyway.
|
||||
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
|
||||
|
||||
// TODO: Only container children need updating so could manually do this slightly better.
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery);
|
||||
}
|
||||
|
||||
private void AddToEntityTree(
|
||||
EntityLookupComponent lookup,
|
||||
TransformComponent xform,
|
||||
@@ -425,7 +432,7 @@ namespace Robust.Shared.GameObjects
|
||||
Box2 localAABB;
|
||||
var transform = new Transform(position, angle);
|
||||
|
||||
if (EntityManager.TryGetComponent<ILookupWorldBox2Component>(uid, out var worldLookup))
|
||||
if (TryComp<ILookupWorldBox2Component>(uid, out var worldLookup))
|
||||
{
|
||||
localAABB = worldLookup.GetAABB(transform);
|
||||
}
|
||||
@@ -449,17 +456,3 @@ namespace Robust.Shared.GameObjects
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flags this entity for an update to their lookup bounds.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct UpdateLookupBoundsEvent
|
||||
{
|
||||
public readonly EntityUid Uid;
|
||||
|
||||
public UpdateLookupBoundsEvent(EntityUid uid)
|
||||
{
|
||||
Uid = uid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,15 +44,17 @@ public sealed class MetaDataSystem : EntitySystem
|
||||
/// </summary>
|
||||
public void RemoveFlag(EntityUid uid, MetaDataFlags flags, MetaDataComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) ||
|
||||
(component.Flags & flags) == 0x0) return;
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var ev = new MetaFlagRemoveAttemptEvent();
|
||||
var toRemove = component.Flags & flags;
|
||||
if (toRemove == 0x0)
|
||||
return;
|
||||
|
||||
var ev = new MetaFlagRemoveAttemptEvent(toRemove);
|
||||
EntityManager.EventBus.RaiseLocalEvent(component.Owner, ref ev);
|
||||
|
||||
if (ev.Cancelled) return;
|
||||
|
||||
component.Flags &= ~flags;
|
||||
component.Flags &= ~ev.ToRemove;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +64,10 @@ public sealed class MetaDataSystem : EntitySystem
|
||||
[ByRefEvent]
|
||||
public struct MetaFlagRemoveAttemptEvent
|
||||
{
|
||||
public bool Cancelled = false;
|
||||
public MetaDataFlags ToRemove;
|
||||
|
||||
public MetaFlagRemoveAttemptEvent()
|
||||
public MetaFlagRemoveAttemptEvent(MetaDataFlags toRemove)
|
||||
{
|
||||
ToRemove = toRemove;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Shared.GameObjects
|
||||
base.Initialize();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
|
||||
|
||||
SubscribeLocalEvent<MapChangedEvent>(ev =>
|
||||
{
|
||||
if (ev.Created)
|
||||
@@ -173,6 +173,16 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, in Ray ray, float maxLength,
|
||||
Func<EntityUid, bool>? predicate = null, bool returnOnFirstHit = true)
|
||||
{
|
||||
// ReSharper disable once ConvertToLocalFunction
|
||||
var wrapper = (EntityUid uid, Func<EntityUid, bool>? wrapped)
|
||||
=> wrapped != null && wrapped(uid);
|
||||
|
||||
return IntersectRayWithPredicate(mapId, in ray, maxLength, predicate, wrapper, returnOnFirstHit);
|
||||
}
|
||||
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate<TState>(MapId mapId, in Ray ray, float maxLength,
|
||||
TState state, Func<EntityUid, TState, bool> predicate, bool returnOnFirstHit = true)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<RayCastResults>();
|
||||
var list = new List<RayCastResults>();
|
||||
@@ -191,7 +201,7 @@ namespace Robust.Shared.GameObjects
|
||||
var treeRay = new Ray(matrix.Transform(ray.Position), relativeAngle);
|
||||
|
||||
comp.Tree.QueryRay(ref list,
|
||||
(ref List<RayCastResults> state, in OccluderComponent value, in Vector2 point, float distFromOrigin) =>
|
||||
(ref List<RayCastResults> listState, in OccluderComponent value, in Vector2 point, float distFromOrigin) =>
|
||||
{
|
||||
if (distFromOrigin > maxLength)
|
||||
return true;
|
||||
@@ -199,11 +209,11 @@ namespace Robust.Shared.GameObjects
|
||||
if (!value.Enabled)
|
||||
return true;
|
||||
|
||||
if (predicate != null && predicate.Invoke(value.Owner))
|
||||
if (predicate.Invoke(value.Owner, state))
|
||||
return true;
|
||||
|
||||
var result = new RayCastResults(distFromOrigin, point, value.Owner);
|
||||
state.Add(result);
|
||||
listState.Add(result);
|
||||
return !returnOnFirstHit;
|
||||
}, treeRay);
|
||||
}
|
||||
|
||||
@@ -11,12 +11,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
internal sealed class SharedGridTraversalSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
private Stack<MoveEvent> _queuedEvents = new();
|
||||
private HashSet<EntityUid> _handledThisTick = new();
|
||||
|
||||
private List<MapGrid> _gridBuffer = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -38,22 +40,32 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var maps = EntityManager.GetEntityQuery<MapComponent>();
|
||||
var grids = EntityManager.GetEntityQuery<MapGridComponent>();
|
||||
var bodies = GetEntityQuery<PhysicsComponent>();
|
||||
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var metas = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
while (_queuedEvents.TryPop(out var moveEvent))
|
||||
{
|
||||
if (!_handledThisTick.Add(moveEvent.Sender)) continue;
|
||||
HandleMove(ref moveEvent, xforms, maps, grids);
|
||||
|
||||
HandleMove(ref moveEvent, xforms, bodies, maps, grids, metas);
|
||||
}
|
||||
|
||||
_handledThisTick.Clear();
|
||||
}
|
||||
|
||||
private void HandleMove(ref MoveEvent moveEvent, EntityQuery<TransformComponent> xforms, EntityQuery<MapComponent> maps, EntityQuery<MapGridComponent> grids)
|
||||
private void HandleMove(
|
||||
ref MoveEvent moveEvent,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
EntityQuery<PhysicsComponent> bodies,
|
||||
EntityQuery<MapComponent> maps,
|
||||
EntityQuery<MapGridComponent> grids,
|
||||
EntityQuery<MetaDataComponent> metas)
|
||||
{
|
||||
var entity = moveEvent.Sender;
|
||||
|
||||
if (Deleted(entity) ||
|
||||
if (!metas.TryGetComponent(entity, out MetaDataComponent? meta) ||
|
||||
meta.EntityDeleted ||
|
||||
maps.HasComponent(entity) ||
|
||||
grids.HasComponent(entity))
|
||||
{
|
||||
@@ -63,12 +75,13 @@ namespace Robust.Shared.GameObjects
|
||||
var xform = xforms.GetComponent(entity);
|
||||
DebugTools.Assert(!float.IsNaN(moveEvent.NewPosition.X) && !float.IsNaN(moveEvent.NewPosition.Y));
|
||||
|
||||
if (_container.IsEntityInContainer(entity, xform)) return;
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer) return;
|
||||
|
||||
var mapPos = moveEvent.NewPosition.ToMapPos(EntityManager);
|
||||
_gridBuffer.Clear();
|
||||
|
||||
// Change parent if necessary
|
||||
if (_mapManager.TryFindGridAt(xform.MapID, mapPos, out var grid) &&
|
||||
if (_mapManager.TryFindGridAt(xform.MapID, mapPos, _gridBuffer, xforms, bodies, out var grid) &&
|
||||
// TODO: Do we even need this?
|
||||
EntityManager.EntityExists(grid.GridEntityId) &&
|
||||
grid.GridEntityId != entity)
|
||||
|
||||
@@ -35,7 +35,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsWakeMessage(component));
|
||||
}
|
||||
|
||||
if (!_containerSystem.IsEntityInContainer(uid, xform))
|
||||
if (!_containerSystem.IsEntityInContainer(uid))
|
||||
{
|
||||
// TODO: Probably a bad idea but ehh future sloth's problem; namely that we have to duplicate code between here and CanCollide.
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, new CollisionChangeMessage(component, uid, component._canCollide));
|
||||
|
||||
@@ -94,6 +94,45 @@ namespace Robust.Shared.GameObjects
|
||||
return entities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all the entities whose fixtures intersect the fixtures of the given entity. Basically a variant of
|
||||
/// <see cref="GetCollidingEntities(PhysicsComponent, Vector2, bool)"/> that allows the user to specify
|
||||
/// their own collision mask.
|
||||
/// </summary>
|
||||
public HashSet<EntityUid> GetEntitiesIntersectingBody(
|
||||
EntityUid uid,
|
||||
int collisionMask,
|
||||
bool approximate = true,
|
||||
PhysicsComponent? body = null,
|
||||
FixturesComponent? fixtureComp = null)
|
||||
{
|
||||
var entities = new HashSet<EntityUid>();
|
||||
|
||||
if (!Resolve(uid, ref body, ref fixtureComp, false) || body.Broadphase == null)
|
||||
return entities;
|
||||
|
||||
var state = (body, entities);
|
||||
|
||||
foreach (var (_, fixture) in fixtureComp.Fixtures)
|
||||
{
|
||||
foreach (var proxy in fixture.Proxies)
|
||||
{
|
||||
body.Broadphase.Tree.QueryAabb(ref state,
|
||||
(ref (PhysicsComponent body, HashSet<EntityUid> entities) state,
|
||||
in FixtureProxy other) =>
|
||||
{
|
||||
if (other.Fixture.Body.Deleted || other.Fixture.Body == body) return true;
|
||||
if ((collisionMask & other.Fixture.CollisionLayer) == 0x0) return true;
|
||||
|
||||
state.entities.Add(other.Fixture.Body.Owner);
|
||||
return true;
|
||||
}, proxy.AABB, approximate);
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all entities colliding with a certain body.
|
||||
/// </summary>
|
||||
@@ -168,6 +207,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
#region RayCast
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified)
|
||||
/// </summary>
|
||||
@@ -177,9 +217,32 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="predicate">A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored.</param>
|
||||
/// <param name="returnOnFirstHit">If true, will only include the first hit entity in results. Otherwise, returns all of them.</param>
|
||||
/// <returns>A result object describing the hit, if any.</returns>
|
||||
// TODO: Make the parameter order here consistent with the other overload.
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, CollisionRay ray,
|
||||
float maxLength = 50F,
|
||||
Func<EntityUid, bool>? predicate = null, bool returnOnFirstHit = true)
|
||||
float maxLength = 50F, Func<EntityUid, bool>? predicate = null, bool returnOnFirstHit = true)
|
||||
{
|
||||
// No, rider. This is better than a local function!
|
||||
// ReSharper disable once ConvertToLocalFunction
|
||||
var wrapper =
|
||||
(EntityUid uid, Func<EntityUid, bool>? wrapped)
|
||||
=> wrapped != null && wrapped(uid);
|
||||
|
||||
return IntersectRayWithPredicate(mapId, ray, predicate, wrapper, maxLength, returnOnFirstHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified)
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="state">A custom state to pass to the predicate.</param>
|
||||
/// <param name="predicate">A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored.</param>
|
||||
/// <param name="returnOnFirstHit">If true, will only include the first hit entity in results. Otherwise, returns all of them.</param>
|
||||
/// <remarks>You can avoid variable capture in many cases by using this method and passing a custom state to the predicate.</remarks>
|
||||
/// <returns>A result object describing the hit, if any.</returns>
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate<TState>(MapId mapId, CollisionRay ray, TState state,
|
||||
Func<EntityUid, TState, bool> predicate, float maxLength = 50F, bool returnOnFirstHit = true)
|
||||
{
|
||||
List<RayCastResults> results = new();
|
||||
var endPoint = ray.Position + ray.Direction.Normalized * maxLength;
|
||||
@@ -207,7 +270,7 @@ namespace Robust.Shared.GameObjects
|
||||
if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0)
|
||||
return true;
|
||||
|
||||
if (predicate?.Invoke(proxy.Fixture.Body.Owner) == true)
|
||||
if (predicate.Invoke(proxy.Fixture.Body.Owner, state) == true)
|
||||
return true;
|
||||
|
||||
// TODO: Shape raycast here
|
||||
@@ -247,7 +310,13 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="returnOnFirstHit">If false, will return a list of everything it hits, otherwise will just return a list of the first entity hit</param>
|
||||
/// <returns>An enumerable of either the first entity hit or everything hit</returns>
|
||||
public IEnumerable<RayCastResults> IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, EntityUid? ignoredEnt = null, bool returnOnFirstHit = true)
|
||||
=> IntersectRayWithPredicate(mapId, ray, maxLength, entity => entity == ignoredEnt, returnOnFirstHit);
|
||||
{
|
||||
// ReSharper disable once ConvertToLocalFunction
|
||||
var wrapper = (EntityUid uid, EntityUid? ignored)
|
||||
=> uid == ignored;
|
||||
|
||||
return IntersectRayWithPredicate(mapId, ray, ignoredEnt, wrapper, maxLength, returnOnFirstHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world and returns the distance the ray traveled while colliding with entities
|
||||
|
||||
@@ -142,9 +142,6 @@ public abstract partial class SharedPhysicsSystem
|
||||
if (physics.LifeStage != ComponentLifeStage.Running)
|
||||
return;
|
||||
|
||||
if (_container.IsEntityInContainer(uid, xform))
|
||||
return;
|
||||
|
||||
// When transferring bodies, we will preserve map angular and linear velocities. For this purpose, we simply
|
||||
// modify the velocities so that the map value remains unchanged.
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ using Prometheus;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -42,6 +44,7 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedJointSystem _joints = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
|
||||
|
||||
@@ -50,9 +53,15 @@ namespace Robust.Shared.GameObjects
|
||||
public bool MetricsEnabled { get; protected set; }
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = Logger.GetSawmill("physics");
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
|
||||
SubscribeLocalEvent<MapChangedEvent>(ev =>
|
||||
{
|
||||
if (ev.Created)
|
||||
@@ -63,10 +72,9 @@ namespace Robust.Shared.GameObjects
|
||||
SubscribeLocalEvent<CollisionChangeMessage>(HandlePhysicsUpdateMessage);
|
||||
SubscribeLocalEvent<PhysicsWakeMessage>(HandleWakeMessage);
|
||||
SubscribeLocalEvent<PhysicsSleepMessage>(HandleSleepMessage);
|
||||
SubscribeLocalEvent<EntMapIdChangedMessage>(HandleMapChange);
|
||||
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInserted);
|
||||
SubscribeLocalEvent<EntRemovedFromContainerMessage>(HandleContainerRemoved);
|
||||
SubscribeLocalEvent<PhysicsComponent, EntParentChangedMessage>(HandleParentChange);
|
||||
SubscribeLocalEvent<PhysicsComponent, EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<SharedPhysicsMapComponent, ComponentInit>(HandlePhysicsMapInit);
|
||||
SubscribeLocalEvent<SharedPhysicsMapComponent, ComponentRemove>(HandlePhysicsMapRemove);
|
||||
SubscribeLocalEvent<PhysicsComponent, ComponentInit>(OnPhysicsInit);
|
||||
@@ -104,18 +112,86 @@ namespace Robust.Shared.GameObjects
|
||||
component.ContactManager.Shutdown();
|
||||
}
|
||||
|
||||
private void HandleParentChange(EntityUid uid, PhysicsComponent body, ref EntParentChangedMessage args)
|
||||
private void OnParentChange(EntityUid uid, PhysicsComponent body, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (LifeStage(uid) is < EntityLifeStage.Initialized or > EntityLifeStage.MapInitialized
|
||||
|| !TryComp(uid, out TransformComponent? xform))
|
||||
var meta = MetaData(uid);
|
||||
|
||||
if (meta.EntityLifeStage < EntityLifeStage.Initialized || !TryComp(uid, out TransformComponent? xform))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (body.CanCollide)
|
||||
_broadphase.UpdateBroadphase(body, xform: xform);
|
||||
|
||||
HandleParentChangeVelocity(uid, body, ref args, xform);
|
||||
// Handle map change
|
||||
var mapId = _transform.GetMapId(args.Entity);
|
||||
|
||||
if (args.OldMapId != mapId)
|
||||
HandleMapChange(body, xform, args.OldMapId, mapId);
|
||||
|
||||
if (mapId != MapId.Nullspace && !_container.IsEntityInContainer(uid, meta))
|
||||
HandleParentChangeVelocity(uid, body, ref args, xform);
|
||||
}
|
||||
|
||||
private void HandleMapChange(PhysicsComponent body, TransformComponent xform, MapId oldMapId, MapId mapId)
|
||||
{
|
||||
_joints.ClearJoints(body);
|
||||
|
||||
// So if the map is being deleted it detaches all of its bodies to null soooo we have this fun check.
|
||||
SharedPhysicsMapComponent? oldMap = null;
|
||||
SharedPhysicsMapComponent? map = null;
|
||||
|
||||
if (oldMapId != MapId.Nullspace)
|
||||
{
|
||||
var oldMapEnt = MapManager.GetMapEntityId(oldMapId);
|
||||
|
||||
if (MetaData(oldMapEnt).EntityLifeStage < EntityLifeStage.Terminating)
|
||||
{
|
||||
oldMap = Comp<SharedPhysicsMapComponent>(oldMapEnt);
|
||||
oldMap.RemoveBody(body);
|
||||
}
|
||||
}
|
||||
|
||||
if (mapId != MapId.Nullspace)
|
||||
{
|
||||
map = Comp<SharedPhysicsMapComponent>(MapManager.GetMapEntityId(mapId));
|
||||
map.AddBody(body);
|
||||
}
|
||||
|
||||
if (xform.ChildCount == 0 ||
|
||||
(oldMap == null && map == null) ||
|
||||
_mapManager.IsGrid(body.Owner) ||
|
||||
_mapManager.IsMap(body.Owner)) return;
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
RecursiveMapUpdate(xform, oldMap, map, xformQuery, bodyQuery, metaQuery);
|
||||
}
|
||||
|
||||
private void RecursiveMapUpdate(
|
||||
TransformComponent xform,
|
||||
SharedPhysicsMapComponent? oldMap,
|
||||
SharedPhysicsMapComponent? map,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PhysicsComponent> bodyQuery,
|
||||
EntityQuery<MetaDataComponent> metaQuery)
|
||||
{
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
if (!bodyQuery.TryGetComponent(child.Value, out var childBody) ||
|
||||
!xformQuery.TryGetComponent(child.Value, out var childXform) ||
|
||||
metaQuery.GetComponent(child.Value).EntityLifeStage == EntityLifeStage.Deleted) continue;
|
||||
|
||||
_joints.ClearJoints(childBody);
|
||||
oldMap?.RemoveBody(childBody);
|
||||
map?.AddBody(childBody);
|
||||
RecursiveMapUpdate(childXform, oldMap, map, xformQuery, bodyQuery, metaQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGridInit(GridInitializeEvent ev)
|
||||
@@ -137,34 +213,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
protected abstract void HandleMapCreated(MapChangedEvent eventArgs);
|
||||
|
||||
private void HandleMapChange(EntMapIdChangedMessage message)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(message.Entity, out PhysicsComponent? physicsComponent))
|
||||
return;
|
||||
|
||||
_joints.ClearJoints(physicsComponent);
|
||||
|
||||
// So if the map is being deleted it detaches all of its bodies to null soooo we have this fun check.
|
||||
|
||||
var oldMapId = message.OldMapId;
|
||||
if (oldMapId != MapId.Nullspace)
|
||||
{
|
||||
var oldMapEnt = MapManager.GetMapEntityId(oldMapId);
|
||||
|
||||
if (MetaData(oldMapEnt).EntityLifeStage < EntityLifeStage.Terminating)
|
||||
{
|
||||
EntityManager.GetComponent<SharedPhysicsMapComponent>(oldMapEnt).RemoveBody(physicsComponent);
|
||||
}
|
||||
}
|
||||
|
||||
var newMapId = Transform(message.Entity).MapID;
|
||||
|
||||
if (newMapId != MapId.Nullspace)
|
||||
{
|
||||
EntityManager.GetComponent<SharedPhysicsMapComponent>(MapManager.GetMapEntityId(newMapId)).AddBody(physicsComponent);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePhysicsUpdateMessage(CollisionChangeMessage message)
|
||||
{
|
||||
var mapId = EntityManager.GetComponent<TransformComponent>(message.Owner).MapID;
|
||||
@@ -225,14 +273,17 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void HandleContainerRemoved(EntRemovedFromContainerMessage message)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(message.Entity, out PhysicsComponent? physicsComponent)) return;
|
||||
// If entity being deleted then the parent change will already be handled elsewhere and we don't want to re-add it to the map.
|
||||
if (!EntityManager.TryGetComponent(message.Entity, out PhysicsComponent? physicsComponent) ||
|
||||
MetaData(message.Entity).EntityLifeStage >= EntityLifeStage.Terminating) return;
|
||||
|
||||
var mapId = EntityManager.GetComponent<TransformComponent>(message.Container.Owner).MapID;
|
||||
var mapId = Transform(message.Container.Owner).MapID;
|
||||
|
||||
if (mapId != MapId.Nullspace)
|
||||
{
|
||||
EntityUid tempQualifier = MapManager.GetMapEntityId(mapId);
|
||||
EntityManager.GetComponent<SharedPhysicsMapComponent>(tempQualifier).AddBody(physicsComponent);
|
||||
DebugTools.Assert(!physicsComponent.Deleted);
|
||||
var tempQualifier = MapManager.GetMapEntityId(mapId);
|
||||
Comp<SharedPhysicsMapComponent>(tempQualifier).AddBody(physicsComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +310,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
comp.ProcessQueue();
|
||||
}
|
||||
|
||||
|
||||
_physicsManager.ClearTransforms();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -29,6 +30,45 @@ public abstract partial class SharedTransformSystem
|
||||
return GetWorldMatrix(xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetWorldMatrix(TransformComponent component, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return component.WorldMatrix;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region World Position
|
||||
|
||||
[Pure]
|
||||
public Vector2 GetWorldPosition(EntityUid uid)
|
||||
{
|
||||
return Transform(uid).WorldPosition;
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 GetWorldPosition(TransformComponent component)
|
||||
{
|
||||
return component.WorldPosition;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 GetWorldPosition(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return GetWorldPosition(xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 GetWorldPosition(TransformComponent component, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return component.WorldPosition;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region World Rotation
|
||||
@@ -54,6 +94,13 @@ public abstract partial class SharedTransformSystem
|
||||
return GetWorldRotation(xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Angle GetWorldRotation(TransformComponent component, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return component.WorldRotation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inverse World Matrix
|
||||
@@ -79,5 +126,21 @@ public abstract partial class SharedTransformSystem
|
||||
return GetInvWorldMatrix(xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetInvWorldMatrix(TransformComponent component, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return component.InvWorldMatrix;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public MapId GetMapId(EntityUid? uid, TransformComponent? xform = null)
|
||||
{
|
||||
if (uid == null ||
|
||||
!uid.Value.IsValid() ||
|
||||
!Resolve(uid.Value, ref xform, false)) return MapId.Nullspace;
|
||||
|
||||
return xform.MapID;
|
||||
}
|
||||
}
|
||||
|
||||
31
Robust.Shared/Map/AnchoredEntitiesEnumerator.cs
Normal file
31
Robust.Shared/Map/AnchoredEntitiesEnumerator.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
public struct AnchoredEntitiesEnumerator
|
||||
{
|
||||
// ReSharper disable once CollectionNeverUpdated.Local
|
||||
private static readonly List<EntityUid> Dummy = new();
|
||||
public static readonly AnchoredEntitiesEnumerator Empty = new(Dummy.GetEnumerator());
|
||||
|
||||
private List<EntityUid>.Enumerator _enumerator;
|
||||
|
||||
internal AnchoredEntitiesEnumerator(List<EntityUid>.Enumerator enumerator)
|
||||
{
|
||||
_enumerator = enumerator;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out EntityUid? uid)
|
||||
{
|
||||
if (!_enumerator.MoveNext())
|
||||
{
|
||||
uid = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
uid = _enumerator.Current;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
31
Robust.Shared/Map/GridEnumerator.cs
Normal file
31
Robust.Shared/Map/GridEnumerator.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
public struct GridEnumerator
|
||||
{
|
||||
private Dictionary<GridId, EntityUid>.Enumerator _enumerator;
|
||||
private EntityQuery<MapGridComponent> _query;
|
||||
|
||||
internal GridEnumerator(Dictionary<GridId, EntityUid>.Enumerator enumerator, EntityQuery<MapGridComponent> query)
|
||||
{
|
||||
_enumerator = enumerator;
|
||||
_query = query;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out IMapGrid? grid)
|
||||
{
|
||||
if (!_enumerator.MoveNext())
|
||||
{
|
||||
grid = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var (_, uid) = _enumerator.Current;
|
||||
|
||||
grid = _query.GetComponent(uid).Grid;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,9 @@ namespace Robust.Shared.Map
|
||||
IEnumerable<EntityUid> GetAnchoredEntities(Box2 worldAABB);
|
||||
IEnumerable<EntityUid> GetAnchoredEntities(Box2Rotated worldBounds);
|
||||
|
||||
// Struct enumerators
|
||||
AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(Vector2i pos);
|
||||
|
||||
Vector2i TileIndicesFor(EntityCoordinates coords) => CoordinatesToTile(coords);
|
||||
Vector2i TileIndicesFor(MapCoordinates worldPos) => CoordinatesToTile(MapToGrid(worldPos));
|
||||
Vector2i TileIndicesFor(Vector2 worldPos) => WorldToTile(worldPos);
|
||||
|
||||
@@ -66,10 +66,11 @@ namespace Robust.Shared.Map
|
||||
/// <summary>
|
||||
/// Returns the tile at the given chunk indices.
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="mapChunk"></param>
|
||||
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
|
||||
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
|
||||
/// <returns>A reference to a tile.</returns>
|
||||
TileRef GetTileRef(MapChunk mapChunk, ushort xIndex, ushort yIndex);
|
||||
TileRef GetTileRef(MapId mapId, MapChunk mapChunk, ushort xIndex, ushort yIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
public interface IMapManager : IPauseManager
|
||||
{
|
||||
/// <summary>
|
||||
/// A faster version of <see cref="GetAllGrids"/>
|
||||
/// </summary>
|
||||
GridEnumerator GetAllGridsEnumerator();
|
||||
|
||||
IEnumerable<IMapGrid> GetAllGrids();
|
||||
|
||||
/// <summary>
|
||||
@@ -125,16 +130,6 @@ namespace Robust.Shared.Map
|
||||
/// <returns></returns>
|
||||
IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldAabb, bool approx = false);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FindGridsIntersecting(Robust.Shared.Map.MapId,Robust.Shared.Maths.Box2,bool)"/>
|
||||
/// </summary>
|
||||
IEnumerable<IMapGrid> FindGridsIntersecting(
|
||||
MapId mapId,
|
||||
Box2 worldAABB,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
bool approx = false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the grids intersecting this AABB.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user