mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
57 Commits
v0.8.76
...
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 |
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
|
||||
5
.github/workflows/build-docfx.yml
vendored
5
.github/workflows/build-docfx.yml
vendored
@@ -1,9 +1,8 @@
|
||||
name: Build & Publish DocFX
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
schedule:
|
||||
- cron: "0 0 * * 0"
|
||||
jobs:
|
||||
docfx:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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.76</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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!;
|
||||
@@ -83,6 +81,19 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
private readonly ObjectPool<RobustTree<EntityUid>> _treePool =
|
||||
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()
|
||||
{
|
||||
base.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);
|
||||
@@ -833,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)
|
||||
@@ -851,7 +957,10 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
return viewers;
|
||||
var viewerArray = viewers.ToArray();
|
||||
|
||||
_uidSetPool.Return(viewers);
|
||||
return viewerArray;
|
||||
}
|
||||
|
||||
// Read Safe
|
||||
@@ -903,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,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 { }
|
||||
}
|
||||
|
||||
@@ -72,21 +72,15 @@ namespace Robust.Shared.GameObjects
|
||||
/// Updates the entity's AABB. Uses <see cref="ILookupWorldBox2Component"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public void UpdateBounds(EntityUid uid, TransformComponent? xform = null)
|
||||
public void UpdateBounds(EntityUid uid, TransformComponent? xform = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_container.IsEntityInContainer(uid, meta))
|
||||
return;
|
||||
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (xform == null)
|
||||
xformQuery.TryGetComponent(uid, out xform);
|
||||
|
||||
if (xform == null)
|
||||
{
|
||||
Logger.Error($"Unable to resolve transform on {EntityManager.ToPrettyString(uid)}");
|
||||
DebugTools.Assert(false);
|
||||
if (!xformQuery.Resolve(uid, ref xform) || xform.Anchored)
|
||||
return;
|
||||
}
|
||||
|
||||
if (xform.Anchored || _container.IsEntityInContainer(uid, xform)) return;
|
||||
|
||||
var lookup = GetLookup(uid, xform, xformQuery);
|
||||
|
||||
@@ -231,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);
|
||||
@@ -244,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)
|
||||
|
||||
@@ -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;
|
||||
@@ -133,4 +134,13 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
|
||||
#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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -16,6 +16,28 @@ namespace Robust.Shared.Map
|
||||
|
||||
void ChunkRemoved(GridId gridId, MapChunk chunk);
|
||||
|
||||
/// <summary>
|
||||
/// Specific version of TryFindGridAt that allows re-usable data structures to be passed in for optimisation reasons.
|
||||
/// </summary>
|
||||
bool TryFindGridAt(
|
||||
MapId mapId,
|
||||
Vector2 worldPos,
|
||||
List<MapGrid> grids,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PhysicsComponent> bodyQuery,
|
||||
[NotNullWhen(true)] out IMapGrid? grid);
|
||||
|
||||
/// <summary>
|
||||
/// Specific version of FindGridsIntersecting that allows re-usable data structures to be passed in for optimisation reasons.
|
||||
/// </summary>
|
||||
IEnumerable<IMapGrid> FindGridsIntersecting(
|
||||
MapId mapId,
|
||||
Box2 worldAabb,
|
||||
List<MapGrid> grids,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
bool approx = false);
|
||||
|
||||
/// <summary>
|
||||
/// Raises the OnTileChanged event.
|
||||
/// </summary>
|
||||
|
||||
@@ -197,6 +197,18 @@ namespace Robust.Shared.Map
|
||||
return list;
|
||||
}
|
||||
|
||||
internal List<EntityUid>? GetSnapGrid(ushort xCell, ushort yCell)
|
||||
{
|
||||
if (xCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xCell), "Tile indices out of bounds.");
|
||||
|
||||
if (yCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yCell), "Tile indices out of bounds.");
|
||||
|
||||
var cell = _snapGrid[xCell, yCell];
|
||||
return cell.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entity to the anchor cell at the given tile indices.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -518,6 +517,20 @@ namespace Robust.Shared.Map
|
||||
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
}
|
||||
|
||||
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(Vector2i pos)
|
||||
{
|
||||
var gridChunkPos = GridTileToChunkIndices(pos);
|
||||
|
||||
if (!_chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
|
||||
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
return snapgrid == null ?
|
||||
AnchoredEntitiesEnumerator.Empty :
|
||||
new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(Box2 worldAABB)
|
||||
{
|
||||
|
||||
@@ -116,9 +116,20 @@ internal partial class MapManager
|
||||
OnGridCreated?.Invoke(mapGrid.ParentMapId, mapGrid.Index);
|
||||
}
|
||||
|
||||
public GridEnumerator GetAllGridsEnumerator()
|
||||
{
|
||||
var query = EntityManager.GetEntityQuery<MapGridComponent>();
|
||||
return new GridEnumerator(_grids.GetEnumerator(), query);
|
||||
}
|
||||
|
||||
public IEnumerable<IMapGrid> GetAllGrids()
|
||||
{
|
||||
return EntityManager.EntityQuery<IMapGridComponent>(true).Select(c => c.Grid);
|
||||
var compQuery = EntityManager.GetEntityQuery<MapGridComponent>();
|
||||
|
||||
foreach (var (_, uid) in _grids)
|
||||
{
|
||||
yield return compQuery.GetComponent(uid).Grid;
|
||||
}
|
||||
}
|
||||
|
||||
public IMapGrid CreateGrid(MapId currentMapId, GridId? forcedGridId = null, ushort chunkSize = 16)
|
||||
|
||||
@@ -32,7 +32,7 @@ internal partial class MapManager
|
||||
EntityManager.EventBus.SubscribeEvent<GridRemovalEvent>(EventSource.Local, this, OnGridRemove);
|
||||
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
|
||||
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, RotateEvent>(OnGridRotate);
|
||||
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, EntMapIdChangedMessage>(OnGridMapChange);
|
||||
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, EntParentChangedMessage>(OnGridParentChange);
|
||||
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, GridFixtureChangeEvent>(OnGridBoundsChange);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ internal partial class MapManager
|
||||
EntityManager.EventBus.UnsubscribeEvent<GridRemovalEvent>(EventSource.Local, this);
|
||||
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, MoveEvent>();
|
||||
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, RotateEvent>();
|
||||
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, EntMapIdChangedMessage>();
|
||||
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, EntParentChangedMessage>();
|
||||
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, GridFixtureChangeEvent>();
|
||||
|
||||
DebugTools.Assert(_gridTrees.Count == 0);
|
||||
@@ -139,7 +139,7 @@ internal partial class MapManager
|
||||
_movedGrids[grid.ParentMapId].Add(grid);
|
||||
}
|
||||
|
||||
private void OnGridMapChange(EntityUid uid, MapGridComponent component, EntMapIdChangedMessage args)
|
||||
private void OnGridParentChange(EntityUid uid, MapGridComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
var aGrid = (MapGrid)component.Grid;
|
||||
var lifestage = EntityManager.GetComponent<MetaDataComponent>(uid).EntityLifeStage;
|
||||
@@ -148,13 +148,20 @@ internal partial class MapManager
|
||||
// Want gridinit / gridremoval to handle this hence specialcase those situations.
|
||||
if (lifestage < EntityLifeStage.Initialized) return;
|
||||
|
||||
var oldMapId = args.OldParent == null
|
||||
? MapId.Nullspace
|
||||
: EntityManager.GetComponent<TransformComponent>(args.OldParent.Value).MapID;
|
||||
|
||||
// Make sure we cleanup old map for moved grid stuff.
|
||||
var mapId = EntityManager.GetComponent<TransformComponent>(uid).MapID;
|
||||
|
||||
if (aGrid.MapProxy != DynamicTree.Proxy.Free && _movedGrids.TryGetValue(args.OldMapId, out var oldMovedGrids))
|
||||
// y'all need jesus
|
||||
if (oldMapId == mapId) return;
|
||||
|
||||
if (aGrid.MapProxy != DynamicTree.Proxy.Free && _movedGrids.TryGetValue(oldMapId, out var oldMovedGrids))
|
||||
{
|
||||
oldMovedGrids.Remove(component.Grid);
|
||||
RemoveGrid(aGrid, args.OldMapId);
|
||||
RemoveGrid(aGrid, oldMapId);
|
||||
}
|
||||
|
||||
if (_movedGrids.TryGetValue(mapId, out var newMovedGrids))
|
||||
|
||||
@@ -19,35 +19,29 @@ internal partial class MapManager
|
||||
return FindGridsIntersecting(mapId, aabb, approx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the grids intersecting this AABB.
|
||||
/// </summary>
|
||||
/// <param name="mapId">The relevant MapID</param>
|
||||
/// <param name="aabb">The AABB to intersect</param>
|
||||
/// <param name="approx">Set to false if you wish to accurately get the grid bounds per-tile.</param>
|
||||
public IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 aabb, bool approx = false)
|
||||
public IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldAabb, bool approx = false)
|
||||
{
|
||||
if (!_gridTrees.ContainsKey(mapId)) return Enumerable.Empty<IMapGrid>();
|
||||
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
var grids = new List<MapGrid>();
|
||||
|
||||
return FindGridsIntersecting(mapId, aabb, xformQuery, physicsQuery, approx);
|
||||
return FindGridsIntersecting(mapId, worldAabb, grids, xformQuery, physicsQuery, approx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the grids intersecting this AABB.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IMapGrid> FindGridsIntersecting(
|
||||
MapId mapId,
|
||||
Box2 aabb,
|
||||
List<MapGrid> grids,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
bool approx = false)
|
||||
{
|
||||
if (!_gridTrees.TryGetValue(mapId, out var gridTree)) return Enumerable.Empty<IMapGrid>();
|
||||
|
||||
var grids = new List<MapGrid>();
|
||||
DebugTools.Assert(grids.Count == 0);
|
||||
var state = (gridTree, grids);
|
||||
|
||||
gridTree.Query(ref state,
|
||||
@@ -103,16 +97,20 @@ internal partial class MapManager
|
||||
return grids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find the map grid under the map location.
|
||||
/// </summary>
|
||||
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, [NotNullWhen(true)] out IMapGrid? grid)
|
||||
/// <inheritdoc />
|
||||
public bool TryFindGridAt(
|
||||
MapId mapId,
|
||||
Vector2 worldPos,
|
||||
List<MapGrid> grids,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PhysicsComponent> bodyQuery,
|
||||
[NotNullWhen(true)] out IMapGrid? grid)
|
||||
{
|
||||
// Need to enlarge the AABB by at least the grid shrinkage size.
|
||||
var aabb = new Box2(worldPos - 0.5f, worldPos + 0.5f);
|
||||
var grids = FindGridsIntersecting(mapId, aabb, true);
|
||||
var intersectingGrids = FindGridsIntersecting(mapId, aabb, grids, xformQuery, bodyQuery, true);
|
||||
|
||||
foreach (var gridInter in grids)
|
||||
foreach (var gridInter in intersectingGrids)
|
||||
{
|
||||
var mapGrid = (MapGrid) gridInter;
|
||||
|
||||
@@ -121,7 +119,7 @@ internal partial class MapManager
|
||||
// (though now we need some extra calcs up front).
|
||||
|
||||
// Doesn't use WorldBounds because it's just an AABB.
|
||||
var matrix = EntityManager.GetComponent<TransformComponent>(mapGrid.GridEntityId).InvWorldMatrix;
|
||||
var matrix = xformQuery.GetComponent(mapGrid.GridEntityId).InvWorldMatrix;
|
||||
var localPos = matrix.Transform(worldPos);
|
||||
|
||||
// NOTE:
|
||||
@@ -145,6 +143,18 @@ internal partial class MapManager
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find the map grid under the map location.
|
||||
/// </summary>
|
||||
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, [NotNullWhen(true)] out IMapGrid? grid)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var bodyQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
var grids = new List<MapGrid>();
|
||||
|
||||
return TryFindGridAt(mapId, worldPos, grids, xformQuery, bodyQuery, out grid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find the map grid under the map location.
|
||||
/// </summary>
|
||||
|
||||
@@ -62,8 +62,12 @@ internal sealed class NetworkedMapManager : MapManager, INetworkedMapManager
|
||||
public GameStateMapData? GetStateData(GameTick fromTick)
|
||||
{
|
||||
var gridDatums = new Dictionary<GridId, GameStateMapData.GridDatum>();
|
||||
foreach (MapGrid grid in GetAllGrids())
|
||||
var enumerator = GetAllGridsEnumerator();
|
||||
|
||||
while (enumerator.MoveNext(out var iGrid))
|
||||
{
|
||||
var grid = (MapGrid)iGrid;
|
||||
|
||||
if (grid.LastTileModifiedTick < fromTick)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
public delegate Task<NetApproval> NetApprovalDelegate(NetApprovalEventArgs eventArgs);
|
||||
|
||||
byte[]? RsaPublicKey { get; }
|
||||
byte[]? CryptoPublicKey { get; }
|
||||
AuthMode Auth { get; }
|
||||
Func<string, Task<NetUserId?>>? AssignUserIdCallback { get; set; }
|
||||
NetApprovalDelegate? HandleApprovalCallback { get; set; }
|
||||
|
||||
@@ -12,25 +12,20 @@ namespace Robust.Shared.Network.Messages.Handshake
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public Guid UserId;
|
||||
public byte[] SharedSecret;
|
||||
public byte[] VerifyToken;
|
||||
public byte[] SealedData;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
UserId = buffer.ReadGuid();
|
||||
var keyLength = buffer.ReadVariableInt32();
|
||||
SharedSecret = buffer.ReadBytes(keyLength);
|
||||
var tokenLength = buffer.ReadVariableInt32();
|
||||
VerifyToken = buffer.ReadBytes(tokenLength);
|
||||
SealedData = buffer.ReadBytes(keyLength);
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
buffer.Write(UserId);
|
||||
buffer.WriteVariableInt32(SharedSecret.Length);
|
||||
buffer.Write(SharedSecret);
|
||||
buffer.WriteVariableInt32(VerifyToken.Length);
|
||||
buffer.Write(VerifyToken);
|
||||
buffer.WriteVariableInt32(SealedData.Length);
|
||||
buffer.Write(SealedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -19,10 +17,6 @@ namespace Robust.Shared.Network.Messages
|
||||
public EntityMessageType Type { get; set; }
|
||||
|
||||
public EntityEventArgs SystemMessage { get; set; }
|
||||
#pragma warning disable 618
|
||||
public ComponentMessage ComponentMessage { get; set; }
|
||||
#pragma warning restore 618
|
||||
|
||||
public EntityUid EntityUid { get; set; }
|
||||
public uint NetId { get; set; }
|
||||
public uint Sequence { get; set; }
|
||||
@@ -44,20 +38,6 @@ namespace Robust.Shared.Network.Messages
|
||||
SystemMessage = serializer.Deserialize<EntityEventArgs>(stream);
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityMessageType.ComponentMessage:
|
||||
{
|
||||
EntityUid = new EntityUid(buffer.ReadInt32());
|
||||
NetId = buffer.ReadUInt32();
|
||||
|
||||
var serializer = IoCManager.Resolve<IRobustSerializer>();
|
||||
int length = buffer.ReadVariableInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
#pragma warning disable 618
|
||||
ComponentMessage = serializer.Deserialize<ComponentMessage>(stream);
|
||||
#pragma warning restore 618
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,22 +61,6 @@ namespace Robust.Shared.Network.Messages
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityMessageType.ComponentMessage:
|
||||
{
|
||||
buffer.Write((int)EntityUid);
|
||||
buffer.Write(NetId);
|
||||
|
||||
var serializer = IoCManager.Resolve<IRobustSerializer>();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
serializer.Serialize(stream, ComponentMessage);
|
||||
buffer.WriteVariableInt32((int)stream.Length);
|
||||
stream.TryGetBuffer(out var segment);
|
||||
buffer.Write(segment);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +71,6 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
case EntityMessageType.Error:
|
||||
return "MsgEntity Error";
|
||||
case EntityMessageType.ComponentMessage:
|
||||
return $"MsgEntity Comp, {timingData}, {EntityUid}/{NetId}: {ComponentMessage}";
|
||||
case EntityMessageType.SystemMessage:
|
||||
return $"MsgEntity Comp, {timingData}, {SystemMessage}";
|
||||
default:
|
||||
|
||||
122
Robust.Shared/Network/NetEncryption.cs
Normal file
122
Robust.Shared/Network/NetEncryption.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Lidgren.Network;
|
||||
using SpaceWizards.Sodium;
|
||||
|
||||
namespace Robust.Shared.Network;
|
||||
|
||||
internal sealed class NetEncryption
|
||||
{
|
||||
// Use a counter for nonces. The counter is 64-bit, I will be impressed if you ever manage to run it out.
|
||||
// 64-bit counter (incl over the wire) is fine, don't need the whole 192-bit.
|
||||
// Server starts at 0, client starts at 1, increment by two.
|
||||
// This means server and client never use eachother's nonces (one side odd, one side even).
|
||||
// Keep in mind, our keys are only valid for one session.
|
||||
private ulong _nonce;
|
||||
private readonly byte[] _key;
|
||||
|
||||
public NetEncryption(byte[] key, bool isServer)
|
||||
{
|
||||
if (key.Length != CryptoAeadXChaCha20Poly1305Ietf.KeyBytes)
|
||||
throw new ArgumentException("Key is of wrong size!");
|
||||
|
||||
_nonce = isServer ? 0ul : 1ul;
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public unsafe void Encrypt(NetOutgoingMessage message)
|
||||
{
|
||||
var nonce = Interlocked.Add(ref _nonce, 2);
|
||||
|
||||
var lengthBytes = message.LengthBytes;
|
||||
var encryptedSize = CryptoAeadXChaCha20Poly1305Ietf.AddBytes + lengthBytes + sizeof(ulong);
|
||||
|
||||
var data = message.Data.AsSpan(0, lengthBytes);
|
||||
|
||||
Span<byte> plaintext;
|
||||
Span<byte> ciphertext;
|
||||
byte[]? returnPool = null;
|
||||
|
||||
if (message.Data.Length >= encryptedSize)
|
||||
{
|
||||
// Since we have enough space in the existing message data,
|
||||
// we copy plaintext to an ArrayPool buffer and write ciphertext into existing message.
|
||||
// This avoids an allocation at the cost of an extra copy operation.
|
||||
|
||||
returnPool = ArrayPool<byte>.Shared.Rent(lengthBytes);
|
||||
plaintext = returnPool.AsSpan(0, lengthBytes);
|
||||
data.CopyTo(plaintext);
|
||||
|
||||
ciphertext = message.Data.AsSpan(0, encryptedSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, an allocation is unavoidable,
|
||||
// so we swap the data buffer in the message with a fresh allocation and don't do an extra copy of the data.
|
||||
|
||||
plaintext = data;
|
||||
ciphertext = message.Data = new byte[encryptedSize];
|
||||
}
|
||||
|
||||
// TODO: this is probably broken for big-endian machines.
|
||||
Span<byte> nonceData = stackalloc byte[CryptoAeadXChaCha20Poly1305Ietf.NoncePublicBytes];
|
||||
nonceData.Fill(0);
|
||||
MemoryMarshal.Write(nonceData, ref nonce);
|
||||
MemoryMarshal.Write(ciphertext, ref nonce);
|
||||
|
||||
CryptoAeadXChaCha20Poly1305Ietf.Encrypt(
|
||||
// ciphertext
|
||||
ciphertext[sizeof(ulong)..],
|
||||
out _,
|
||||
// plaintext
|
||||
plaintext,
|
||||
// additional data (unused)
|
||||
ReadOnlySpan<byte>.Empty,
|
||||
// nonce
|
||||
nonceData,
|
||||
// key
|
||||
_key);
|
||||
|
||||
message.LengthBytes = encryptedSize;
|
||||
|
||||
if (returnPool != null)
|
||||
ArrayPool<byte>.Shared.Return(returnPool);
|
||||
}
|
||||
|
||||
public unsafe void Decrypt(NetIncomingMessage message)
|
||||
{
|
||||
var nonce = message.ReadUInt64();
|
||||
var cipherText = message.Data.AsSpan(sizeof(ulong), message.LengthBytes - sizeof(ulong));
|
||||
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(cipherText.Length);
|
||||
cipherText.CopyTo(buffer);
|
||||
|
||||
// TODO: this is probably broken for big-endian machines.
|
||||
Span<byte> nonceData = stackalloc byte[CryptoAeadXChaCha20Poly1305Ietf.NoncePublicBytes];
|
||||
nonceData.Fill(0);
|
||||
MemoryMarshal.Write(nonceData, ref nonce);
|
||||
|
||||
var result = CryptoAeadXChaCha20Poly1305Ietf.Decrypt(
|
||||
// plaintext
|
||||
message.Data,
|
||||
out var messageLength,
|
||||
// ciphertext
|
||||
buffer.AsSpan(0, cipherText.Length),
|
||||
// additional data (unused)
|
||||
ReadOnlySpan<byte>.Empty,
|
||||
// nonce
|
||||
nonceData,
|
||||
// key
|
||||
_key);
|
||||
|
||||
message.Position = 0;
|
||||
message.LengthBytes = messageLength;
|
||||
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
|
||||
if (!result)
|
||||
throw new SodiumException("Decryption operation failed!");
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using Lidgren.Network;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network.Messages.Handshake;
|
||||
using Robust.Shared.Utility;
|
||||
using SpaceWizards.Sodium;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
{
|
||||
@@ -154,11 +155,11 @@ namespace Robust.Shared.Network
|
||||
var encRequest = new MsgEncryptionRequest();
|
||||
encRequest.ReadFromBuffer(response);
|
||||
|
||||
var sharedSecret = new byte[AesKeyLength];
|
||||
var sharedSecret = new byte[SharedKeyLength];
|
||||
RandomNumberGenerator.Fill(sharedSecret);
|
||||
|
||||
if (encrypt)
|
||||
encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length);
|
||||
encryption = new NetEncryption(sharedSecret, isServer: false);
|
||||
|
||||
byte[] keyBytes;
|
||||
if (hasPubKey)
|
||||
@@ -172,11 +173,18 @@ namespace Robust.Shared.Network
|
||||
keyBytes = encRequest.PublicKey;
|
||||
}
|
||||
|
||||
var rsaKey = RSA.Create();
|
||||
rsaKey.ImportRSAPublicKey(keyBytes, out _);
|
||||
if (keyBytes.Length != CryptoBox.PublicKeyBytes)
|
||||
{
|
||||
connection.Disconnect("Invalid public key length");
|
||||
return;
|
||||
}
|
||||
|
||||
var encryptedSecret = rsaKey.Encrypt(sharedSecret, RSAEncryptionPadding.OaepSHA256);
|
||||
var encryptedVerifyToken = rsaKey.Encrypt(encRequest.VerifyToken, RSAEncryptionPadding.OaepSHA256);
|
||||
// Data is [shared]+[verify]
|
||||
var data = new byte[sharedSecret.Length + encRequest.VerifyToken.Length];
|
||||
sharedSecret.CopyTo(data.AsSpan());
|
||||
encRequest.VerifyToken.CopyTo(data.AsSpan(sharedSecret.Length));
|
||||
|
||||
var sealedData = CryptoBox.Seal(data, keyBytes);
|
||||
|
||||
var authHashBytes = MakeAuthHash(sharedSecret, keyBytes);
|
||||
var authHash = Convert.ToBase64String(authHashBytes);
|
||||
@@ -190,8 +198,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
var encryptionResponse = new MsgEncryptionResponse
|
||||
{
|
||||
SharedSecret = encryptedSecret,
|
||||
VerifyToken = encryptedVerifyToken,
|
||||
SealedData = sealedData,
|
||||
UserId = userId!.Value.UserId
|
||||
};
|
||||
|
||||
|
||||
@@ -23,22 +23,24 @@ Note that the S->C packet AFTER `MsgLoginStart` is preceded with a bool (+pad) t
|
||||
|
||||
A more detailed overview is here:
|
||||
|
||||
First the client sends `MsgLoginStart`. This contains the client's username, whether it wants to authenticate, and whether it needs the server's public RSA key sent (when authenticating && it doesn't have it yet from the launcher).
|
||||
First the client sends `MsgLoginStart`. This contains the client's username, whether it wants to authenticate, and whether it needs the server's public encryption key sent (when authenticating && it doesn't have it yet from the launcher).
|
||||
|
||||
The server can then choose to do block the client, let the client authenticate, or let the client in as guest. If it lets the client in as guest it skips straight to sending `MsgLoginSuccess` (see below). Otherwise it will send an `MsgEncryptionRequest` to the client to initiate authentication.
|
||||
|
||||
`MsgEncryptionRequest` contains a random verify token sent by the server, as well as the server's public RSA key (if requested).
|
||||
`MsgEncryptionRequest` contains a random verify token sent by the server, as well as the server's public encryption key (if requested).
|
||||
|
||||
When the client receives `MsgEncryptionRequest`, it will generate a 32-byte random secret. It will then generate an SHA-256 hash of this secret and the server's public key. This hash is POSTed to `api/session/join` (along with login token in `Authorization` header) on the auth server. The shared secret and verify token are separately encrypted with the server's RSA key, then sent along with the client's account GUID to the server in `MsgEncryptionResponse`.
|
||||
When the client receives `MsgEncryptionRequest`, it will generate a 32-byte random secret. It will then generate an SHA-256 hash of this secret and the server's public key. This hash is POSTed to `api/session/join` (along with login token in `Authorization` header) on the auth server. The shared secret and verify token are separately encrypted with the server's encryption key, then sent along with the client's account GUID to the server in `MsgEncryptionResponse`.
|
||||
|
||||
The server will then decrypt the verify token and shared secret with its private RSA key. If the verify token does not match then drop the client (to check if the client is using the correct key). Then the server will generate the same hash as mentioned earlier and GET it to `api/session/hasJoined?hash=<hash>&userId=<userId>` to check if the user did indeed authenticate correctly. And also gets the user's username and GUID again because why not.
|
||||
The server will then decrypt the verify token and shared secret with its private encryption key. If the verify token does not match then drop the client (to check if the client is using the correct key). Then the server will generate the same hash as mentioned earlier and GET it to `api/session/hasJoined?hash=<hash>&userId=<userId>` to check if the user did indeed authenticate correctly. And also gets the user's username and GUID again because why not.
|
||||
|
||||
From this point on, if authenticating, all messages sent between client and server will be AES encrypted with the shared secret generated earlier.
|
||||
From this point on, if authenticating, all messages sent between client and server will be encrypted with the shared secret generated earlier.
|
||||
|
||||
Then the server shall reply with `MsgLoginSuccess` with the assigned username/userID if login is successful.
|
||||
|
||||
I think that was everything.
|
||||
|
||||
Oh yeah, the server generates a new 2048-bit RSA key every startup and exposes it via its status API on `/info`.
|
||||
Oh yeah, the server generates a new encryption key every startup and exposes it via its status API on `/info`.
|
||||
|
||||
We use libsodium for most of the crypto stuff. The public/private key stuff is with [Sealed box](https://doc.libsodium.org/public-key_cryptography/sealed_boxes). Packet encryption for game packets is done with [AEAD XChaCha20-Poly1305](https://doc.libsodium.org/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction).
|
||||
|
||||
This is a rough outline. If you want complete gritty details just check the damn code.
|
||||
|
||||
@@ -10,31 +10,25 @@ using Robust.Shared.AuthLib;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network.Messages.Handshake;
|
||||
using Robust.Shared.Utility;
|
||||
using SpaceWizards.Sodium;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
{
|
||||
partial class NetManager
|
||||
{
|
||||
private const int RsaKeySize = 2048;
|
||||
private readonly byte[] _cryptoPrivateKey = new byte[CryptoBox.SecretKeyBytes];
|
||||
|
||||
private RSA? _authRsaPrivateKey;
|
||||
|
||||
public byte[]? RsaPublicKey { get; private set; }
|
||||
public byte[] CryptoPublicKey { get; } = new byte[CryptoBox.PublicKeyBytes];
|
||||
public AuthMode Auth { get; private set; }
|
||||
|
||||
public Func<string, Task<NetUserId?>>? AssignUserIdCallback { get; set; }
|
||||
public IServerNetManager.NetApprovalDelegate? HandleApprovalCallback { get; set; }
|
||||
|
||||
private void SAGenerateRsaKeys()
|
||||
private void SAGenerateKeys()
|
||||
{
|
||||
_authRsaPrivateKey = RSA.Create(RsaKeySize);
|
||||
RsaPublicKey = _authRsaPrivateKey.ExportRSAPublicKey();
|
||||
CryptoBox.KeyPair(CryptoPublicKey, _cryptoPrivateKey);
|
||||
|
||||
/*
|
||||
Logger.DebugS("auth", "Private RSA key is {0}",
|
||||
Convert.ToBase64String(_authRsaPrivateKey.ExportRSAPrivateKey()));
|
||||
*/
|
||||
Logger.DebugS("auth", "Public RSA key is {0}", Convert.ToBase64String(RsaPublicKey));
|
||||
Logger.DebugS("auth", "Public key is {0}", Convert.ToBase64String(CryptoPublicKey));
|
||||
}
|
||||
|
||||
private async void HandleHandshake(NetPeerData peer, NetConnection connection)
|
||||
@@ -72,7 +66,7 @@ namespace Robust.Shared.Network
|
||||
RandomNumberGenerator.Fill(verifyToken);
|
||||
var msgEncReq = new MsgEncryptionRequest
|
||||
{
|
||||
PublicKey = needPk ? RsaPublicKey : Array.Empty<byte>(),
|
||||
PublicKey = needPk ? CryptoPublicKey : Array.Empty<byte>(),
|
||||
VerifyToken = verifyToken
|
||||
};
|
||||
|
||||
@@ -87,18 +81,14 @@ namespace Robust.Shared.Network
|
||||
var msgEncResponse = new MsgEncryptionResponse();
|
||||
msgEncResponse.ReadFromBuffer(incPacket);
|
||||
|
||||
byte[] verifyTokenCheck;
|
||||
byte[] sharedSecret;
|
||||
try
|
||||
{
|
||||
verifyTokenCheck = _authRsaPrivateKey!.Decrypt(
|
||||
msgEncResponse.VerifyToken,
|
||||
RSAEncryptionPadding.OaepSHA256);
|
||||
sharedSecret = _authRsaPrivateKey!.Decrypt(
|
||||
msgEncResponse.SharedSecret,
|
||||
RSAEncryptionPadding.OaepSHA256);
|
||||
}
|
||||
catch (CryptographicException)
|
||||
var encResp = new byte[verifyToken.Length + SharedKeyLength];
|
||||
var ret = CryptoBox.SealOpen(
|
||||
encResp,
|
||||
msgEncResponse.SealedData,
|
||||
CryptoPublicKey,
|
||||
_cryptoPrivateKey);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
// Launcher gives the client the public RSA key of the server BUT
|
||||
// that doesn't persist if the server restarts.
|
||||
@@ -108,16 +98,20 @@ namespace Robust.Shared.Network
|
||||
return;
|
||||
}
|
||||
|
||||
if (!verifyToken.SequenceEqual(verifyTokenCheck))
|
||||
// Data is [shared]+[verify]
|
||||
var verifyTokenCheck = encResp[SharedKeyLength..];
|
||||
var sharedSecret = encResp[..SharedKeyLength];
|
||||
|
||||
if (!verifyToken.AsSpan().SequenceEqual(verifyTokenCheck))
|
||||
{
|
||||
connection.Disconnect("Verify token is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (msgLogin.Encrypt)
|
||||
encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length);
|
||||
encryption = new NetEncryption(sharedSecret, isServer: true);
|
||||
|
||||
var authHashBytes = MakeAuthHash(sharedSecret, RsaPublicKey!);
|
||||
var authHashBytes = MakeAuthHash(sharedSecret, CryptoPublicKey!);
|
||||
var authHash = Base64Helpers.ConvertToBase64Url(authHashBytes);
|
||||
|
||||
var client = new HttpClient();
|
||||
|
||||
@@ -18,6 +18,7 @@ using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using SpaceWizards.Sodium;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
{
|
||||
@@ -38,7 +39,7 @@ namespace Robust.Shared.Network
|
||||
/// </summary>
|
||||
public sealed partial class NetManager : IClientNetManager, IServerNetManager
|
||||
{
|
||||
internal const int AesKeyLength = 32;
|
||||
internal const int SharedKeyLength = CryptoAeadXChaCha20Poly1305Ietf.KeyBytes; // 32 bytes
|
||||
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
|
||||
@@ -266,7 +267,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
SAGenerateRsaKeys();
|
||||
SAGenerateKeys();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,10 +831,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
var encryption = IsServer ? channel.Encryption : _clientEncryption;
|
||||
|
||||
if (encryption != null)
|
||||
{
|
||||
msg.Decrypt(encryption);
|
||||
}
|
||||
encryption?.Decrypt(msg);
|
||||
|
||||
var id = msg.ReadByte();
|
||||
|
||||
@@ -1062,10 +1060,8 @@ namespace Robust.Shared.Network
|
||||
|
||||
var peer = channel.Connection.Peer;
|
||||
var packet = BuildMessage(message, peer);
|
||||
if (channel.Encryption != null)
|
||||
{
|
||||
packet.Encrypt(channel.Encryption);
|
||||
}
|
||||
|
||||
channel.Encryption?.Encrypt(packet);
|
||||
|
||||
var method = message.DeliveryMethod;
|
||||
peer.SendMessage(packet, channel.Connection, method);
|
||||
@@ -1105,10 +1101,8 @@ namespace Robust.Shared.Network
|
||||
var peer = _netPeers[0];
|
||||
var packet = BuildMessage(message, peer.Peer);
|
||||
var method = message.DeliveryMethod;
|
||||
if (_clientEncryption != null)
|
||||
{
|
||||
packet.Encrypt(_clientEncryption);
|
||||
}
|
||||
|
||||
_clientEncryption?.Encrypt(packet);
|
||||
|
||||
peer.Peer.SendMessage(packet, peer.ConnectionsWithChannels[0], method);
|
||||
LogSend(message, method, packet);
|
||||
|
||||
@@ -54,9 +54,19 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("id")]
|
||||
public string ID { get; set; } = string.Empty;
|
||||
public string ID
|
||||
{
|
||||
get => _id ?? AutoGeneratedId;
|
||||
set => _id = value;
|
||||
}
|
||||
|
||||
// Because Ids are optional and will be auto generated, this means every component that doesn't specify an ID
|
||||
// will get its fixture component saved to the map. This is a shitty workaround:
|
||||
public string AutoGeneratedId = string.Empty;
|
||||
|
||||
[DataField("id")]
|
||||
private string? _id;
|
||||
|
||||
[ViewVariables]
|
||||
[field: NonSerialized]
|
||||
public FixtureProxy[] Proxies { get; set; } = Array.Empty<FixtureProxy>();
|
||||
|
||||
@@ -30,6 +30,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
@@ -115,6 +116,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
public override JointType JointType => JointType.Distance;
|
||||
|
||||
public DistanceJoint() {}
|
||||
|
||||
/// <summary>
|
||||
/// This requires defining an
|
||||
/// anchor point on both bodies and the non-zero length of the
|
||||
@@ -142,6 +145,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// Manipulating the length can lead to non-physical behavior when the frequency is zero.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("length")]
|
||||
public float Length
|
||||
{
|
||||
get => _length;
|
||||
@@ -161,6 +165,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// The upper limit allowed between the 2 bodies.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxLength")]
|
||||
public float MaxLength
|
||||
{
|
||||
get => _maxLength;
|
||||
@@ -180,6 +185,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// The lower limit allowed between the 2 bodies.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("minLength")]
|
||||
public float MinLength
|
||||
{
|
||||
get => _minLength;
|
||||
@@ -199,6 +205,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// The linear stiffness in N/m.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("stiffness")]
|
||||
public float Stiffness
|
||||
{
|
||||
get => _stiffness;
|
||||
@@ -217,6 +224,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// The linear damping in N*s/m.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("damping")]
|
||||
public float Damping
|
||||
{
|
||||
get => _damping;
|
||||
|
||||
@@ -97,14 +97,18 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// The maximum friction force in N.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxForce")]
|
||||
public float MaxForce { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum friction torque in N-m.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxTorque")]
|
||||
public float MaxTorque { get; set; }
|
||||
|
||||
public FrictionJoint() {}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for FrictionJoint.
|
||||
/// </summary>
|
||||
|
||||
@@ -26,6 +26,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
@@ -135,7 +136,10 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
/// </summary>
|
||||
public sealed class PrismaticJoint : Joint, IEquatable<PrismaticJoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// The local translation unit axis in bodyA.
|
||||
/// </summary>
|
||||
[DataField("localAxisA")]
|
||||
public Vector2 LocalAxisA
|
||||
{
|
||||
get => _localAxisA;
|
||||
@@ -149,25 +153,46 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
private Vector2 _localAxisA;
|
||||
|
||||
/// <summary>
|
||||
/// The constrained angle between the bodies: bodyB_angle - bodyA_angle.
|
||||
/// </summary>
|
||||
[DataField("referenceANgle")]
|
||||
public float ReferenceAngle;
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable the joint limit.
|
||||
/// </summary>
|
||||
[DataField("enableLimit")]
|
||||
public bool EnableLimit;
|
||||
|
||||
/// <summary>
|
||||
/// The lower translation limit, usually in meters.
|
||||
/// </summary>
|
||||
[DataField("lowerTranslation")]
|
||||
public float LowerTranslation;
|
||||
|
||||
/// <summary>
|
||||
/// The upper translation limit, usually in meters.
|
||||
/// </summary>
|
||||
[DataField("upperTranslation")]
|
||||
public float UpperTranslation;
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable the joint motor.
|
||||
/// </summary>
|
||||
[DataField("enableMotor")]
|
||||
public bool EnableMotor;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum motor torque, usually in N-m.
|
||||
/// </summary>
|
||||
[DataField("maxMotorForce")]
|
||||
public float MaxMotorForce;
|
||||
|
||||
/// <summary>
|
||||
/// The desired motor speed in radians per second.
|
||||
/// </summary>
|
||||
[DataField("motorSpeed")]
|
||||
public float MotorSpeed;
|
||||
|
||||
internal Vector2 _localXAxisA;
|
||||
@@ -194,6 +219,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
private float _translation;
|
||||
private float _axialMass;
|
||||
|
||||
public PrismaticJoint() {}
|
||||
|
||||
public PrismaticJoint(EntityUid bodyAUid, EntityUid bodyBUid) : base(bodyAUid, bodyBUid)
|
||||
{
|
||||
LocalAxisA = new Vector2(1f, 0f);
|
||||
|
||||
@@ -25,6 +25,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
@@ -84,39 +85,48 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
private float _upperImpulse;
|
||||
|
||||
// Settable
|
||||
[DataField("enableLimit")]
|
||||
public bool EnableLimit;
|
||||
|
||||
/// <summary>
|
||||
/// A flag to enable the joint motor.
|
||||
/// </summary>
|
||||
[DataField("enableMotor")]
|
||||
public bool EnableMotor;
|
||||
|
||||
/// <summary>
|
||||
/// The bodyB angle minus bodyA angle in the reference state (radians).
|
||||
/// </summary>
|
||||
[DataField("referenceAngle")]
|
||||
public float ReferenceAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The lower angle for the joint limit (radians).
|
||||
/// </summary>
|
||||
[DataField("lowerAngle")]
|
||||
public float LowerAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The upper angle for the joint limit (radians).
|
||||
/// </summary>
|
||||
[DataField("upperAngle")]
|
||||
public float UpperAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The desired motor speed. Usually in radians per second.
|
||||
/// </summary>
|
||||
[DataField("motorSpeed")]
|
||||
public float MotorSpeed;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum motor torque used to achieve the desired motor speed.
|
||||
/// Usually in N-m.
|
||||
/// </summary>
|
||||
[DataField("maxMotorTorque")]
|
||||
public float MaxMotorTorque;
|
||||
|
||||
public RevoluteJoint() {}
|
||||
|
||||
public RevoluteJoint(PhysicsComponent bodyA, PhysicsComponent bodyB, Vector2 anchor) : base(bodyA.Owner, bodyB.Owner)
|
||||
{
|
||||
LocalAnchorA = bodyA.GetLocalPoint(anchor);
|
||||
|
||||
@@ -121,6 +121,23 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
public MapId MapId => _entityManager.GetComponent<TransformComponent>(Owner).MapID;
|
||||
|
||||
#region AddRemove
|
||||
|
||||
public void AddBody(PhysicsComponent body)
|
||||
{
|
||||
if (Bodies.Contains(body)) return;
|
||||
|
||||
// TODO: Kinda dodgy with this and wake shit.
|
||||
// Look at my note under ProcessWakeQueue
|
||||
if (body.Awake && body.BodyType != BodyType.Static)
|
||||
{
|
||||
_queuedWake.Remove(body);
|
||||
AwakeBodies.Add(body);
|
||||
}
|
||||
|
||||
Bodies.Add(body);
|
||||
body.PhysicsMap = this;
|
||||
}
|
||||
|
||||
public void AddAwakeBody(PhysicsComponent body)
|
||||
{
|
||||
_queuedWake.Add(body);
|
||||
@@ -138,6 +155,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
_queuedSleep.Add(body);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Queue
|
||||
@@ -165,22 +183,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBody(PhysicsComponent body)
|
||||
{
|
||||
if (Bodies.Contains(body)) return;
|
||||
|
||||
// TODO: Kinda dodgy with this and wake shit.
|
||||
// Look at my note under ProcessWakeQueue
|
||||
if (body.Awake && body.BodyType != BodyType.Static)
|
||||
{
|
||||
_queuedWake.Remove(body);
|
||||
AwakeBodies.Add(body);
|
||||
}
|
||||
|
||||
Bodies.Add(body);
|
||||
body.PhysicsMap = this;
|
||||
}
|
||||
|
||||
private void ProcessWakeQueue()
|
||||
{
|
||||
foreach (var body in _queuedWake)
|
||||
@@ -300,6 +302,14 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
// TODO: When this gets ECSd add a helper and remove
|
||||
|
||||
if (seed.Deleted)
|
||||
{
|
||||
// This should never happen. Yet it does.
|
||||
Logger.Error($"Deleted physics component in awake bodies set. Owner Uid: {seed.Owner}. Physics map: {_entityManager.ToPrettyString(Owner)}");
|
||||
RemoveBody(seed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// I tried not running prediction for non-contacted entities but unfortunately it looked like shit
|
||||
// when contact broke so if you want to try that then GOOD LUCK.
|
||||
if (seed.Island ||
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Robust.Shared.Physics
|
||||
// (probably change this someday for perf reasons?)
|
||||
foreach (var fixture in component.SerializedFixtures)
|
||||
{
|
||||
fixture.ID = GetFixtureName(component, fixture);
|
||||
EnsureFixtureId(component, fixture);
|
||||
|
||||
if (component.Fixtures.TryAdd(fixture.ID, fixture)) continue;
|
||||
|
||||
@@ -93,6 +93,21 @@ namespace Robust.Shared.Physics
|
||||
|
||||
#region Public
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add a new fixture. Will do nothing if a fixture with the requested ID already exists.
|
||||
/// </summary>
|
||||
public bool TryCreateFixture(PhysicsComponent body, Fixture fixture, bool updates = true, FixturesComponent? manager = null, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(body.Owner, ref manager, ref xform, false))
|
||||
return false;
|
||||
|
||||
if (!string.IsNullOrEmpty(fixture.ID) && manager.Fixtures.ContainsKey(fixture.ID))
|
||||
return false;
|
||||
|
||||
CreateFixture(body, fixture, updates, manager, xform);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CreateFixture(PhysicsComponent body, Fixture fixture, bool updates = true, FixturesComponent? manager = null, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(body.Owner, ref manager, ref xform))
|
||||
@@ -101,7 +116,7 @@ namespace Robust.Shared.Physics
|
||||
return;
|
||||
}
|
||||
|
||||
fixture.ID = GetFixtureName(manager, fixture);
|
||||
EnsureFixtureId(manager, fixture);
|
||||
manager.Fixtures.Add(fixture.ID, fixture);
|
||||
fixture.Body = body;
|
||||
|
||||
@@ -120,7 +135,7 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
FixtureUpdate(manager, body);
|
||||
body.ResetMassData();
|
||||
manager.Dirty();
|
||||
Dirty(manager);
|
||||
}
|
||||
// TODO: Set newcontacts to true.
|
||||
}
|
||||
@@ -350,7 +365,10 @@ namespace Robust.Shared.Physics
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFixtureName(FixturesComponent component, Fixture fixture)
|
||||
/// <summary>
|
||||
/// Return the fixture's id if it has one. Otherwise, this automatically generates a fixture id and stores it.
|
||||
/// </summary>
|
||||
private string EnsureFixtureId(FixturesComponent component, Fixture fixture)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(fixture.ID)) return fixture.ID;
|
||||
|
||||
@@ -364,6 +382,7 @@ namespace Robust.Shared.Physics
|
||||
|
||||
if (!found)
|
||||
{
|
||||
fixture.AutoGeneratedId = name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
public abstract class SharedBroadphaseSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
|
||||
|
||||
private const int MinimumBroadphaseCapacity = 256;
|
||||
@@ -41,6 +41,8 @@ namespace Robust.Shared.Physics
|
||||
private Dictionary<FixtureProxy, Box2> _gridMoveBuffer = new(64);
|
||||
private List<FixtureProxy> _queryBuffer = new(32);
|
||||
|
||||
private List<MapGrid> _gridsPool = new(8);
|
||||
|
||||
/// <summary>
|
||||
/// How much to expand bounds by to check cross-broadphase collisions.
|
||||
/// Ideally you want to set this to your largest body size.
|
||||
@@ -188,9 +190,11 @@ namespace Robust.Shared.Physics
|
||||
continue;
|
||||
}
|
||||
|
||||
_gridsPool.Clear();
|
||||
|
||||
// Get every broadphase we may be intersecting.
|
||||
// Also TODO: Don't put grids on movebuffer so you get peak shuttle driving performance.
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), xformQuery, physicsQuery))
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), _gridsPool, xformQuery, physicsQuery))
|
||||
{
|
||||
FindPairs(proxy, worldAABB, grid.GridEntityId, xformQuery, broadphaseQuery);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
@@ -48,11 +49,15 @@ namespace Robust.Shared.Physics
|
||||
// To avoid issues with component states we'll queue up all dirty joints and check it every tick to see if
|
||||
// we can delete the component.
|
||||
private HashSet<JointComponent> _dirtyJoints = new();
|
||||
private HashSet<Joint> _addedJoints = new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = Logger.GetSawmill("physics");
|
||||
UpdatesOutsidePrediction = true;
|
||||
|
||||
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
|
||||
@@ -102,6 +107,13 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var joint in _addedJoints)
|
||||
{
|
||||
InitJoint(joint);
|
||||
}
|
||||
|
||||
_addedJoints.Clear();
|
||||
|
||||
foreach (var joint in _dirtyJoints)
|
||||
{
|
||||
if (joint.Deleted || joint.JointCount != 0) continue;
|
||||
@@ -111,6 +123,65 @@ namespace Robust.Shared.Physics
|
||||
_dirtyJoints.Clear();
|
||||
}
|
||||
|
||||
private void InitJoint(Joint joint)
|
||||
{
|
||||
var bodyA = joint.BodyA;
|
||||
var bodyB = joint.BodyB;
|
||||
|
||||
var jointComponentA = EntityManager.EnsureComponent<JointComponent>(bodyA.Owner);
|
||||
var jointComponentB = EntityManager.EnsureComponent<JointComponent>(bodyB.Owner);
|
||||
var jointsA = jointComponentA.Joints;
|
||||
var jointsB = jointComponentB.Joints;
|
||||
|
||||
if (jointsA.ContainsKey(joint.ID))
|
||||
{
|
||||
// If they both already have it we should be gucci
|
||||
// This can occur because of client states coming in blah blah
|
||||
// The reason for this is we defer everything until Update
|
||||
// (and the reason we defer is to avoid modifying components during iteration when we do the EnsureComponent)
|
||||
if (jointsB.ContainsKey(joint.ID)) return;
|
||||
|
||||
_sawmill.Error($"Existing joint {joint.ID} on {bodyA.Owner}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (jointsB.ContainsKey(joint.ID))
|
||||
{
|
||||
_sawmill.Error($"Existing joint {joint.ID} on {bodyB.Owner}");
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Debug($"Added joint {joint.ID}");
|
||||
|
||||
jointsA.Add(joint.ID, joint);
|
||||
jointsB.Add(joint.ID, joint);
|
||||
|
||||
// If the joint prevents collisions, then flag any contacts for filtering.
|
||||
if (!joint.CollideConnected)
|
||||
{
|
||||
FilterContactsForJoint(joint);
|
||||
}
|
||||
|
||||
bodyA.WakeBody();
|
||||
bodyB.WakeBody();
|
||||
Dirty(bodyA);
|
||||
Dirty(bodyB);
|
||||
Dirty(jointComponentA);
|
||||
Dirty(jointComponentB);
|
||||
|
||||
// Also flag these for checking juusssttt in case.
|
||||
_dirtyJoints.Add(jointComponentA);
|
||||
_dirtyJoints.Add(jointComponentB);
|
||||
// Note: creating a joint doesn't wake the bodies.
|
||||
|
||||
// Raise broadcast last so we can do both sides of directed first.
|
||||
var vera = new JointAddedEvent(joint, bodyA, bodyB);
|
||||
EntityManager.EventBus.RaiseLocalEvent(bodyA.Owner, vera, false);
|
||||
var smug = new JointAddedEvent(joint, bodyB, bodyA);
|
||||
EntityManager.EventBus.RaiseLocalEvent(bodyB.Owner, smug, false);
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, vera);
|
||||
}
|
||||
|
||||
private static string GetJointId(Joint joint)
|
||||
{
|
||||
var id = joint.ID;
|
||||
@@ -262,72 +333,26 @@ namespace Robust.Shared.Physics
|
||||
if (mapidA == MapId.Nullspace ||
|
||||
mapidA != EntityManager.GetComponent<TransformComponent>(bodyB.Owner).MapID)
|
||||
{
|
||||
Logger.ErrorS("physics", $"Tried to add joint to ineligible bodies");
|
||||
_sawmill.Error($"Tried to add joint to ineligible bodies");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(joint.ID))
|
||||
{
|
||||
Logger.ErrorS("physics", $"Can't add a joint with no ID");
|
||||
_sawmill.Error($"Can't add a joint with no ID");
|
||||
DebugTools.Assert($"Can't add a joint with no ID");
|
||||
return;
|
||||
}
|
||||
|
||||
var jointComponentA = EntityManager.EnsureComponent<JointComponent>(bodyA.Owner);
|
||||
var jointComponentB = EntityManager.EnsureComponent<JointComponent>(bodyB.Owner);
|
||||
var jointsA = jointComponentA.Joints;
|
||||
var jointsB = jointComponentB.Joints;
|
||||
|
||||
if (jointsA.ContainsKey(joint.ID))
|
||||
{
|
||||
// If they both already have it we should be gucci
|
||||
// This can occur because of client states coming in blah blah
|
||||
// The reason for this is we defer everything until Update
|
||||
// (and the reason we defer is to avoid modifying components during iteration when we do the EnsureComponent)
|
||||
if (jointsB.ContainsKey(joint.ID)) return;
|
||||
|
||||
Logger.ErrorS("physics", $"Existing joint {joint.ID} on {bodyA.Owner}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (jointsB.ContainsKey(joint.ID))
|
||||
{
|
||||
Logger.ErrorS("physics", $"Existing joint {joint.ID} on {bodyB.Owner}");
|
||||
return;
|
||||
}
|
||||
Logger.DebugS("physics", $"Added joint {joint.ID}");
|
||||
|
||||
|
||||
jointsA.Add(joint.ID, joint);
|
||||
jointsB.Add(joint.ID, joint);
|
||||
|
||||
// If the joint prevents collisions, then flag any contacts for filtering.
|
||||
if (!joint.CollideConnected)
|
||||
{
|
||||
FilterContactsForJoint(joint);
|
||||
}
|
||||
|
||||
bodyA.WakeBody();
|
||||
bodyB.WakeBody();
|
||||
bodyA.Dirty();
|
||||
bodyB.Dirty();
|
||||
jointComponentA.Dirty();
|
||||
jointComponentB.Dirty();
|
||||
// Note: creating a joint doesn't wake the bodies.
|
||||
|
||||
// Raise broadcast last so we can do both sides of directed first.
|
||||
var vera = new JointAddedEvent(joint, bodyA, bodyB);
|
||||
EntityManager.EventBus.RaiseLocalEvent(bodyA.Owner, vera, false);
|
||||
var smug = new JointAddedEvent(joint, bodyB, bodyA);
|
||||
EntityManager.EventBus.RaiseLocalEvent(bodyB.Owner, smug, false);
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, vera);
|
||||
// Need to defer this for prediction reasons, yay!
|
||||
_addedJoints.Add(joint);
|
||||
}
|
||||
|
||||
public void ClearJoints(PhysicsComponent body)
|
||||
{
|
||||
if (!EntityManager.HasComponent<JointComponent>(body.Owner)) return;
|
||||
if (!TryComp<JointComponent>(body.Owner, out var joint)) return;
|
||||
|
||||
EntityManager.RemoveComponent<JointComponent>(body.Owner);
|
||||
_dirtyJoints.Add(joint);
|
||||
}
|
||||
|
||||
public void RemoveJoint(Joint joint)
|
||||
@@ -357,7 +382,7 @@ namespace Robust.Shared.Physics
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.DebugS("physics", $"Removed joint {joint.ID}");
|
||||
_sawmill.Debug($"Removed joint {joint.ID}");
|
||||
|
||||
// Wake up connected bodies.
|
||||
if (EntityManager.TryGetComponent<PhysicsComponent>(bodyAUid, out var bodyA) &&
|
||||
@@ -374,12 +399,12 @@ namespace Robust.Shared.Physics
|
||||
|
||||
if (!jointComponentA.Deleted)
|
||||
{
|
||||
jointComponentA.Dirty(EntityManager);
|
||||
Dirty(jointComponentA);
|
||||
}
|
||||
|
||||
if (!jointComponentB.Deleted)
|
||||
{
|
||||
jointComponentB.Dirty(EntityManager);
|
||||
Dirty(jointComponentB);
|
||||
}
|
||||
|
||||
if (jointComponentA.Deleted && jointComponentB.Deleted)
|
||||
|
||||
@@ -96,6 +96,12 @@ namespace Robust.Shared.Player
|
||||
return this;
|
||||
}
|
||||
|
||||
public static IEnumerable<ICommonSession> GetAllPlayers(ISharedPlayerManager? playerManager = null)
|
||||
{
|
||||
IoCManager.Resolve(ref playerManager);
|
||||
return playerManager.NetworkedSessions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all players to the filter.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user