mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
4 Commits
v247.0.1
...
serializat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
190c35dbdf | ||
|
|
b52c26d0eb | ||
|
|
35c983d2fd | ||
|
|
88cfd04f01 |
@@ -57,7 +57,7 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
195
RELEASE-NOTES.md
195
RELEASE-NOTES.md
@@ -54,201 +54,10 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 247.0.1
|
||||
## 240.1.3-source-gen-debug
|
||||
|
||||
|
||||
## 247.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `ITileDefinitionManager.AssignAlias` and general tile alias functionality has been removed. `TileAliasPrototype` still exist, but are only used during entity deserialization.
|
||||
* `IMapManager.AddUninitializedMap` has been removed. Use the map-init options on `CreateMap()` instead.
|
||||
* Re-using a MapId will now log a warning. This may cause some integration tests to fail if they are configured to fail
|
||||
when warnings are logged.
|
||||
* The minimum supported map format / version has been increased from 2 to 3.
|
||||
* The server-side `MapLoaderSystem` and associated classes & structs has been moved to `Robust.Shared`, and has been significantly modified.
|
||||
* The`TryLoad` and `Save` methods have been replaced with grid, map, generic entity variants. I.e, `SaveGrid`, `SaveMap`, and `SaveEntities`.
|
||||
* Most of the serialization logic and methods have been moved out of `MapLoaderSystem` and into new `EntitySerializer`
|
||||
and `EntityDeserializer` classes, which also replace the old `MapSerializationContext`.
|
||||
* The `MapLoadOptions` class has been split into `MapLoadOptions`, `SerializationOptions`, and `DeserializationOptions`
|
||||
structs.
|
||||
* The interaction between PVS overrides and visibility masks / layers have changed:
|
||||
* Any forced entities (i.e., `PvsOverrideSystem.AddForceSend()`) now ignore visibility masks.
|
||||
* Any global & session overrides (`PvsOverrideSystem.AddGlobalOverride()` & `PvsOverrideSystem.AddSessionOverride()`) now respect visibility masks.
|
||||
* Entities added via the `ExpandPvsEvent` respect visibility masks.
|
||||
* The mask used for any global/session overrides can be modified via `ExpandPvsEvent.Mask`.
|
||||
* Toolshed Changes:
|
||||
* The signature of Toolshed type parsers have changed. Instead of taking in an optional command argument name string, they now take in a `CommandArgument` struct.
|
||||
* Toolshed commands can no longer contain a '|', as this symbol is now used for explicitly piping the output of one command to another. command pipes. The existing `|` and '|~' commands have been renamed to `bitor` and `bitnotor`.
|
||||
* Semicolon terminated command blocks in toolshed commands no longer return anything. I.e., `i { i 2 ; }` is no longer a valid command, as the block has no return value.
|
||||
|
||||
### New features
|
||||
|
||||
* The current map format/version has increased from 6 to 7 and now contains more information to try support serialization of maps with null-space entities and full game saves.
|
||||
* `IEntitySystemManager` now provides access to the system `IDependencyCollection`.
|
||||
* Toolshed commands now support optional and `params T[]` arguments. optional / variable length commands can be terminated using ';' or '|'.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed entity deserialization for components with a data fields that have a AlwaysPushInheritance Attribute
|
||||
* Audio entities attached to invisible / masked entities should no longer be able to temporarily make those entities visible to all players.
|
||||
* The map-like Toolshed commands now work when a collection is piped in.
|
||||
* Fixed a bug in toolshed that could cause it to preferentially use the incorrect command implementation.
|
||||
* E.g., passing a concrete enumerable type would previously use the command implementation that takes in an unconstrained generic parameter `T` instead of a dedicated `IEnumeerable<T>` implementation.
|
||||
|
||||
### Other
|
||||
|
||||
* `MapChangedEvent` has been marked as obsolete, and should be replaced with `MapCreatedEvent` and `MapRemovedEvent.
|
||||
* The default auto-completion hint for Toolshed commands have been changed and somewhat standardized. Most parsers should now generate a hint of the form:
|
||||
* `<name (Type)>` for mandatory arguments
|
||||
* `[name (Type)]` for optional arguments
|
||||
* `[name (Type)]...` for variable length arguments (i.e., for `params T[]`)
|
||||
|
||||
|
||||
## 246.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The fixes to renderer state may have inadvertantly broken some rendering code that relied upon the old behavior.
|
||||
* TileRenderFlag has been removed and now it's just a byte flag on the tile for content usage.
|
||||
|
||||
### New features
|
||||
|
||||
* Add BeforeLighting overlay draw space for overlays that need to draw directly to lighting and want to do it immediately beforehand.
|
||||
* Change BlurLights to BlurRenderTarget and make it public for content usage.
|
||||
* Add ContentFlag to tiles for content-flag usage.
|
||||
* Add a basic mix shader for doing canvas blends.
|
||||
* Add GetClearColorEvent for content to override the clear color behavior.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix pushing renderer state not restoring stencil status, blend status, queued shader instance scissor state.
|
||||
|
||||
|
||||
## 245.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add more info to the AnchorEntity debug message.
|
||||
* Make ParseObject public where it will parse a supplied Type and string into the specified object.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix EntityPrototypeView not always updating the entity correctly.
|
||||
* Tweak BUI shutdown to potentially avoid skipping closing.
|
||||
|
||||
### Other
|
||||
|
||||
* Increase Audio entity despawn buffer to avoid clipping.
|
||||
|
||||
|
||||
## 245.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `BoundUserInterface.Open()` now has the `MustCallBase` attribute
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an error in `MappingDataNode.TryAddCopy()`, which was causing yaml inheritance/deserialization bugs.
|
||||
|
||||
|
||||
## 244.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Increase physics speedcap default from 35m/s to 400m/s in-line with box2d v3.
|
||||
|
||||
### New features
|
||||
|
||||
* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods.
|
||||
* Add CreateWindowCenteredRight for BUIs.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Avoid calling UpdateState before opening a BUI.
|
||||
|
||||
|
||||
## 243.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `BaseWindow` sometimes not properly updating the mouse cursor shape.
|
||||
* Revert `BaseWindow` OnClose ordering due to prior reliance upon the ordering.
|
||||
|
||||
|
||||
## 243.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* RemoveChild is called after OnClose for BaseWindow.
|
||||
|
||||
### New features
|
||||
|
||||
* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow<T>` helper or via manually registering it via RegisterControl.
|
||||
|
||||
### Other
|
||||
|
||||
* Ensure grid fixtures get updated in client state handling even if exceptions occur.
|
||||
|
||||
|
||||
## 242.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed prototype reloading/hotloading not properly handling data-fields with the `AlwaysPushInheritanceAttribute`
|
||||
* Fix the pooled polygons using incorrect vertices for EntityLookup and MapManager.
|
||||
|
||||
### Internal
|
||||
|
||||
* Avoid normalizing angles constructed from vectors.
|
||||
|
||||
|
||||
## 242.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The order in which the client initialises networked entities has changed. It will now always apply component states, initialise, and start an entity's parent before processing any children. This might break anything that was relying on the old behaviour where all component states were applied before any entities were initialised & started.
|
||||
* `IClydeViewport` overlay rendering methods now take in an `IRenderHandle` instead of a world/screen handle.
|
||||
* The `OverlayDrawArgs` struct now has an internal constructor.
|
||||
|
||||
### New features
|
||||
|
||||
* Controls can now be manually restyled via `Control.InvalidateStyleSheet()` and `Control.DoStyleUpdate()`
|
||||
* Added `IUserInterfaceManager.RenderControl()` for manually drawing controls.
|
||||
* `OverlayDrawArgs` struct now has an `IRenderHandle` field such that overlays can use the new `RenderControl()` methods.
|
||||
* TileSpawnWindow will now take focus when opened.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a client-side bug where `TransformComponent.GridUid` does not get set properly when an existing entity is attached to a new entity outside of the player's PVS range.
|
||||
* EntityPrototypeView will only create entities when it's on the UI tree and not when the prototype is set.
|
||||
* Make CollisionWake not log errors if it can't resolve.
|
||||
|
||||
### Other
|
||||
|
||||
* Replace IPhysShape API with generics on IMapManager and EntityLookupSystem.
|
||||
|
||||
### Internal
|
||||
|
||||
* Significantly reduce allocations for Box2 / Box2Rotated queries.
|
||||
|
||||
|
||||
## 241.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove DeferredClose from BUIs.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `EntityManager.DirtyFields()`, which allows components with delta states to simultaneously mark several fields as dirty at the same time.
|
||||
* Add `CloserUserUIs<T>` to close keys of a specific key.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `RaisePredictiveEvent()` not properly re-raising events during prediction for event handlers that did not take an `EntitySessionEventArgs` argument.
|
||||
* BUI openings are now deferred to avoid having slight desync between deferred closes and opens occurring in the same tick.
|
||||
## 240.1.2-source-gen-debug
|
||||
|
||||
|
||||
## 240.1.2
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
kind: canvas
|
||||
light_mode: unshaded
|
||||
|
||||
# Simple mix blend
|
||||
- type: shader
|
||||
id: Mix
|
||||
kind: canvas
|
||||
blend_mode: Mix
|
||||
|
||||
- type: shader
|
||||
id: shaded
|
||||
kind: canvas
|
||||
|
||||
@@ -42,7 +42,8 @@ command-description-as =
|
||||
command-description-count =
|
||||
Counts the amount of entries in it's input, returning an integer.
|
||||
command-description-map =
|
||||
Maps the input over the given block.
|
||||
Maps the input over the given block, with the provided expected return type.
|
||||
This command may be modified to not need an explicit return type in the future.
|
||||
command-description-select =
|
||||
Selects N objects or N% of objects from the input.
|
||||
One can additionally invert this command with not to make it select everything except N objects instead.
|
||||
@@ -148,7 +149,7 @@ command-description-max =
|
||||
Returns the maximum of two values.
|
||||
command-description-BitAndCommand =
|
||||
Performs bitwise AND.
|
||||
command-description-bitor =
|
||||
command-description-BitOrCommand =
|
||||
Performs bitwise OR.
|
||||
command-description-BitXorCommand =
|
||||
Performs bitwise XOR.
|
||||
@@ -202,11 +203,11 @@ command-description-mappos =
|
||||
command-description-pos =
|
||||
Returns an entity's coordinates.
|
||||
command-description-tp-coords =
|
||||
Teleports the given entities to the target coordinates.
|
||||
Teleports the target to the given coordinates.
|
||||
command-description-tp-to =
|
||||
Teleports the given entities to the target entity.
|
||||
Teleports the target to the given other entity.
|
||||
command-description-tp-into =
|
||||
Teleports the given entities "into" the target entity, attaching it at (0 0) relative to it.
|
||||
Teleports the target "into" the given other entity, attaching it at (0 0) relative to it.
|
||||
command-description-comp-get =
|
||||
Gets the given component from the given entity.
|
||||
command-description-comp-add =
|
||||
@@ -276,7 +277,7 @@ command-description-ModVecCommand =
|
||||
Performs the modulus operation over the input with the given constant right-hand value.
|
||||
command-description-BitAndNotCommand =
|
||||
Performs bitwise AND-NOT over the input.
|
||||
command-description-bitornot =
|
||||
command-description-BitOrNotCommand =
|
||||
Performs bitwise OR-NOT over the input.
|
||||
command-description-BitXnorCommand =
|
||||
Performs bitwise XNOR over the input.
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
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 partial class HasComponentBenchmark
|
||||
{
|
||||
private static readonly Consumer Consumer = new();
|
||||
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
|
||||
private ComponentRegistration _compReg = default!;
|
||||
|
||||
private A _dummyA = new();
|
||||
|
||||
[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 map = _simulation.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
_compReg = _entityManager.ComponentFactory.GetRegistration(typeof(A));
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
var uid = _entityManager.SpawnEntity(null, coords);
|
||||
_entityManager.AddComponent<A>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentGeneric()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent<A>(uid);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentCompReg()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent(uid, _compReg);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentType()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent(uid, typeof(A));
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentGetType()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var type = _dummyA.GetType();
|
||||
var result = _entityManager.HasComponent(uid, type);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed partial class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal static class Program
|
||||
public static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -25,7 +24,6 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -63,10 +61,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -382,7 +382,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -101,33 +101,11 @@ namespace Robust.Client.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
// Client only dirties during prediction
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
public override void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
// I.e., can't it just dirty the whole component to trigger a reset?
|
||||
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.DirtyField(uid, comp, fieldName, metadata);
|
||||
}
|
||||
|
||||
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
// I.e., can't it just dirty the whole component to trigger a reset?
|
||||
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.DirtyFields(uid, comp, meta, fields);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
var id = new MapId(--LastMapId);
|
||||
while (MapExists(id) || UsedIds.Contains(id))
|
||||
while (MapManager.MapExists(id))
|
||||
{
|
||||
id = new MapId(--LastMapId);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
private Dictionary<EntityUid, Dictionary<Enum, Vector2>> _savedPositions = new();
|
||||
private Dictionary<BoundUserInterface, Control> _registeredControls = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -25,59 +17,6 @@ public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
ProtoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
protected override void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
base.OnUserInterfaceShutdown(ent, ref args);
|
||||
_savedPositions.Remove(ent.Owner);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OpenUi(Entity<UserInterfaceComponent?> entity, Enum key, bool predicted = false)
|
||||
{
|
||||
var player = Player.LocalEntity;
|
||||
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
OpenUi(entity, key, player.Value, predicted);
|
||||
}
|
||||
|
||||
protected override void SavePosition(BoundUserInterface bui)
|
||||
{
|
||||
if (!_registeredControls.Remove(bui, out var control))
|
||||
return;
|
||||
|
||||
var keyed = _savedPositions[bui.Owner];
|
||||
keyed[bui.UiKey] = control.Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a control so it will later have its position stored by <see cref="SavePosition"/> when the BUI is closed.
|
||||
/// </summary>
|
||||
public void RegisterControl(BoundUserInterface bui, Control control)
|
||||
{
|
||||
DebugTools.Assert(!_registeredControls.ContainsKey(bui));
|
||||
_registeredControls[bui] = control;
|
||||
_savedPositions.GetOrNew(bui.Owner);
|
||||
}
|
||||
|
||||
public override bool TryGetPosition(Entity<UserInterfaceComponent?> entity, Enum key, out Vector2 position)
|
||||
{
|
||||
position = default;
|
||||
|
||||
if (!_savedPositions.TryGetValue(entity.Owner, out var keyed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keyed.TryGetValue(key, out position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
var player = Player.LocalEntity;
|
||||
|
||||
@@ -23,6 +23,7 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Profiling;
|
||||
@@ -41,13 +42,13 @@ namespace Robust.Client.GameStates
|
||||
private uint _nextInputCmdSeq = 1;
|
||||
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
|
||||
|
||||
private readonly Queue<(uint sequence, GameTick sourceTick, object msg, object sessionMsg)>
|
||||
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
|
||||
_pendingSystemMessages
|
||||
= new();
|
||||
|
||||
// Game state dictionaries that get used every tick.
|
||||
private readonly Dictionary<EntityUid, StateData> _toApply = new();
|
||||
private StateData[] _toApplySorted = default!;
|
||||
private readonly Dictionary<EntityUid, (NetEntity NetEntity, MetaDataComponent Meta, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
|
||||
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
|
||||
private readonly Dictionary<ushort, (IComponent Component, IComponentState? curState, IComponentState? nextState)> _compStateWork = new();
|
||||
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
|
||||
private readonly HashSet<NetEntity> _stateEnts = new();
|
||||
@@ -55,29 +56,15 @@ namespace Robust.Client.GameStates
|
||||
private readonly List<IComponent> _toRemove = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
|
||||
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
|
||||
private readonly HashSet<EntityUid> _sorted = new();
|
||||
private readonly List<NetEntity> _created = new();
|
||||
private readonly List<NetEntity> _detached = new();
|
||||
|
||||
private readonly record struct StateData(
|
||||
EntityUid Uid,
|
||||
NetEntity NetEntity,
|
||||
MetaDataComponent Meta,
|
||||
bool Created,
|
||||
bool EnteringPvs,
|
||||
GameTick LastApplied,
|
||||
EntityState? CurState,
|
||||
EntityState? NextState,
|
||||
HashSet<Type>? PendingReapply);
|
||||
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
|
||||
|
||||
private uint _metaCompNetId;
|
||||
private uint _xformCompNetId;
|
||||
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IClientNetManager _network = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
@@ -85,7 +72,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entities = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly ProfManager _prof = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
@@ -139,6 +126,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private bool _resettingPredictedEntities;
|
||||
private readonly List<EntityUid> _brokenEnts = new();
|
||||
private readonly List<(EntityUid, NetEntity)> _toStart = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -184,12 +172,6 @@ namespace Robust.Client.GameStates
|
||||
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
|
||||
|
||||
_metaCompNetId = metaId.Value;
|
||||
|
||||
var xformId = _compFactory.GetRegistration(typeof(TransformComponent)).NetID;
|
||||
if (!xformId.HasValue)
|
||||
throw new InvalidOperationException("TransformComponent does not have a NetId.");
|
||||
|
||||
_xformCompNetId = xformId.Value;
|
||||
}
|
||||
|
||||
private void OnComponentAdded(AddedComponentEventArgs args)
|
||||
@@ -201,11 +183,11 @@ namespace Robust.Client.GameStates
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
|
||||
if (_entities.IsClientSide(args.BaseArgs.Owner))
|
||||
if (_entityManager.IsClientSide(args.BaseArgs.Owner))
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} to entity {_entities.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Added component {comp.Name} to entity {_entityManager.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
@@ -403,7 +385,7 @@ namespace Robust.Client.GameStates
|
||||
try
|
||||
{
|
||||
#endif
|
||||
ApplyGameState(curState, nextState);
|
||||
createdEntities = ApplyGameState(curState, nextState);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (MissingMetadataException e)
|
||||
@@ -417,7 +399,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("MergeImplicitData"))
|
||||
{
|
||||
MergeImplicitData();
|
||||
GenerateImplicitStates(createdEntities);
|
||||
}
|
||||
|
||||
if (_lastProcessedInput < curState.LastProcessedInput)
|
||||
@@ -474,7 +456,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("Tick"))
|
||||
{
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled, histogram: null);
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +504,9 @@ namespace Robust.Client.GameStates
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= _timing.CurTick)
|
||||
{
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.msg);
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
}
|
||||
@@ -561,7 +545,7 @@ namespace Robust.Client.GameStates
|
||||
PredictionNeedsResetting = false;
|
||||
var countReset = 0;
|
||||
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
|
||||
var metaQuery = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
RemQueue<IComponent> toRemove = new();
|
||||
|
||||
foreach (var entity in system.DirtyEntities)
|
||||
@@ -648,7 +632,7 @@ namespace Robust.Client.GameStates
|
||||
if (!last.TryGetValue(netId, out var state))
|
||||
continue;
|
||||
|
||||
var comp = _entities.AddComponent(entity, netId, meta);
|
||||
var comp = _entityManager.AddComponent(entity, netId, meta);
|
||||
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was removed: {comp.GetType()}");
|
||||
@@ -668,7 +652,7 @@ namespace Robust.Client.GameStates
|
||||
meta.EntityLastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
|
||||
_entities.System<PhysicsSystem>().ResetContacts();
|
||||
_entityManager.System<PhysicsSystem>().ResetContacts();
|
||||
|
||||
// TODO maybe reset more of physics?
|
||||
// E.g., warm impulses for warm starting?
|
||||
@@ -687,21 +671,21 @@ namespace Robust.Client.GameStates
|
||||
/// initial server state for any newly created entity. It does this by simply using the standard <see
|
||||
/// cref="IEntityManager.GetComponentState"/>.
|
||||
/// </remarks>
|
||||
public void MergeImplicitData()
|
||||
public void GenerateImplicitStates(IEnumerable<NetEntity> createdEntities)
|
||||
{
|
||||
var bus = _entities.EventBus;
|
||||
var bus = _entityManager.EventBus;
|
||||
|
||||
foreach (var netEntity in _created)
|
||||
foreach (var netEntity in createdEntities)
|
||||
{
|
||||
if (!_entities.TryGetEntityData(netEntity, out _, out var meta))
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
|
||||
{
|
||||
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
|
||||
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw new KeyNotFoundException();
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
#endif
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
@@ -710,13 +694,12 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entities.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
|
||||
_created.Clear();
|
||||
_processor.MergeImplicitData(_outputData);
|
||||
|
||||
foreach (var data in _outputData.Values)
|
||||
@@ -752,9 +735,10 @@ namespace Robust.Client.GameStates
|
||||
_config.TickProcessMessages();
|
||||
}
|
||||
|
||||
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
|
||||
using (_prof.Group("Entity"))
|
||||
{
|
||||
ApplyEntityStates(curState, nextState);
|
||||
output = ApplyEntityStates(curState, nextState);
|
||||
}
|
||||
|
||||
using (_prof.Group("Player"))
|
||||
@@ -764,13 +748,13 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("Callback"))
|
||||
{
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, _detached));
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, output.Detached));
|
||||
}
|
||||
|
||||
return _created;
|
||||
return output.Created;
|
||||
}
|
||||
|
||||
private void ApplyEntityStates(GameState curState, GameState? nextState)
|
||||
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
|
||||
{
|
||||
var metas = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
var xforms = _entities.GetEntityQuery<TransformComponent>();
|
||||
@@ -778,74 +762,90 @@ namespace Robust.Client.GameStates
|
||||
|
||||
var enteringPvs = 0;
|
||||
_toApply.Clear();
|
||||
_created.Clear();
|
||||
_toCreate.Clear();
|
||||
_pendingReapplyNetStates.Clear();
|
||||
var curSpan = curState.EntityStates.Span;
|
||||
|
||||
// Create new entities
|
||||
// This is done BEFORE state application to ensure any new parents exist before existing children have their states applied, otherwise, we may have issues with entity transforms!
|
||||
|
||||
using (_prof.Group("Create uninitialized entities"))
|
||||
{
|
||||
var created = 0;
|
||||
using var _ = _prof.Group("Create uninitialized entities");
|
||||
var count = 0;
|
||||
|
||||
foreach (var es in curSpan)
|
||||
{
|
||||
if (_entities.TryGetEntity(es.NetEntity, out var nUid))
|
||||
if (_entityManager.TryGetEntity(es.NetEntity, out var nUid))
|
||||
{
|
||||
DebugTools.Assert(_entities.EntityExists(nUid));
|
||||
DebugTools.Assert(_entityManager.EntityExists(nUid));
|
||||
continue;
|
||||
}
|
||||
|
||||
created++;
|
||||
CreateNewEntity(es, curState.ToSequence);
|
||||
count++;
|
||||
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(es.NetEntity);
|
||||
|
||||
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
|
||||
_toCreate.Add(es.NetEntity, es);
|
||||
_toApply.Add(uid, (es.NetEntity, newMeta, false, GameTick.Zero, es, null));
|
||||
|
||||
// Client creates a client-side net entity for the newly created entity.
|
||||
// We need to clear this mapping before assigning the real net id.
|
||||
// TODO NetEntity Jank: prevent the client from creating this in the first place.
|
||||
_entityManager.ClearNetEntity(newMeta.NetEntity);
|
||||
|
||||
_entityManager.SetNetEntity(uid, es.NetEntity, newMeta);
|
||||
newMeta.LastStateApplied = curState.ToSequence;
|
||||
|
||||
// Check if there's any component states awaiting this entity.
|
||||
if (_entityManager.PendingNetEntityStates.Remove(es.NetEntity, out var value))
|
||||
{
|
||||
foreach (var (type, owner) in value)
|
||||
{
|
||||
var pending = _pendingReapplyNetStates.GetOrNew(owner);
|
||||
pending.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_prof.WriteValue("Count", ProfData.Int32(created));
|
||||
_prof.WriteValue("Count", ProfData.Int32(count));
|
||||
}
|
||||
|
||||
// Add entity entities that aren't new to _toCreate.
|
||||
// In the process, we also check if these entities are re-entering PVS range.
|
||||
foreach (var es in curSpan)
|
||||
{
|
||||
if (!_entities.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
if (_toCreate.ContainsKey(es.NetEntity))
|
||||
continue;
|
||||
|
||||
var isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
|
||||
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
bool isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
|
||||
if (isEnteringPvs)
|
||||
{
|
||||
// _toApply already contains newly created entities, but these should never be "entering PVS"
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value));
|
||||
|
||||
meta.Flags &= ~MetaDataFlags.Detached;
|
||||
enteringPvs++;
|
||||
}
|
||||
else if (meta.LastStateApplied >= es.EntityLastModified && meta.LastStateApplied != GameTick.Zero)
|
||||
{
|
||||
// _toApply already contains newly created entities, but for those this set should have no effect
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value) || meta.LastStateApplied == curState.ToSequence);
|
||||
|
||||
meta.LastStateApplied = curState.ToSequence;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Any newly created entities already added to _toApply should've already been caught by the previous continue
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value));
|
||||
|
||||
_toApply.Add(uid.Value, new(uid.Value, es.NetEntity, meta, false, isEnteringPvs, meta.LastStateApplied, es, null, null));
|
||||
_toApply.Add(uid.Value, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
|
||||
meta.LastStateApplied = curState.ToSequence;
|
||||
}
|
||||
|
||||
// Detach entities to null space
|
||||
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
|
||||
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
|
||||
ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
var detached = ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
|
||||
// Check next state (AFTER having created new entities introduced in curstate)
|
||||
if (nextState != null)
|
||||
{
|
||||
foreach (var es in nextState.EntityStates.Span)
|
||||
{
|
||||
if (!_entities.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
// Does the next state actually have any future information about this entity that could be used for interpolation?
|
||||
@@ -854,14 +854,15 @@ namespace Robust.Client.GameStates
|
||||
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid.Value, out var exists);
|
||||
|
||||
state = exists
|
||||
? state with {NextState = es}
|
||||
: new(uid.Value, es.NetEntity, meta, false, false, GameTick.Zero, null, es, null);
|
||||
if (exists)
|
||||
state = (es.NetEntity, meta, state.EnteringPvs, state.LastApplied, state.curState, es);
|
||||
else
|
||||
state = (es.NetEntity, meta, false, GameTick.Zero, null, es);
|
||||
}
|
||||
}
|
||||
|
||||
// Check pending states and see if we need to force any entities to re-run component states.
|
||||
foreach (var (uid, pending) in _pendingReapplyNetStates)
|
||||
foreach (var uid in _pendingReapplyNetStates.Keys)
|
||||
{
|
||||
// Original entity referencing the NetEntity may have been deleted.
|
||||
if (!metas.TryGetComponent(uid, out var meta))
|
||||
@@ -878,30 +879,51 @@ namespace Robust.Client.GameStates
|
||||
|
||||
DebugTools.Assert(!curState.EntityDeletions.Value.Contains(meta.NetEntity));
|
||||
|
||||
// State already being re-applied so don't bulldoze it.
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
|
||||
|
||||
state = exists
|
||||
? state with {PendingReapply = pending}
|
||||
: new(uid, meta.NetEntity, meta, false, false, GameTick.Zero, null, null, pending);
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
state = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
|
||||
}
|
||||
|
||||
_queuedBroadphaseUpdates.Clear();
|
||||
|
||||
using (_prof.Group("Sort States"))
|
||||
{
|
||||
SortStates(_toApply);
|
||||
}
|
||||
|
||||
// Apply entity states.
|
||||
using (_prof.Group("Apply States"))
|
||||
{
|
||||
var span = _toApplySorted.AsSpan(0, _toApply.Count);
|
||||
foreach (ref var data in span)
|
||||
foreach (var (entity, data) in _toApply)
|
||||
{
|
||||
ApplyEntState(data, curState.ToSequence);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
|
||||
_entityManager.DeleteEntity(entity);
|
||||
RequestFullState();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!data.EnteringPvs)
|
||||
continue;
|
||||
|
||||
// Now that things like collision data, fixtures, and positions have been updated, we queue a
|
||||
// broadphase update. However, if this entity is parented to some other entity also re-entering PVS,
|
||||
// we only need to update it's parent (as it recursively updates children anyways).
|
||||
var xform = xforms.GetComponent(entity);
|
||||
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
|
||||
xform.Broadphase = null;
|
||||
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
|
||||
_queuedBroadphaseUpdates.Add((entity, xform));
|
||||
}
|
||||
|
||||
Array.Clear(_toApplySorted, 0, _toApply.Count);
|
||||
_prof.WriteValue("Count", ProfData.Int32(_toApply.Count));
|
||||
}
|
||||
|
||||
@@ -936,166 +958,14 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any entities that failed to properly initialize/start
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entities.DeleteEntity(entity);
|
||||
}
|
||||
|
||||
_brokenEnts.Clear();
|
||||
// Initialize and start the newly created entities.
|
||||
if (_toCreate.Count > 0)
|
||||
InitializeAndStart(_toCreate, metas, xforms);
|
||||
|
||||
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
|
||||
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
|
||||
}
|
||||
|
||||
private void ApplyEntState(in StateData data, GameTick toTick)
|
||||
{
|
||||
try
|
||||
{
|
||||
HandleEntityState(data, _entities.EventBus, toTick);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(data.Uid)}. Exception: {e}");
|
||||
_brokenEnts.Add(data.Uid);
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.Created)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entities.InitializeEntity(data.Uid, data.Meta);
|
||||
_entities.StartEntity(data.Uid);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error(
|
||||
$"Caught exception while initializing or starting entity: {_entities.ToPrettyString(data.Uid)}. Exception: {e}");
|
||||
_brokenEnts.Add(data.Uid);
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.EnteringPvs)
|
||||
return;
|
||||
|
||||
// Now that things like collision data, fixtures, and positions have been updated, we queue a
|
||||
// broadphase update. However, if this entity is parented to some other entity also re-entering PVS,
|
||||
// we only need to update it's parent (as it recursively updates children anyways).
|
||||
var xform = _entities.TransformQuery.Comp(data.Uid);
|
||||
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
|
||||
xform.Broadphase = null;
|
||||
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
|
||||
_queuedBroadphaseUpdates.Add((data.Uid, xform));
|
||||
}
|
||||
|
||||
private void CreateNewEntity(EntityState state, GameTick toTick)
|
||||
{
|
||||
// TODO GAME STATE
|
||||
// store MetaData & Transform information separately.
|
||||
var metaState =
|
||||
(MetaDataComponentState?) state.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId)
|
||||
.State;
|
||||
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(state.NetEntity);
|
||||
|
||||
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
|
||||
_toApply.Add(uid, new(uid, state.NetEntity, newMeta, true, false, GameTick.Zero, state, null, null));
|
||||
_created.Add(state.NetEntity);
|
||||
|
||||
// Client creates a client-side net entity for the newly created entity.
|
||||
// We need to clear this mapping before assigning the real net id.
|
||||
// TODO NetEntity Jank: prevent the client from creating this in the first place.
|
||||
_entities.ClearNetEntity(newMeta.NetEntity);
|
||||
|
||||
_entities.SetNetEntity(uid, state.NetEntity, newMeta);
|
||||
newMeta.LastStateApplied = toTick;
|
||||
|
||||
// Check if there's any component states awaiting this entity.
|
||||
if (!_entities.PendingNetEntityStates.Remove(state.NetEntity, out var value))
|
||||
return;
|
||||
|
||||
foreach (var (type, owner) in value)
|
||||
{
|
||||
var pending = _pendingReapplyNetStates.GetOrNew(owner);
|
||||
pending.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort states to ensure that we always apply states, initialize, and start parent entities before any of their
|
||||
/// children.
|
||||
/// </summary>
|
||||
private void SortStates(Dictionary<EntityUid, StateData> toApply)
|
||||
{
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (_toApplySorted == null || _toApplySorted.Length < toApply.Count)
|
||||
Array.Resize(ref _toApplySorted, toApply.Count);
|
||||
|
||||
_sorted.Clear();
|
||||
|
||||
var i = 0;
|
||||
foreach (var (ent, data) in toApply)
|
||||
{
|
||||
AddToSorted(ent, data, ref i);
|
||||
}
|
||||
|
||||
DebugTools.AssertEqual(i, toApply.Count);
|
||||
}
|
||||
|
||||
private void AddToSorted(EntityUid ent, in StateData data, ref int i)
|
||||
{
|
||||
if (!_sorted.Add(ent))
|
||||
return;
|
||||
|
||||
EnsureParentsSorted(ent, data, ref i);
|
||||
_toApplySorted[i++] = data;
|
||||
}
|
||||
|
||||
private void EnsureParentsSorted(EntityUid ent, in StateData data, ref int i)
|
||||
{
|
||||
var parent = GetStateParent(ent, data);
|
||||
|
||||
while (parent != EntityUid.Invalid)
|
||||
{
|
||||
if (_toApply.TryGetValue(parent, out var parentData))
|
||||
{
|
||||
AddToSorted(parent, parentData, ref i);
|
||||
// The above method will handle the rest of the transform hierarchy, so we can just return early.
|
||||
return;
|
||||
}
|
||||
|
||||
parent = _entities.TransformQuery.GetComponent(parent).ParentUid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the entity's parent in the game state that is being applies. I.e., if the state contains a new
|
||||
/// transform state, get the parent from that. Otherwise, return the entity's current parent.
|
||||
/// </summary>
|
||||
private EntityUid GetStateParent(EntityUid uid, in StateData data)
|
||||
{
|
||||
// TODO GAME STATE
|
||||
// store MetaData & Transform information separately.
|
||||
if (data.CurState != null
|
||||
&& data.CurState.ComponentChanges.Value
|
||||
.TryFirstOrNull(c => c.NetID == _xformCompNetId, out var found))
|
||||
{
|
||||
var state = (TransformComponentState) found.Value.State!;
|
||||
return _entities.GetEntity(state.ParentID);
|
||||
}
|
||||
|
||||
return _entities.TransformQuery.GetComponent(uid).ParentUid;
|
||||
return (_toCreate.Keys, detached);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1130,7 +1000,7 @@ namespace Robust.Client.GameStates
|
||||
_toDelete.Clear();
|
||||
|
||||
// Client side entities won't need the transform, but that should always be a tiny minority of entities
|
||||
var metaQuery = _entities.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
|
||||
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
|
||||
|
||||
while (metaQuery.MoveNext(out var ent, out var metadata, out var xform))
|
||||
{
|
||||
@@ -1197,9 +1067,9 @@ namespace Robust.Client.GameStates
|
||||
foreach (var netEntity in delSpan)
|
||||
{
|
||||
// Don't worry about this for later.
|
||||
_entities.PendingNetEntityStates.Remove(netEntity);
|
||||
_entityManager.PendingNetEntityStates.Remove(netEntity);
|
||||
|
||||
if (!_entities.TryGetEntity(netEntity, out var id))
|
||||
if (!_entityManager.TryGetEntity(netEntity, out var id))
|
||||
continue;
|
||||
|
||||
if (!xforms.TryGetComponent(id, out var xform))
|
||||
@@ -1229,10 +1099,9 @@ namespace Robust.Client.GameStates
|
||||
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
|
||||
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
|
||||
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
_detached.Clear();
|
||||
}
|
||||
|
||||
private void ProcessPvsDeparture(
|
||||
private List<NetEntity> ProcessPvsDeparture(
|
||||
GameTick toTick,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
@@ -1241,23 +1110,25 @@ namespace Robust.Client.GameStates
|
||||
EntityLookupSystem lookupSys)
|
||||
{
|
||||
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
|
||||
var detached = new List<NetEntity>();
|
||||
|
||||
if (toDetach.Count == 0)
|
||||
return;
|
||||
return detached;
|
||||
|
||||
// TODO optimize
|
||||
// If an entity is leaving PVS, so are all of its children. If we can preserve the hierarchy we can avoid
|
||||
// things like container insertion and ejection.
|
||||
|
||||
using var _ = _prof.Group("Leave PVS");
|
||||
detached.EnsureCapacity(toDetach.Count);
|
||||
|
||||
_detached.Clear();
|
||||
foreach (var (tick, ents) in toDetach)
|
||||
{
|
||||
Detach(tick, toTick, ents, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
Detach(tick, toTick, ents, metas, xforms, xformSys, containerSys, lookupSys, detached);
|
||||
}
|
||||
|
||||
_prof.WriteValue("Count", ProfData.Int32(_detached.Count));
|
||||
_prof.WriteValue("Count", ProfData.Int32(detached.Count));
|
||||
return detached;
|
||||
}
|
||||
|
||||
private void Detach(GameTick maxTick,
|
||||
@@ -1267,11 +1138,12 @@ namespace Robust.Client.GameStates
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
SharedTransformSystem xformSys,
|
||||
ContainerSystem containerSys,
|
||||
EntityLookupSystem lookupSys)
|
||||
EntityLookupSystem lookupSys,
|
||||
List<NetEntity>? detached = null)
|
||||
{
|
||||
foreach (var netEntity in entities)
|
||||
{
|
||||
if (!_entities.TryGetEntityData(netEntity, out var ent, out var meta))
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out var ent, out var meta))
|
||||
continue;
|
||||
|
||||
if (meta.LastStateApplied > maxTick)
|
||||
@@ -1312,75 +1184,159 @@ namespace Robust.Client.GameStates
|
||||
containerSys.AddExpectedEntity(netEntity, container);
|
||||
}
|
||||
|
||||
_detached.Add(netEntity);
|
||||
detached?.Add(netEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEntityState(in StateData data, IEventBus bus, GameTick toTick)
|
||||
private void InitializeAndStart(
|
||||
Dictionary<NetEntity, EntityState> toCreate,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
_toStart.Clear();
|
||||
|
||||
using (_prof.Group("Initialize Entity"))
|
||||
{
|
||||
EntityUid entity = default;
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
{
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
InitializeRecursive(entity, meta, metas, xforms);
|
||||
}
|
||||
}
|
||||
|
||||
using (_prof.Group("Start Entity"))
|
||||
{
|
||||
foreach (var (entity, netEntity) in _toStart)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entities.StartEntity(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void InitializeRecursive(
|
||||
EntityUid entity,
|
||||
MetaDataComponent meta,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
var xform = xforms.GetComponent(entity);
|
||||
if (xform.ParentUid is {Valid: true} parent)
|
||||
{
|
||||
var parentMeta = metas.GetComponent(parent);
|
||||
if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized)
|
||||
InitializeRecursive(parent, parentMeta, metas, xforms);
|
||||
}
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Initialized)
|
||||
{
|
||||
// Was probably already initialized because one of its children appeared earlier in the list.
|
||||
DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, meta.NetEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(meta.NetEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
|
||||
{
|
||||
_compStateWork.Clear();
|
||||
|
||||
// First remove any deleted components
|
||||
if (data.CurState?.NetComponents is {} netComps)
|
||||
if (curState?.NetComponents != null)
|
||||
{
|
||||
_toRemove.Clear();
|
||||
|
||||
foreach (var (id, comp) in data.Meta.NetComponents)
|
||||
foreach (var (id, comp) in meta.NetComponents)
|
||||
{
|
||||
DebugTools.Assert(comp.NetSyncEnabled);
|
||||
|
||||
if (!netComps.Contains(id))
|
||||
if (!curState.NetComponents.Contains(id))
|
||||
_toRemove.Add(comp);
|
||||
}
|
||||
|
||||
foreach (var comp in _toRemove)
|
||||
{
|
||||
_entities.RemoveComponent(data.Uid, comp, data.Meta);
|
||||
_entities.RemoveComponent(uid, comp, meta);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.EnteringPvs)
|
||||
if (enteringPvs)
|
||||
{
|
||||
// last-server state has already been updated with new information from curState
|
||||
// --> simply reset to the most recent server state.
|
||||
//
|
||||
// as to why we need to reset: because in the process of detaching to null-space, we will have dirtied
|
||||
// the entity. most notably, all entities will have been ejected from their containers.
|
||||
foreach (var (id, state) in _processor.GetLastServerStates(data.NetEntity))
|
||||
foreach (var (id, state) in _processor.GetLastServerStates(netEntity))
|
||||
{
|
||||
if (!data.Meta.NetComponents.TryGetValue(id, out var comp))
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
_entities.AddComponent(data.Uid, comp, true, metadata: data.Meta);
|
||||
_entityManager.AddComponent(uid, comp, true, metadata: meta);
|
||||
}
|
||||
|
||||
_compStateWork[id] = (comp, state, null);
|
||||
}
|
||||
}
|
||||
else if (data.CurState != null)
|
||||
else if (curState != null)
|
||||
{
|
||||
foreach (var compChange in data.CurState.ComponentChanges.Span)
|
||||
foreach (var compChange in curState.ComponentChanges.Span)
|
||||
{
|
||||
if (!data.Meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
|
||||
if (!meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(compChange.NetID);
|
||||
_entities.AddComponent(data.Uid, comp, true, metadata: data.Meta);
|
||||
_entityManager.AddComponent(uid, comp, true, metadata:meta);
|
||||
}
|
||||
else if (compChange.LastModifiedTick <= data.LastApplied && data.LastApplied != GameTick.Zero)
|
||||
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
|
||||
continue;
|
||||
|
||||
_compStateWork[compChange.NetID] = (comp, compChange.State, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.NextState != null)
|
||||
if (nextState != null)
|
||||
{
|
||||
foreach (var compState in data.NextState.ComponentChanges.Span)
|
||||
foreach (var compState in nextState.ComponentChanges.Span)
|
||||
{
|
||||
if (compState.LastModifiedTick != toTick + 1)
|
||||
continue;
|
||||
|
||||
if (!data.Meta.NetComponents.TryGetValue(compState.NetID, out var comp))
|
||||
if (!meta.NetComponents.TryGetValue(compState.NetID, out var comp))
|
||||
{
|
||||
// The component can be null here due to interp, because the NEXT state will have a new
|
||||
// component, but the component does not yet exist.
|
||||
@@ -1398,10 +1354,9 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// If we have a NetEntity we reference come in then apply their state.
|
||||
DebugTools.Assert(_pendingReapplyNetStates.ContainsKey(data.Uid) == (data.PendingReapply != null));
|
||||
if (data.PendingReapply is {} reapplyTypes)
|
||||
if (_pendingReapplyNetStates.TryGetValue(uid, out var reapplyTypes))
|
||||
{
|
||||
var lastState = _processor.GetLastServerStates(data.NetEntity);
|
||||
var lastState = _processor.GetLastServerStates(netEntity);
|
||||
|
||||
foreach (var type in reapplyTypes)
|
||||
{
|
||||
@@ -1411,7 +1366,7 @@ namespace Robust.Client.GameStates
|
||||
if (netId == null)
|
||||
continue;
|
||||
|
||||
if (!data.Meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
if (!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
!lastState.TryGetValue(netId.Value, out var lastCompState))
|
||||
{
|
||||
continue;
|
||||
@@ -1433,7 +1388,7 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(data.Uid, comp, ref handleState);
|
||||
bus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1555,7 +1510,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
|
||||
var query = _entities.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var meta))
|
||||
{
|
||||
@@ -1581,14 +1536,14 @@ namespace Robust.Client.GameStates
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
_entities.AddComponent(uid, comp, true, meta);
|
||||
_entityManager.AddComponent(uid, comp, true, meta);
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
_entityManager.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
}
|
||||
|
||||
// ensure we don't have any extra components
|
||||
|
||||
@@ -82,7 +82,11 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
|
||||
|
||||
void MergeImplicitData();
|
||||
/// <summary>
|
||||
/// Generates implicit component states for newly created entities.
|
||||
/// This should always be called after running <see cref="ApplyGameState(GameState, GameState)"/>.
|
||||
/// </summary>
|
||||
void GenerateImplicitStates(IEnumerable<NetEntity> states);
|
||||
|
||||
/// <summary>
|
||||
/// Resets any entities that have changed while predicting future ticks.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Client.GameStates;
|
||||
|
||||
public sealed partial class PvsOverrideSystem : SharedPvsOverrideSystem;
|
||||
@@ -125,8 +125,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
|
||||
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldBox, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle.DrawingHandleWorld, new UIBox2i((0, 0), vp.Size), vp.Eye!.Position.MapId, worldBox, worldBounds);
|
||||
|
||||
if (!overlay.BeforeDraw(args))
|
||||
return;
|
||||
@@ -166,7 +165,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void RenderOverlaysDirect(
|
||||
Viewport vp,
|
||||
IViewportControl vpControl,
|
||||
IRenderHandle handle,
|
||||
DrawingHandleBase handle,
|
||||
OverlaySpace space,
|
||||
in UIBox2i bounds)
|
||||
{
|
||||
@@ -176,9 +175,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var worldBounds = CalcWorldBounds(vp);
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldAABB, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, vp.Eye!.Position.MapId, worldAABB, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
@@ -423,19 +421,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var oldTransform = _currentMatrixModel;
|
||||
var oldScissor = _currentScissorState;
|
||||
var oldMatrixProj = _currentMatrixProj;
|
||||
var oldMatrixView = _currentMatrixView;
|
||||
var oldBoundTarget = _currentBoundRenderTarget;
|
||||
var oldRenderTarget = _currentRenderTarget;
|
||||
var oldShader = _queuedShaderInstance;
|
||||
var oldCaps = _glCaps;
|
||||
|
||||
// Need to get state before flushing render queue in case they modify the original state.
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
// Have to flush the render queue so that all commands finish rendering to the previous framebuffer.
|
||||
FlushRenderQueue();
|
||||
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
{
|
||||
BindRenderTargetFull(RtToLoaded(rt));
|
||||
if (clearColor is not null)
|
||||
@@ -457,16 +448,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
PopRenderStateFull(state);
|
||||
_updateUniformConstants(_currentRenderTarget.Size);
|
||||
|
||||
SetScissorFull(oldScissor);
|
||||
_currentMatrixModel = oldTransform;
|
||||
|
||||
DebugTools.Assert(oldCaps.Equals(_glCaps));
|
||||
DebugTools.Assert(_currentMatrixModel.Equals(oldTransform));
|
||||
DebugTools.Assert(_currentScissorState.Equals(oldScissor));
|
||||
DebugTools.Assert(_currentMatrixProj.Equals(oldMatrixProj));
|
||||
DebugTools.Assert(oldMatrixView.Equals(_currentMatrixView));
|
||||
DebugTools.Assert(oldRenderTarget.Equals(_currentRenderTarget));
|
||||
DebugTools.Assert(oldBoundTarget.Equals(_currentBoundRenderTarget));
|
||||
DebugTools.Assert(oldShader.Equals(_queuedShaderInstance));
|
||||
}
|
||||
|
||||
private void RenderViewport(Viewport viewport)
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
|
||||
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Client.ComponentTrees;
|
||||
using Robust.Shared.Graphics;
|
||||
using static Robust.Shared.GameObjects.OccluderComponent;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -277,7 +279,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
const float arbitraryDistanceMax = 1234;
|
||||
|
||||
IsBlending = false;
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
CheckGlError();
|
||||
@@ -326,7 +329,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
CheckGlError();
|
||||
|
||||
IsBlending = true;
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
|
||||
@@ -390,43 +394,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FinalizeDepthDraw();
|
||||
}
|
||||
|
||||
IsStencilling = true;
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
_isStencilling = true;
|
||||
|
||||
var (lightW, lightH) = GetLightMapSize(viewport.Size);
|
||||
GL.Viewport(0, 0, lightW, lightH);
|
||||
CheckGlError();
|
||||
|
||||
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
|
||||
DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId));
|
||||
CheckGlError();
|
||||
|
||||
var clearEv = new GetClearColorEvent();
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, ref clearEv);
|
||||
|
||||
var clearColor = clearEv.Color ?? GetClearColor(mapUid);
|
||||
GLClearColor(clearColor);
|
||||
GLClearColor(_entityManager.GetComponentOrNull<MapLightComponent>(mapUid)?.AmbientLightColor ?? MapLightComponent.DefaultColor);
|
||||
GL.ClearStencil(0xFF);
|
||||
GL.StencilMask(0xFF);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
|
||||
CheckGlError();
|
||||
|
||||
var oldTarget = _currentRenderTarget;
|
||||
var oldProj = _currentMatrixProj;
|
||||
var oldShader = _queuedShaderInstance;
|
||||
var oldModel = _currentMatrixModel;
|
||||
var oldScissor = _currentScissorState;
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.BeforeLighting, worldAABB, worldBounds);
|
||||
PopRenderStateFull(state);
|
||||
|
||||
DebugTools.Assert(oldScissor.Equals(_currentScissorState));
|
||||
DebugTools.Assert(oldModel.Equals(_currentMatrixModel));
|
||||
DebugTools.Assert(oldShader.Equals(_queuedShaderInstance));
|
||||
DebugTools.Assert(oldProj.Equals(_currentMatrixProj));
|
||||
DebugTools.Assert(oldTarget.Equals(_currentRenderTarget));
|
||||
DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId));
|
||||
|
||||
ApplyLightingFovToBuffer(viewport, eye);
|
||||
|
||||
var lightShader = _loadedShaders[_enableSoftShadows ? _lightSoftShaderHandle : _lightHardShaderHandle]
|
||||
@@ -527,12 +509,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
ResetBlendFunc();
|
||||
IsStencilling = false;
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
_isStencilling = false;
|
||||
|
||||
CheckGlError();
|
||||
|
||||
if (_cfg.GetCVar(CVars.LightBlur))
|
||||
BlurRenderTarget(viewport, viewport.LightRenderTarget, viewport.LightBlurTarget, eye, 14f);
|
||||
BlurLights(viewport, eye);
|
||||
|
||||
using (_prof.Group("BlurOntoWalls"))
|
||||
{
|
||||
@@ -548,8 +531,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.Viewport(0, 0, viewport.Size.X, viewport.Size.Y);
|
||||
CheckGlError();
|
||||
|
||||
_lightingReady = true;
|
||||
Array.Clear(_lightsToRenderList, 0, count);
|
||||
|
||||
_lightingReady = true;
|
||||
}
|
||||
|
||||
private static bool LightQuery(ref (
|
||||
@@ -659,33 +643,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return (state.count, expandedBounds);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public Color GetClearColor(EntityUid mapUid)
|
||||
private void BlurLights(Viewport viewport, IEye eye)
|
||||
{
|
||||
return _entityManager.GetComponentOrNull<MapLightComponent>(mapUid)?.AmbientLightColor ??
|
||||
MapLightComponent.DefaultColor;
|
||||
}
|
||||
using var _ = DebugGroup(nameof(BlurLights));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier)
|
||||
{
|
||||
if (target is not RenderTexture rTexture || blurBuffer is not RenderTexture blurTexture)
|
||||
return;
|
||||
|
||||
using var _ = DebugGroup(nameof(BlurRenderTarget));
|
||||
|
||||
var state = PushRenderStateFull();
|
||||
IsBlending = false;
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
CalcScreenMatrices(viewport.Size, out var proj, out var view);
|
||||
SetProjViewBuffer(proj, view);
|
||||
|
||||
var shader = _loadedShaders[_lightBlurShaderHandle].Program;
|
||||
shader.Use();
|
||||
|
||||
SetupGlobalUniformsImmediate(shader, rTexture.Texture);
|
||||
SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
|
||||
|
||||
var size = target.Size;
|
||||
var size = viewport.LightRenderTarget.Size;
|
||||
shader.SetUniformMaybe("size", (Vector2)size);
|
||||
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
|
||||
@@ -695,13 +667,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Initially we're pulling from the light render target.
|
||||
// So we set it out of the loop so
|
||||
// _wallBleedIntermediateRenderTarget2 gets bound at the end of the loop body.
|
||||
SetTexture(TextureUnit.Texture0, rTexture.Texture);
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
|
||||
|
||||
// Have to scale the blurring radius based on viewport size and camera zoom.
|
||||
const float refCameraHeight = 14;
|
||||
var facBase = _cfg.GetCVar(CVars.LightBlurFactor);
|
||||
var cameraSize = eye.Zoom.Y * viewport.Size.Y * (1 / viewport.RenderScale.Y) / EyeManager.PixelsPerMeter;
|
||||
// 7e-3f is just a magic factor that makes it look ok.
|
||||
var factor = facBase * (multiplier / cameraSize);
|
||||
var factor = facBase * (refCameraHeight / cameraSize);
|
||||
|
||||
// Multi-iteration gaussian blur.
|
||||
for (var i = 3; i > 0; i--)
|
||||
@@ -710,31 +683,35 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Set factor.
|
||||
shader.SetUniformMaybe("radius", scale);
|
||||
|
||||
BindRenderTargetImmediate(RtToLoaded(blurBuffer));
|
||||
BindRenderTargetFull(viewport.LightBlurTarget);
|
||||
|
||||
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitX);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, blurTexture.Texture);
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture);
|
||||
|
||||
BindRenderTargetImmediate(RtToLoaded(rTexture));
|
||||
BindRenderTargetFull(viewport.LightRenderTarget);
|
||||
|
||||
// Blur vertically to _wallBleedIntermediateRenderTarget2.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitY);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, rTexture.Texture);
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
|
||||
}
|
||||
|
||||
PopRenderStateFull(state);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
// We didn't trample over the old _currentMatrices so just roll it back.
|
||||
SetProjViewBuffer(_currentMatrixProj, _currentMatrixView);
|
||||
}
|
||||
|
||||
private void BlurOntoWalls(Viewport viewport, IEye eye)
|
||||
{
|
||||
using var _ = DebugGroup(nameof(BlurOntoWalls));
|
||||
|
||||
IsBlending = false;
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
CalcScreenMatrices(viewport.Size, out var proj, out var view);
|
||||
SetProjViewBuffer(proj, view);
|
||||
|
||||
@@ -784,7 +761,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget2.Texture);
|
||||
}
|
||||
|
||||
IsBlending = true;
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
// We didn't trample over the old _currentMatrices so just roll it back.
|
||||
SetProjViewBuffer(_currentMatrixProj, _currentMatrixView);
|
||||
}
|
||||
@@ -797,7 +775,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
GL.Viewport(0, 0, viewport.LightRenderTarget.Size.X, viewport.LightRenderTarget.Size.Y);
|
||||
CheckGlError();
|
||||
IsBlending = false;
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
|
||||
var shader = _loadedShaders[_mergeWallLayerShaderHandle].Program;
|
||||
shader.Use();
|
||||
@@ -817,7 +796,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
IntPtr.Zero);
|
||||
CheckGlError();
|
||||
|
||||
IsBlending = true;
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
private void ApplyFovToBuffer(Viewport viewport, IEye eye)
|
||||
@@ -847,7 +827,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FovSetTransformAndBlit(viewport, eye.Position.Position, fovShader);
|
||||
|
||||
GL.StencilMask(0x00);
|
||||
IsStencilling = false;
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
_isStencilling = false;
|
||||
}
|
||||
|
||||
private void ApplyLightingFovToBuffer(Viewport viewport, IEye eye)
|
||||
@@ -1154,20 +1135,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var lightMapSize = GetLightMapSize(viewport.Size);
|
||||
var lightMapSizeQuart = GetLightMapSize(viewport.Size, true);
|
||||
|
||||
viewport.LightRenderTarget?.Dispose();
|
||||
viewport.WallMaskRenderTarget?.Dispose();
|
||||
viewport.WallBleedIntermediateRenderTarget1?.Dispose();
|
||||
viewport.WallBleedIntermediateRenderTarget2?.Dispose();
|
||||
var lightMapColorFormat = _hasGLFloatFramebuffers
|
||||
? RenderTargetColorFormat.R11FG11FB10F
|
||||
: RenderTargetColorFormat.Rgba8;
|
||||
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
|
||||
|
||||
viewport.LightRenderTarget?.Dispose();
|
||||
viewport.WallMaskRenderTarget?.Dispose();
|
||||
viewport.WallBleedIntermediateRenderTarget1?.Dispose();
|
||||
viewport.WallBleedIntermediateRenderTarget2?.Dispose();
|
||||
|
||||
viewport.WallMaskRenderTarget = CreateRenderTarget(viewport.Size, RenderTargetColorFormat.R8,
|
||||
name: $"{viewport.Name}-{nameof(viewport.WallMaskRenderTarget)}");
|
||||
|
||||
viewport.LightRenderTarget = (RenderTexture) CreateLightRenderTarget(lightMapSize,
|
||||
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: true),
|
||||
lightMapSampleParameters,
|
||||
$"{viewport.Name}-{nameof(viewport.LightRenderTarget)}");
|
||||
|
||||
viewport.LightBlurTarget = CreateRenderTarget(lightMapSize,
|
||||
@@ -1175,13 +1158,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
lightMapSampleParameters,
|
||||
$"{viewport.Name}-{nameof(viewport.LightBlurTarget)}");
|
||||
|
||||
viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat),
|
||||
viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat,
|
||||
lightMapSampleParameters,
|
||||
$"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget1)}");
|
||||
|
||||
viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat),
|
||||
viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat,
|
||||
lightMapSampleParameters,
|
||||
$"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget2)}");
|
||||
}
|
||||
|
||||
@@ -30,20 +30,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// It, like _mainWindowRenderTarget, is initialized in Clyde's constructor
|
||||
private LoadedRenderTarget _currentBoundRenderTarget;
|
||||
|
||||
|
||||
public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true)
|
||||
{
|
||||
var lightMapColorFormat = _hasGLFloatFramebuffers
|
||||
? RTCF.R11FG11FB10F
|
||||
: RTCF.Rgba8;
|
||||
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
|
||||
|
||||
return CreateRenderTarget(size,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: depthStencil),
|
||||
lightMapSampleParameters,
|
||||
name: name);
|
||||
}
|
||||
|
||||
IRenderTexture IClyde.CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters, string? name)
|
||||
{
|
||||
@@ -218,8 +204,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Size = size,
|
||||
TextureHandle = textureObject.TextureId,
|
||||
MemoryPressure = pressure,
|
||||
ColorFormat = format.ColorFormat,
|
||||
SampleParameters = sampleParameters,
|
||||
ColorFormat = format.ColorFormat
|
||||
};
|
||||
|
||||
//GC.AddMemoryPressure(pressure);
|
||||
@@ -266,15 +251,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LoadedRenderTarget RtToLoaded(IRenderTarget rt)
|
||||
private LoadedRenderTarget RtToLoaded(RenderTargetBase rt)
|
||||
{
|
||||
switch (rt)
|
||||
{
|
||||
case RenderTargetBase based:
|
||||
return _renderTargets[based.Handle];
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
return _renderTargets[rt.Handle];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -323,8 +302,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Renderbuffer handle
|
||||
public GLHandle DepthStencilHandle;
|
||||
public long MemoryPressure;
|
||||
|
||||
public TextureSampleParameters? SampleParameters;
|
||||
}
|
||||
|
||||
private abstract class RenderTargetBase : IRenderTarget
|
||||
|
||||
@@ -90,61 +90,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// (queue) and (misc), current state of the scissor test. Null if disabled.
|
||||
private UIBox2i? _currentScissorState;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks enabled GL capabilities for renderer state.
|
||||
/// </summary>
|
||||
private GLCaps _glCaps = GLCaps.None;
|
||||
|
||||
private bool IsStencilling
|
||||
{
|
||||
get => (_glCaps & GLCaps.Stencilling) == GLCaps.Stencilling;
|
||||
set
|
||||
{
|
||||
if (value == IsStencilling)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
_glCaps |= GLCaps.Stencilling;
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glCaps &= ~GLCaps.Stencilling;
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
}
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsBlending
|
||||
{
|
||||
get => (_glCaps & GLCaps.Blending) == GLCaps.Blending;
|
||||
set
|
||||
{
|
||||
if (value == IsBlending)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
_glCaps |= GLCaps.Blending;
|
||||
GL.Enable(EnableCap.Blend);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glCaps &= ~GLCaps.Blending;
|
||||
GL.Disable(EnableCap.Blend);
|
||||
}
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsScissoring
|
||||
{
|
||||
get => _currentScissorState != null;
|
||||
}
|
||||
// Some simple flags that basically just tracks the current state of glEnable(GL_STENCIL/GL_SCISSOR_TEST)
|
||||
private bool _isScissoring;
|
||||
private bool _isStencilling;
|
||||
|
||||
private readonly RefList<RenderCommand> _queuedRenderCommands = new RefList<RenderCommand>();
|
||||
|
||||
@@ -416,17 +364,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void SetScissorImmediate(bool enable, in UIBox2i box)
|
||||
{
|
||||
if (enable)
|
||||
var oldIsScissoring = _isScissoring;
|
||||
_isScissoring = enable;
|
||||
if (_isScissoring)
|
||||
{
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
}
|
||||
if (!oldIsScissoring)
|
||||
{
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
if (enable)
|
||||
{
|
||||
// Don't forget to flip it, these coordinates have bottom left as origin.
|
||||
// TODO: Broken when rendering to non-screen render targets.
|
||||
|
||||
@@ -440,6 +387,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
CheckGlError();
|
||||
}
|
||||
else if (oldIsScissoring)
|
||||
{
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: sRGB IS IN LINEAR IF FRAMEBUFFER_SRGB IS ACTIVE.
|
||||
@@ -468,11 +420,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var program = shader.Program;
|
||||
|
||||
program.Use();
|
||||
IsStencilling = instance.Stencil.Enabled;
|
||||
|
||||
// Handle stencil parameters.
|
||||
if (instance.Stencil.Enabled)
|
||||
{
|
||||
if (!_isStencilling)
|
||||
{
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
CheckGlError();
|
||||
_isStencilling = true;
|
||||
}
|
||||
|
||||
GL.StencilMask(instance.Stencil.WriteMask);
|
||||
CheckGlError();
|
||||
GL.StencilFunc(ToGLStencilFunc(instance.Stencil.Func), instance.Stencil.Ref, instance.Stencil.ReadMask);
|
||||
@@ -480,6 +438,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, ToGLStencilOp(instance.Stencil.Op));
|
||||
CheckGlError();
|
||||
}
|
||||
else if (_isStencilling)
|
||||
{
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
CheckGlError();
|
||||
_isStencilling = false;
|
||||
}
|
||||
|
||||
if (instance.Parameters.Count == 0)
|
||||
return (program, instance);
|
||||
@@ -895,34 +859,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private FullStoredRendererState PushRenderStateFull()
|
||||
{
|
||||
return new FullStoredRendererState(
|
||||
_currentMatrixProj,
|
||||
_currentMatrixView,
|
||||
_currentBoundRenderTarget,
|
||||
_currentRenderTarget,
|
||||
_queuedShaderInstance,
|
||||
_currentScissorState,
|
||||
_glCaps);
|
||||
return new FullStoredRendererState(_currentMatrixProj, _currentMatrixView, _currentRenderTarget);
|
||||
}
|
||||
|
||||
private void PopRenderStateFull(in FullStoredRendererState state)
|
||||
{
|
||||
SetProjViewFull(state.ProjMatrix, state.ViewMatrix);
|
||||
BindRenderTargetImmediate(state.BoundRenderTarget);
|
||||
BindRenderTargetFull(state.RenderTarget);
|
||||
|
||||
_queuedShaderInstance = state.QueuedShaderInstance;
|
||||
_currentRenderTarget = state.RenderTarget;
|
||||
var (width, height) = state.BoundRenderTarget.Size;
|
||||
var (width, height) = state.RenderTarget.Size;
|
||||
GL.Viewport(0, 0, width, height);
|
||||
|
||||
IsStencilling = (state.GLCaps & GLCaps.Stencilling) == GLCaps.Stencilling;
|
||||
IsBlending = (state.GLCaps & GLCaps.Blending) == GLCaps.Blending;
|
||||
|
||||
SetScissorFull(state.ScissorState);
|
||||
|
||||
GL.ClearStencil(0xFF);
|
||||
GL.StencilMask(0xFF);
|
||||
GL.Clear(ClearBufferMask.StencilBufferBit);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
private void SetViewportImmediate(Box2i box)
|
||||
@@ -1114,44 +1061,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public readonly Matrix3x2 ProjMatrix;
|
||||
public readonly Matrix3x2 ViewMatrix;
|
||||
public readonly LoadedRenderTarget BoundRenderTarget;
|
||||
public readonly LoadedRenderTarget RenderTarget;
|
||||
public readonly ClydeShaderInstance QueuedShaderInstance;
|
||||
|
||||
public readonly UIBox2i? ScissorState;
|
||||
|
||||
public readonly GLCaps GLCaps;
|
||||
|
||||
public FullStoredRendererState(
|
||||
in Matrix3x2 projMatrix,
|
||||
in Matrix3x2 viewMatrix,
|
||||
LoadedRenderTarget boundRenderTarget,
|
||||
LoadedRenderTarget renderTarget,
|
||||
ClydeShaderInstance queuedShaderInstance,
|
||||
UIBox2i? scissorState,
|
||||
GLCaps glcaps
|
||||
)
|
||||
public FullStoredRendererState(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix,
|
||||
LoadedRenderTarget renderTarget)
|
||||
{
|
||||
ProjMatrix = projMatrix;
|
||||
ViewMatrix = viewMatrix;
|
||||
BoundRenderTarget = boundRenderTarget;
|
||||
RenderTarget = renderTarget;
|
||||
QueuedShaderInstance = queuedShaderInstance;
|
||||
|
||||
ScissorState = scissorState;
|
||||
GLCaps = glcaps;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum GLCaps : ushort
|
||||
{
|
||||
// If you add flags here make sure to update PopRenderState!
|
||||
None = 0,
|
||||
|
||||
Blending = 1 << 0,
|
||||
|
||||
Stencilling = 1 << 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysBelow(
|
||||
IRenderHandle handle,
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
@@ -179,7 +179,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysAbove(
|
||||
IRenderHandle handle,
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
|
||||
@@ -21,7 +21,6 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
|
||||
|
||||
@@ -246,7 +245,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
overrideVersion != null,
|
||||
_windowing!.GetDescription());
|
||||
|
||||
IsBlending = true;
|
||||
GL.Enable(EnableCap.Blend);
|
||||
if (_hasGLSrgb && !_isGLES)
|
||||
{
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
@@ -9,7 +9,6 @@ using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -190,22 +189,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new DummyTexture(size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Color GetClearColor(EntityUid mapUid)
|
||||
{
|
||||
return Color.Transparent;
|
||||
}
|
||||
|
||||
public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier)
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true)
|
||||
{
|
||||
return CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.R8, hasDepthStencil: depthStencil), null, name: name);
|
||||
}
|
||||
|
||||
public IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null)
|
||||
{
|
||||
@@ -514,7 +497,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysBelow(
|
||||
IRenderHandle handle,
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
@@ -522,7 +505,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysAbove(
|
||||
IRenderHandle handle,
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -74,24 +71,6 @@ namespace Robust.Client.Graphics
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clear color for the specified map viewport.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
Color GetClearColor(EntityUid mapUid);
|
||||
|
||||
/// <summary>
|
||||
/// Applies a blur to the specified render target. Requires a separate buffer with similar properties to draw intermediate steps into.
|
||||
/// </summary>
|
||||
/// <param name="viewport">The viewport being used for drawing.</param>
|
||||
/// <param name="target">The blur target.</param>
|
||||
/// <param name="blurBuffer">The separate buffer to draw into.</param>
|
||||
/// <param name="eye">The eye being drawn with.</param>
|
||||
/// <param name="multiplier">Scale of how much blur to blur by.</param>
|
||||
void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier);
|
||||
|
||||
IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true);
|
||||
|
||||
IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
IRenderTexture RenderTarget { get; }
|
||||
IRenderTexture LightRenderTarget { get; }
|
||||
|
||||
IEye? Eye { get; set; }
|
||||
Vector2i Size { get; }
|
||||
|
||||
@@ -67,7 +66,7 @@ namespace Robust.Client.Graphics
|
||||
/// Not relative to the current transform of <see cref="handle"/>.
|
||||
/// </param>
|
||||
public void RenderScreenOverlaysBelow(
|
||||
IRenderHandle handle,
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds);
|
||||
|
||||
@@ -81,7 +80,7 @@ namespace Robust.Client.Graphics
|
||||
/// Not relative to the current transform of <see cref="handle"/>.
|
||||
/// </param>
|
||||
public void RenderScreenOverlaysAbove(
|
||||
IRenderHandle handle,
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
@@ -17,42 +15,5 @@ namespace Robust.Client.Graphics
|
||||
Vector2i Size { get; }
|
||||
|
||||
void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel<T>;
|
||||
|
||||
public Vector2 LocalToWorld(IEye eye, Vector2 point, Vector2 scale)
|
||||
{
|
||||
var newPoint = point;
|
||||
|
||||
// (inlined version of UiProjMatrix^-1)
|
||||
newPoint -= Size / 2f;
|
||||
newPoint *= new Vector2(1, -1) / EyeManager.PixelsPerMeter;
|
||||
|
||||
// view matrix
|
||||
eye.GetViewMatrixInv(out var viewMatrixInv, scale);
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrixInv);
|
||||
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
public Vector2 WorldToLocal(Vector2 point, IEye eye, Vector2 scale)
|
||||
{
|
||||
var newPoint = point;
|
||||
|
||||
eye.GetViewMatrix(out var viewMatrix, scale);
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrix);
|
||||
|
||||
// (inlined version of UiProjMatrix)
|
||||
newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
newPoint += Size / 2f;
|
||||
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
public Matrix3x2 GetWorldToLocalMatrix(IEye eye, Vector2 scale)
|
||||
{
|
||||
eye.GetViewMatrix(out var viewMatrix, scale * new Vector2(EyeManager.PixelsPerMeter, -EyeManager.PixelsPerMeter));
|
||||
viewMatrix.M31 += Size.X / 2f;
|
||||
viewMatrix.M32 += Size.Y / 2f;
|
||||
return viewMatrix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Raised by the engine if content wishes to override the default clear color.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GetClearColorEvent
|
||||
{
|
||||
public Color? Color;
|
||||
}
|
||||
@@ -9,5 +9,6 @@ namespace Robust.Client.Graphics
|
||||
public bool DrawHardFov { get; set; } = true;
|
||||
public bool DrawLighting { get; set; } = true;
|
||||
public bool LockConsoleAccess { get; set; } = false;
|
||||
public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -40,8 +39,6 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public readonly UIBox2i ViewportBounds;
|
||||
|
||||
public readonly EntityUid MapUid;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="MapId"/> of the viewport's eye.
|
||||
/// </summary>
|
||||
@@ -57,32 +54,24 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public readonly Box2Rotated WorldBounds;
|
||||
|
||||
public readonly IRenderHandle RenderHandle;
|
||||
|
||||
public DrawingHandleScreen ScreenHandle => (DrawingHandleScreen) DrawingHandle;
|
||||
public DrawingHandleWorld WorldHandle => (DrawingHandleWorld) DrawingHandle;
|
||||
|
||||
internal OverlayDrawArgs(
|
||||
public OverlayDrawArgs(
|
||||
OverlaySpace space,
|
||||
IViewportControl? viewportControl,
|
||||
IClydeViewport viewport,
|
||||
IRenderHandle renderHandle,
|
||||
DrawingHandleBase drawingHandle,
|
||||
in UIBox2i viewportBounds,
|
||||
in EntityUid mapUid,
|
||||
in MapId mapId,
|
||||
in Box2 worldAabb,
|
||||
in Box2Rotated worldBounds)
|
||||
{
|
||||
DrawingHandle = space is OverlaySpace.ScreenSpace or OverlaySpace.ScreenSpaceBelowWorld
|
||||
? renderHandle.DrawingHandleScreen
|
||||
: renderHandle.DrawingHandleWorld;
|
||||
|
||||
Space = space;
|
||||
ViewportControl = viewportControl;
|
||||
Viewport = viewport;
|
||||
RenderHandle = renderHandle;
|
||||
DrawingHandle = drawingHandle;
|
||||
ViewportBounds = viewportBounds;
|
||||
MapUid = mapUid;
|
||||
MapId = mapId;
|
||||
WorldAABB = worldAabb;
|
||||
WorldBounds = worldBounds;
|
||||
|
||||
@@ -93,7 +93,7 @@ public sealed partial class PhysicsSystem
|
||||
var maps = new HashSet<EntityUid>();
|
||||
|
||||
var enumerator = AllEntityQuery<PredictedPhysicsComponent, PhysicsComponent, TransformComponent>();
|
||||
while (enumerator.MoveNext(out _, out var physics, out var xform))
|
||||
while (enumerator.MoveNext(out var _, out var physics, out var xform))
|
||||
{
|
||||
DebugTools.Assert(physics.Predict);
|
||||
|
||||
|
||||
@@ -352,9 +352,6 @@ public sealed partial class ReplayLoadManager
|
||||
// prototype changes when jumping around in time. This also requires reworking how the initial
|
||||
// implicit state data is generated, because we can't simply cache it anymore.
|
||||
// Also, does reloading prototypes in release mode modify existing entities?
|
||||
// Yes, yes it does. See PrototypeReloadSystem.UpdateEntity()
|
||||
// Its just not supported ATM.
|
||||
// TBH it'd be easier if overriding existing prototypes in release mode was just forbidden.
|
||||
|
||||
var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
|
||||
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
@@ -364,6 +361,7 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
}
|
||||
|
||||
_protoMan.ResolveResults();
|
||||
_protoMan.ReloadPrototypes(changed);
|
||||
_locMan.ReloadLocalizations();
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ internal sealed partial class ReplayPlaybackManager
|
||||
_gameState.UpdateFullRep(state, cloneDelta: true);
|
||||
var next = Replay.NextState;
|
||||
BeforeApplyState?.Invoke((state, next));
|
||||
_gameState.ApplyGameState(state, next);
|
||||
_gameState.MergeImplicitData();
|
||||
var created = _gameState.ApplyGameState(state, next);
|
||||
_gameState.GenerateImplicitStates(created);
|
||||
DebugTools.Assert(Replay.LastApplied >= state.FromSequence);
|
||||
DebugTools.Assert(Replay.LastApplied + 1 <= state.ToSequence);
|
||||
Replay.LastApplied = state.ToSequence;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -7,63 +6,14 @@ namespace Robust.Client.UserInterface;
|
||||
|
||||
public static class BoundUserInterfaceExt
|
||||
{
|
||||
private static T GetWindow<T>(BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = bui.CreateDisposableControl<T>();
|
||||
window.OnClose += bui.Close;
|
||||
var system = bui.EntMan.System<UserInterfaceSystem>();
|
||||
system.RegisterControl(bui, window);
|
||||
return window;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a window and also handle closing the BUI when it's closed.
|
||||
/// </summary>
|
||||
public static T CreateWindow<T>(this BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = GetWindow<T>(bui);
|
||||
|
||||
if (bui.EntMan.System<UserInterfaceSystem>().TryGetPosition(bui.Owner, bui.UiKey, out var position))
|
||||
{
|
||||
window.Open(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCentered();
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public static T CreateWindowCenteredLeft<T>(this BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = GetWindow<T>(bui);
|
||||
|
||||
if (bui.EntMan.System<UserInterfaceSystem>().TryGetPosition(bui.Owner, bui.UiKey, out var position))
|
||||
{
|
||||
window.Open(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public static T CreateWindowCenteredRight<T>(this BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = GetWindow<T>(bui);
|
||||
|
||||
if (bui.EntMan.System<UserInterfaceSystem>().TryGetPosition(bui.Owner, bui.UiKey, out var position))
|
||||
{
|
||||
window.Open(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCenteredRight();
|
||||
}
|
||||
|
||||
var window = bui.CreateDisposableControl<T>();
|
||||
window.OpenCentered();
|
||||
window.OnClose += bui.Close;
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Robust.Client.UserInterface
|
||||
UserInterfaceManagerInternal.QueueStyleUpdate(this);
|
||||
}
|
||||
|
||||
public void InvalidateStyleSheet()
|
||||
internal void StyleSheetUpdate()
|
||||
{
|
||||
_stylesheetUpdateNeeded = true;
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
internal void StylesheetUpdateRecursive()
|
||||
{
|
||||
InvalidateStyleSheet();
|
||||
StyleSheetUpdate();
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
@@ -149,7 +149,7 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
public void DoStyleUpdate()
|
||||
internal void DoStyleUpdate()
|
||||
{
|
||||
_styleProperties.Clear();
|
||||
|
||||
|
||||
@@ -549,7 +549,7 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void Draw(IRenderHandle renderHandle)
|
||||
internal virtual void DrawInternal(IRenderHandle renderHandle)
|
||||
{
|
||||
Draw(renderHandle.DrawingHandleScreen);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ public sealed class TileSpawningUIController : UIController
|
||||
return;
|
||||
_window = UIManager.CreateWindow<TileSpawnWindow>();
|
||||
LayoutContainer.SetAnchorPreset(_window,LayoutContainer.LayoutPreset.CenterLeft);
|
||||
_window.SearchBar.GrabKeyboardFocus();
|
||||
_window.ClearButton.OnPressed += OnTileClearPressed;
|
||||
_window.SearchBar.OnTextChanged += OnTileSearchChanged;
|
||||
_window.TileList.OnItemSelected += OnTileItemSelected;
|
||||
|
||||
@@ -9,7 +9,6 @@ public class EntityPrototypeView : SpriteView
|
||||
{
|
||||
private string? _currentPrototype;
|
||||
private EntityUid? _ourEntity;
|
||||
private bool _isShowing;
|
||||
|
||||
public EntityPrototypeView()
|
||||
{
|
||||
@@ -23,6 +22,8 @@ public class EntityPrototypeView : SpriteView
|
||||
|
||||
public void SetPrototype(EntProtoId? entProto)
|
||||
{
|
||||
SpriteSystem ??= EntMan.System<SpriteSystem>();
|
||||
|
||||
if (entProto == _currentPrototype
|
||||
&& EntMan.TryGetComponent(Entity?.Owner, out MetaDataComponent? meta)
|
||||
&& meta.EntityPrototype?.ID == _currentPrototype)
|
||||
@@ -31,10 +32,14 @@ public class EntityPrototypeView : SpriteView
|
||||
}
|
||||
|
||||
_currentPrototype = entProto;
|
||||
SetEntity(null);
|
||||
EntMan.DeleteEntity(_ourEntity);
|
||||
|
||||
if (_ourEntity != null || _isShowing)
|
||||
if (_currentPrototype != null)
|
||||
{
|
||||
UpdateEntity();
|
||||
_ourEntity = EntMan.Spawn(_currentPrototype);
|
||||
SpriteSystem.ForceUpdate(_ourEntity.Value);
|
||||
SetEntity(_ourEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,38 +48,12 @@ public class EntityPrototypeView : SpriteView
|
||||
base.EnteredTree();
|
||||
|
||||
if (_currentPrototype != null)
|
||||
{
|
||||
UpdateEntity();
|
||||
}
|
||||
|
||||
_isShowing = true;
|
||||
SetPrototype(_currentPrototype);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
EntMan.TryQueueDeleteEntity(_ourEntity);
|
||||
_ourEntity = null;
|
||||
|
||||
_isShowing = false;
|
||||
}
|
||||
|
||||
private void UpdateEntity()
|
||||
{
|
||||
SetEntity(null);
|
||||
EntMan.DeleteEntity(_ourEntity);
|
||||
|
||||
if (_currentPrototype != null)
|
||||
{
|
||||
SpriteSystem ??= EntMan.System<SpriteSystem>();
|
||||
|
||||
_ourEntity = EntMan.Spawn(_currentPrototype);
|
||||
SpriteSystem.ForceUpdate(_ourEntity.Value);
|
||||
SetEntity(_ourEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ourEntity = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_spriteSize = new Vector2(longestRotatedSide, longestRotatedSide);
|
||||
}
|
||||
|
||||
protected internal override void Draw(IRenderHandle renderHandle)
|
||||
internal override void DrawInternal(IRenderHandle renderHandle)
|
||||
{
|
||||
if (!ResolveEntity(out var uid, out var sprite, out var xform))
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,6 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -103,8 +102,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
var cursor = CursorShape.Arrow;
|
||||
var previewDragMode = GetDragModeFor(args.RelativePosition);
|
||||
previewDragMode &= ~DragMode.Move;
|
||||
|
||||
switch (previewDragMode)
|
||||
{
|
||||
case DragMode.Top:
|
||||
@@ -119,6 +116,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
case DragMode.Bottom | DragMode.Left:
|
||||
case DragMode.Top | DragMode.Right:
|
||||
cursor = CursorShape.Crosshair;
|
||||
break;
|
||||
|
||||
case DragMode.Bottom | DragMode.Right:
|
||||
case DragMode.Top | DragMode.Left:
|
||||
cursor = CursorShape.Crosshair;
|
||||
@@ -160,6 +160,15 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
var rect = new UIBox2(left, top, right, bottom);
|
||||
LayoutContainer.SetPosition(this, rect.TopLeft);
|
||||
SetSize = rect.Size;
|
||||
|
||||
/*
|
||||
var timing = IoCManager.Resolve<IGameTiming>();
|
||||
|
||||
var l = GetValue<float>(LayoutContainer.MarginLeftProperty);
|
||||
var t = GetValue<float>(LayoutContainer.MarginTopProperty);
|
||||
|
||||
Logger.Debug($"{timing.CurFrame}: {rect.TopLeft}/({l}, {t}), {rect.Size}/{SetSize}");
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,16 +229,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
OnOpen?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the window and places it at the specified position.
|
||||
/// </summary>
|
||||
public void Open(Vector2 position)
|
||||
{
|
||||
Measure(Vector2Helpers.Infinity);
|
||||
Open();
|
||||
LayoutContainer.SetPosition(this, position);
|
||||
}
|
||||
|
||||
public void OpenCentered() => OpenCenteredAt(new Vector2(0.5f, 0.5f));
|
||||
|
||||
public void OpenToLeft() => OpenCenteredAt(new Vector2(0, 0.5f));
|
||||
|
||||
@@ -10,11 +10,4 @@ public sealed partial class TileSpawnWindow : DefaultWindow
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
SearchBar.GrabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,13 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
// -- Handlers: Out --
|
||||
|
||||
protected internal override void Draw(IRenderHandle handle)
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (Viewport == null)
|
||||
{
|
||||
handle.DrawingHandleScreen.DrawRect(UIBox2.FromDimensions(new Vector2(0, 0), Size * UIScale), Color.Red);
|
||||
handle.DrawRect(UIBox2.FromDimensions(new Vector2(0, 0), Size * UIScale), Color.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -82,7 +82,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
Viewport.RenderScreenOverlaysBelow(handle, this, viewportBounds);
|
||||
|
||||
Viewport.Render();
|
||||
handle.DrawingHandleScreen.DrawTextureRect(Viewport.RenderTarget.Texture,
|
||||
handle.DrawTextureRect(Viewport.RenderTarget.Texture,
|
||||
UIBox2.FromDimensions(new Vector2(0, 0), (Vector2i) (Viewport.Size / _viewportResolution)));
|
||||
|
||||
Viewport.RenderScreenOverlaysAbove(handle, this, viewportBounds);
|
||||
|
||||
@@ -7,7 +7,6 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
{
|
||||
@@ -147,16 +146,6 @@ namespace Robust.Client.UserInterface
|
||||
/// but not necessarily a new or existing control is rearranged.
|
||||
/// </summary>
|
||||
void UpdateHovered();
|
||||
|
||||
/// <summary>
|
||||
/// Render a control and all of its children.
|
||||
/// </summary>
|
||||
void RenderControl(in Control.ControlRenderArguments args, Control control);
|
||||
|
||||
/// <summary>
|
||||
/// Render a control and all of its children.
|
||||
/// </summary>
|
||||
void RenderControl(IRenderHandle handle, Control control, Vector2i position);
|
||||
}
|
||||
|
||||
public readonly struct PostDrawUIRootEventArgs
|
||||
|
||||
@@ -313,10 +313,7 @@ internal partial class UserInterfaceManager
|
||||
|
||||
private void _clearTooltip()
|
||||
{
|
||||
_resetTooltipTimer();
|
||||
|
||||
if (!_showingTooltip)
|
||||
return;
|
||||
if (!_showingTooltip) return;
|
||||
|
||||
if (_suppliedTooltip != null)
|
||||
{
|
||||
@@ -325,6 +322,7 @@ internal partial class UserInterfaceManager
|
||||
}
|
||||
|
||||
CurrentlyHovered?.PerformHideTooltip();
|
||||
_resetTooltipTimer();
|
||||
_showingTooltip = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ internal sealed partial class UserInterfaceManager
|
||||
_roots.Add(newRoot);
|
||||
_windowsToRoot.Add(window.Id, newRoot);
|
||||
|
||||
newRoot.InvalidateStyleSheet();
|
||||
newRoot.StyleSheetUpdate();
|
||||
newRoot.InvalidateMeasure();
|
||||
QueueMeasureUpdate(newRoot);
|
||||
QueueArrangeUpdate(newRoot);
|
||||
|
||||
@@ -335,30 +335,6 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderControl(in Control.ControlRenderArguments args, Control control)
|
||||
{
|
||||
var _ = 0;
|
||||
RenderControl(args.Handle,
|
||||
ref _,
|
||||
control,
|
||||
args.Position,
|
||||
args.Modulate,
|
||||
args.ScissorBox,
|
||||
args.CoordinateTransform);
|
||||
}
|
||||
|
||||
public void RenderControl(IRenderHandle handle, Control control, Vector2i position)
|
||||
{
|
||||
var _ = 0;
|
||||
RenderControl(handle,
|
||||
ref _,
|
||||
control,
|
||||
position,
|
||||
Color.White,
|
||||
null,
|
||||
Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
public void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox, Matrix3x2 coordinateTransform)
|
||||
{
|
||||
@@ -417,7 +393,7 @@ namespace Robust.Client.UserInterface
|
||||
// Handle modulation with care.
|
||||
var oldMod = handle.Modulate;
|
||||
handle.Modulate = modulate * control.ActualModulateSelf;
|
||||
control.Draw(renderHandle);
|
||||
control.DrawInternal(renderHandle);
|
||||
handle.Modulate = oldMod;
|
||||
handle.UseShader(null);
|
||||
}
|
||||
@@ -433,11 +409,10 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
control.PreRenderChildren(ref args);
|
||||
|
||||
for (var index = 0; index < control.ChildCount; index++)
|
||||
foreach (var child in control.Children)
|
||||
{
|
||||
var child = control.GetChild(index);
|
||||
var pos = position + (Vector2i)Vector2.Transform(child.PixelPosition, coordinateTransform);
|
||||
control.RenderChildOverride(ref args, index, pos);
|
||||
control.RenderChildOverride(ref args, child.GetPositionInParent(), pos);
|
||||
}
|
||||
|
||||
control.PostRenderChildren(ref args);
|
||||
|
||||
@@ -19,112 +19,117 @@ public class Generator : IIncrementalGenerator
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext initContext)
|
||||
{
|
||||
IncrementalValuesProvider<(string name, string code)?> dataDefinitions = initContext.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
static (node, _) => node is TypeDeclarationSyntax,
|
||||
static (context, _) =>
|
||||
{
|
||||
var type = (TypeDeclarationSyntax)context.Node;
|
||||
var symbol = (ITypeSymbol)context.SemanticModel.GetDeclaredSymbol(type)!;
|
||||
if (!IsDataDefinition(symbol))
|
||||
return null;
|
||||
|
||||
return GenerateForDataDefinition(type, symbol);
|
||||
}
|
||||
)
|
||||
.Where(static type => type != null);
|
||||
IncrementalValuesProvider<TypeDeclarationSyntax> dataDefinitions = initContext.SyntaxProvider.CreateSyntaxProvider(
|
||||
static (node, _) => node is TypeDeclarationSyntax,
|
||||
static (context, _) =>
|
||||
{
|
||||
var type = (TypeDeclarationSyntax) context.Node;
|
||||
var symbol = (ITypeSymbol) context.SemanticModel.GetDeclaredSymbol(type)!;
|
||||
return IsDataDefinition(symbol) ? type : null;
|
||||
}
|
||||
).Where(static type => type != null)!;
|
||||
|
||||
var comparer = new DataDefinitionComparer();
|
||||
initContext.RegisterSourceOutput(
|
||||
dataDefinitions,
|
||||
initContext.CompilationProvider.Combine(dataDefinitions.WithComparer(comparer).Collect()),
|
||||
static (sourceContext, source) =>
|
||||
{
|
||||
// TODO: deduplicate based on name?
|
||||
var (name, code) = source!.Value;
|
||||
var (compilation, declarations) = source;
|
||||
var builder = new StringBuilder();
|
||||
var containingTypes = new Stack<INamedTypeSymbol>();
|
||||
var declarationsGenerated = new HashSet<string>();
|
||||
var deltaType = compilation.GetTypeByMetadataName(ComponentDeltaInterfaceName)!;
|
||||
|
||||
sourceContext.AddSource(name, code);
|
||||
foreach (var declaration in declarations)
|
||||
{
|
||||
builder.Clear();
|
||||
containingTypes.Clear();
|
||||
|
||||
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
|
||||
|
||||
var symbolName = type
|
||||
.ToDisplayString()
|
||||
.Replace('<', '{')
|
||||
.Replace('>', '}');
|
||||
|
||||
if (!declarationsGenerated.Add(symbolName))
|
||||
continue;
|
||||
|
||||
var nonPartial = !IsPartial(declaration);
|
||||
|
||||
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty
|
||||
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
|
||||
|
||||
var containingType = type.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
containingTypes.Push(containingType);
|
||||
containingType = containingType.ContainingType;
|
||||
}
|
||||
|
||||
var containingTypesStart = new StringBuilder();
|
||||
var containingTypesEnd = new StringBuilder();
|
||||
foreach (var parent in containingTypes)
|
||||
{
|
||||
var syntax = (ClassDeclarationSyntax) parent.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (!IsPartial(syntax))
|
||||
{
|
||||
nonPartial = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
|
||||
containingTypesEnd.AppendLine("}");
|
||||
}
|
||||
|
||||
var definition = GetDataDefinition(type);
|
||||
if (nonPartial || definition.InvalidFields)
|
||||
continue;
|
||||
|
||||
builder.AppendLine($$"""
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Exceptions;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
#pragma warning disable RA0002 // Robust access analyzer
|
||||
|
||||
{{namespaceString}}
|
||||
|
||||
{{containingTypesStart}}
|
||||
|
||||
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
|
||||
{
|
||||
{{GetConstructor(definition)}}
|
||||
|
||||
{{GetCopyMethods(definition, deltaType)}}
|
||||
|
||||
{{GetInstantiators(definition, deltaType)}}
|
||||
}
|
||||
|
||||
{{containingTypesEnd}}
|
||||
""");
|
||||
|
||||
var sourceText = CSharpSyntaxTree
|
||||
.ParseText(builder.ToString())
|
||||
.GetRoot()
|
||||
.NormalizeWhitespace()
|
||||
.ToFullString();
|
||||
|
||||
sourceContext.AddSource($"{symbolName}.g.cs", sourceText);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static (string, string)? GenerateForDataDefinition(
|
||||
TypeDeclarationSyntax declaration,
|
||||
ITypeSymbol type)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var containingTypes = new Stack<INamedTypeSymbol>();
|
||||
containingTypes.Clear();
|
||||
|
||||
var symbolName = type
|
||||
.ToDisplayString()
|
||||
.Replace('<', '{')
|
||||
.Replace('>', '}');
|
||||
|
||||
var nonPartial = !IsPartial(declaration);
|
||||
|
||||
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty
|
||||
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
|
||||
|
||||
var containingType = type.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
containingTypes.Push(containingType);
|
||||
containingType = containingType.ContainingType;
|
||||
}
|
||||
|
||||
var containingTypesStart = new StringBuilder();
|
||||
var containingTypesEnd = new StringBuilder();
|
||||
foreach (var parent in containingTypes)
|
||||
{
|
||||
var syntax = (ClassDeclarationSyntax)parent.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (!IsPartial(syntax))
|
||||
{
|
||||
nonPartial = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
|
||||
containingTypesEnd.AppendLine("}");
|
||||
}
|
||||
|
||||
var definition = GetDataDefinition(type);
|
||||
if (nonPartial || definition.InvalidFields)
|
||||
return null;
|
||||
|
||||
builder.AppendLine($$"""
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Exceptions;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
#pragma warning disable RA0002 // Robust access analyzer
|
||||
|
||||
{{namespaceString}}
|
||||
|
||||
{{containingTypesStart}}
|
||||
|
||||
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
|
||||
{
|
||||
{{GetConstructor(definition)}}
|
||||
|
||||
{{GetCopyMethods(definition)}}
|
||||
|
||||
{{GetInstantiators(definition)}}
|
||||
}
|
||||
|
||||
{{containingTypesEnd}}
|
||||
""");
|
||||
|
||||
return ($"{symbolName}.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static DataDefinition GetDataDefinition(ITypeSymbol definition)
|
||||
{
|
||||
var fields = new List<DataField>();
|
||||
@@ -191,7 +196,7 @@ public class Generator : IIncrementalGenerator
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetCopyMethods(DataDefinition definition)
|
||||
private static string GetCopyMethods(DataDefinition definition, ITypeSymbol deltaType)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
@@ -262,36 +267,36 @@ public class Generator : IIncrementalGenerator
|
||||
{{baseCopy}}
|
||||
""");
|
||||
|
||||
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true))
|
||||
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true, deltaType))
|
||||
{
|
||||
var interfaceModifiers = baseType != null &&
|
||||
baseType.AllInterfaces.Any(i => i.ToDisplayString() == interfaceName)
|
||||
var interfaceModifiers = baseType != null && baseType.AllInterfaces.Contains(@interface, SymbolEqualityComparer.Default)
|
||||
? "override "
|
||||
: modifiers;
|
||||
var interfaceName = @interface.ToDisplayString();
|
||||
|
||||
builder.AppendLine($$"""
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var def = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref def, serialization, hookCtx, context);
|
||||
target = def;
|
||||
}
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var def = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref def, serialization, hookCtx, context);
|
||||
target = def;
|
||||
}
|
||||
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
InternalCopy(ref target, serialization, hookCtx, context);
|
||||
}
|
||||
""");
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
InternalCopy(ref target, serialization, hookCtx, context);
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetInstantiators(DataDefinition definition)
|
||||
private static string GetInstantiators(DataDefinition definition, ITypeSymbol deltaType)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var modifiers = string.Empty;
|
||||
@@ -325,28 +330,27 @@ public class Generator : IIncrementalGenerator
|
||||
""");
|
||||
}
|
||||
|
||||
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false))
|
||||
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false, deltaType))
|
||||
{
|
||||
var interfaceName = @interface.ToDisplayString();
|
||||
builder.AppendLine($$"""
|
||||
{{interfaceName}} {{interfaceName}}.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
{{interfaceName}} {{interfaceName}}.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
|
||||
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
""");
|
||||
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
private static IEnumerable<string> InternalGetImplicitDataDefinitionInterfaces(
|
||||
ITypeSymbol type,
|
||||
bool all)
|
||||
private static IEnumerable<ITypeSymbol> InternalGetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all, ITypeSymbol deltaType)
|
||||
{
|
||||
var symbols = GetImplicitDataDefinitionInterfaces(type, all);
|
||||
|
||||
@@ -364,10 +368,10 @@ public class Generator : IIncrementalGenerator
|
||||
return symbols;
|
||||
}
|
||||
|
||||
if (symbols.Any(x => x == ComponentDeltaInterfaceName))
|
||||
if (symbols.Any(x => x.ToDisplayString() == deltaType.ToDisplayString()))
|
||||
return symbols;
|
||||
|
||||
return symbols.Append(ComponentDeltaInterfaceName);
|
||||
return symbols.Append(deltaType);
|
||||
}
|
||||
|
||||
// TODO serveronly? do we care? who knows!!
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
@@ -94,13 +93,13 @@ internal static class Types
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
|
||||
internal static IEnumerable<ITypeSymbol> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
|
||||
{
|
||||
var interfaces = all ? type.AllInterfaces : type.Interfaces;
|
||||
foreach (var @interface in interfaces)
|
||||
{
|
||||
if (IsImplicitDataDefinitionInterface(@interface))
|
||||
yield return @interface.ToDisplayString();
|
||||
yield return @interface;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,11 +104,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var entity = SetupAudio(filename, audioParams);
|
||||
// Move it after setting it up
|
||||
XformSystem.SetCoordinates(entity, new EntityCoordinates(uid, Vector2.Zero));
|
||||
|
||||
// TODO AUDIO
|
||||
// Add methods that allow for custom audio range.
|
||||
// Some methods try to reduce the audio range, resulting in a custom filter which then unnecessarily has to
|
||||
// use PVS overrides. PlayEntity with a reduced range shouldn't need PVS overrides at all.
|
||||
AddAudioFilter(entity, entity.Comp, playerFilter);
|
||||
|
||||
return (entity, entity.Comp);
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
_resources.Initialize(dataDir);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Console.Commands
|
||||
{
|
||||
@@ -43,7 +42,7 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
_ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
|
||||
_ent.System<MapLoaderSystem>().Save(uid, args[1]);
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
}
|
||||
|
||||
@@ -64,6 +63,7 @@ namespace Robust.Server.Console.Commands
|
||||
public sealed class LoadGridCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
public override string Command => "loadgrid";
|
||||
@@ -91,14 +91,13 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var sys = _system.GetEntitySystem<SharedMapSystem>();
|
||||
if (!sys.MapExists(mapId))
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError("Target map does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 offset = default;
|
||||
var loadOptions = new MapLoadOptions();
|
||||
if (args.Length >= 4)
|
||||
{
|
||||
if (!float.TryParse(args[2], out var x))
|
||||
@@ -113,10 +112,9 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
offset = new Vector2(x, y);
|
||||
loadOptions.Offset = new Vector2(x, y);
|
||||
}
|
||||
|
||||
Angle rot = default;
|
||||
if (args.Length >= 5)
|
||||
{
|
||||
if (!float.TryParse(args[4], out var rotation))
|
||||
@@ -125,10 +123,9 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
rot = Angle.FromDegrees(rotation);
|
||||
loadOptions.Rotation = Angle.FromDegrees(rotation);
|
||||
}
|
||||
|
||||
var opts = DeserializationOptions.Default;
|
||||
if (args.Length >= 6)
|
||||
{
|
||||
if (!bool.TryParse(args[5], out var storeUids))
|
||||
@@ -137,11 +134,10 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
opts.StoreYamlUids = storeUids;
|
||||
loadOptions.StoreMapUids = storeUids;
|
||||
}
|
||||
|
||||
var path = new ResPath(args[1]);
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TryLoadGrid(mapId, path, out _, opts, offset, rot);
|
||||
_system.GetEntitySystem<MapLoaderSystem>().Load(mapId, args[1], loadOptions);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
@@ -153,6 +149,7 @@ namespace Robust.Server.Console.Commands
|
||||
public sealed class SaveMap : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
public override string Command => "savemap";
|
||||
@@ -192,14 +189,13 @@ namespace Robust.Server.Console.Commands
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var sys = _system.GetEntitySystem<SharedMapSystem>();
|
||||
if (!sys.MapExists(mapId))
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-savemap-not-exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sys.IsInitialized(mapId) &&
|
||||
if (_map.IsMapInitialized(mapId) &&
|
||||
( args.Length < 3 || !bool.TryParse(args[2], out var force) || !force))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-savemap-init-warning"));
|
||||
@@ -207,7 +203,7 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-attempt", ("mapId", mapId), ("path", args[1])));
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TrySaveMap(mapId, new ResPath(args[1]));
|
||||
_system.GetEntitySystem<MapLoaderSystem>().SaveMap(mapId, args[1]);
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||
}
|
||||
}
|
||||
@@ -215,6 +211,7 @@ namespace Robust.Server.Console.Commands
|
||||
public sealed class LoadMap : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
public override string Command => "loadmap";
|
||||
@@ -270,49 +267,61 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var sys = _system.GetEntitySystem<SharedMapSystem>();
|
||||
if (sys.MapExists(mapId))
|
||||
if (_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-loadmap-exists", ("mapId", mapId)));
|
||||
return;
|
||||
}
|
||||
|
||||
float x = 0;
|
||||
if (args.Length >= 3 && !float.TryParse(args[2], out x))
|
||||
var loadOptions = new MapLoadOptions();
|
||||
|
||||
float x = 0, y = 0;
|
||||
if (args.Length >= 3)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
|
||||
return;
|
||||
if (!float.TryParse(args[2], out x))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float y = 0;
|
||||
if (args.Length >= 4 && !float.TryParse(args[3], out y))
|
||||
if (args.Length >= 4)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3])));
|
||||
return;
|
||||
}
|
||||
var offset = new Vector2(x, y);
|
||||
|
||||
float rotation = 0;
|
||||
if (args.Length >= 5 && !float.TryParse(args[4], out rotation))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[4])));
|
||||
return;
|
||||
}
|
||||
var rot = new Angle(rotation);
|
||||
|
||||
bool storeUids = false;
|
||||
if (args.Length >= 6 && !bool.TryParse(args[5], out storeUids))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[5])));
|
||||
return;
|
||||
if (!float.TryParse(args[3], out y))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var opts = new DeserializationOptions {StoreYamlUids = storeUids};
|
||||
loadOptions.Offset = new Vector2(x, y);
|
||||
|
||||
var path = new ResPath(args[1]);
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TryLoadMapWithId(mapId, path, out _, out _, opts, offset, rot);
|
||||
if (args.Length >= 5)
|
||||
{
|
||||
if (!float.TryParse(args[4], out var rotation))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[4])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sys.MapExists(mapId))
|
||||
loadOptions.Rotation = new Angle(rotation);
|
||||
}
|
||||
|
||||
if (args.Length >= 6)
|
||||
{
|
||||
if (!bool.TryParse(args[5], out var storeUids))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[5])));
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.StoreMapUids = storeUids;
|
||||
}
|
||||
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TryLoad(mapId, args[1], out _, loadOptions);
|
||||
|
||||
if (_map.MapExists(mapId))
|
||||
shell.WriteLine(Loc.GetString("cmd-loadmap-success", ("mapId", mapId), ("path", args[1])));
|
||||
else
|
||||
shell.WriteLine(Loc.GetString("cmd-loadmap-error", ("path", args[1])));
|
||||
|
||||
1354
Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs
Normal file
1354
Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ namespace Robust.Server.GameObjects
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
var id = new MapId(++LastMapId);
|
||||
while (MapExists(id) || UsedIds.Contains(id))
|
||||
while (MapManager.MapExists(id))
|
||||
{
|
||||
id = new MapId(++LastMapId);
|
||||
}
|
||||
|
||||
21
Robust.Server/GameObjects/IServerEntityManagerInternal.cs
Normal file
21
Robust.Server/GameObjects/IServerEntityManagerInternal.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
internal interface IServerEntityManagerInternal : IServerEntityManager
|
||||
{
|
||||
// These methods are used by the map loader to do multi-stage entity construction during map load.
|
||||
// I would recommend you refer to the MapLoader for usage.
|
||||
|
||||
EntityUid AllocEntity(EntityPrototype? prototype);
|
||||
|
||||
void FinishEntityLoad(EntityUid entity, IEntityLoadContext? context = null);
|
||||
|
||||
void FinishEntityLoad(EntityUid entity, EntityPrototype? prototype, IEntityLoadContext? context = null);
|
||||
|
||||
void FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta = null);
|
||||
|
||||
void FinishEntityStartup(EntityUid entity);
|
||||
}
|
||||
}
|
||||
17
Robust.Server/GameObjects/MapSaveIdComponent.cs
Normal file
17
Robust.Server/GameObjects/MapSaveIdComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata component used to keep consistent UIDs inside map files cross saving.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This component stores the previous map UID of entities from map load.
|
||||
/// This can then be used to re-serialize the entity with the same UID for the merge driver to recognize.
|
||||
/// </remarks>
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
public sealed partial class MapSaveIdComponent : Component
|
||||
{
|
||||
public int Uid { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Components;
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="MapLoaderSystem"/> to track the original tile map from when a map was loaded.
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Server.GameObjects
|
||||
/// Manager for entities -- controls things like template loading and instantiation
|
||||
/// </summary>
|
||||
[UsedImplicitly] // DI Container
|
||||
public sealed class ServerEntityManager : EntityManager, IServerEntityManager
|
||||
public sealed class ServerEntityManager : EntityManager, IServerEntityManagerInternal
|
||||
{
|
||||
private static readonly Gauge EntitiesCount = Metrics.CreateGauge(
|
||||
"robust_entities_count",
|
||||
@@ -61,6 +61,32 @@ namespace Robust.Server.GameObjects
|
||||
_pvs = System<PvsSystem>();
|
||||
}
|
||||
|
||||
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype)
|
||||
{
|
||||
return AllocEntity(prototype, out _);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, IEntityLoadContext? context)
|
||||
{
|
||||
LoadEntity(entity, context);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, EntityPrototype? prototype, IEntityLoadContext? context)
|
||||
{
|
||||
LoadEntity(entity, context, prototype);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta)
|
||||
{
|
||||
InitializeEntity(entity, meta);
|
||||
}
|
||||
|
||||
[Obsolete("Use StartEntity")]
|
||||
void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity)
|
||||
{
|
||||
StartEntity(entity);
|
||||
}
|
||||
|
||||
internal override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
{
|
||||
if (prototypeName == null)
|
||||
|
||||
@@ -20,10 +20,6 @@ namespace Robust.Server.GameStates;
|
||||
/// </summary>
|
||||
internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<PvsData> memoryRegion)
|
||||
{
|
||||
#if DEBUG
|
||||
public HashSet<NetEntity> ToSendSet = new();
|
||||
#endif
|
||||
|
||||
public readonly ICommonSession Session = session;
|
||||
|
||||
public readonly ResizableMemoryRegion<PvsData> DataMemory = memoryRegion;
|
||||
@@ -184,9 +180,6 @@ internal struct PvsMetadata
|
||||
public NetEntity NetEntity;
|
||||
|
||||
public GameTick LastModifiedTick;
|
||||
|
||||
// TODO PVS maybe store as int?
|
||||
// Theres extra space anyways, and the mask checks always need to convert to an int first, so it'd probably be faster too.
|
||||
public ushort VisMask;
|
||||
public EntityLifeStage LifeStage;
|
||||
#if DEBUG
|
||||
@@ -204,7 +197,7 @@ internal struct PvsMetadata
|
||||
{
|
||||
DebugTools.AssertEqual(NetEntity, comp.NetEntity);
|
||||
DebugTools.AssertEqual(VisMask, comp.VisibilityMask);
|
||||
DebugTools.Assert(LifeStage == comp.EntityLifeStage);
|
||||
DebugTools.AssertEqual(LifeStage, comp.EntityLifeStage);
|
||||
DebugTools.Assert(LastModifiedTick == comp.EntityLastModifiedTick || LastModifiedTick.Value == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@ using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
public sealed class PvsOverrideSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
@@ -29,8 +28,7 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
base.Initialize();
|
||||
EntityManager.EntityDeleted += OnDeleted;
|
||||
_player.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
SubscribeLocalEvent<MapRemovedEvent>(OnMapRemoved);
|
||||
SubscribeLocalEvent<MapCreatedEvent>(OnMapCreated);
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridCreated);
|
||||
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
|
||||
|
||||
@@ -134,12 +132,10 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
|
||||
/// <summary>
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
|
||||
/// causing them to be sent to all clients. This will still respect visibility masks, it only overrides the range.
|
||||
/// causing them to always be sent to all clients.
|
||||
/// </summary>
|
||||
public override void AddGlobalOverride(EntityUid uid)
|
||||
public void AddGlobalOverride(EntityUid uid)
|
||||
{
|
||||
base.AddGlobalOverride(uid);
|
||||
|
||||
if (GlobalOverride.Add(uid))
|
||||
_hasOverride.Add(uid);
|
||||
}
|
||||
@@ -147,10 +143,8 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
/// <summary>
|
||||
/// Removes an entity from the global overrides.
|
||||
/// </summary>
|
||||
public override void RemoveGlobalOverride(EntityUid uid)
|
||||
public void RemoveGlobalOverride(EntityUid uid)
|
||||
{
|
||||
base.RemoveGlobalOverride(uid);
|
||||
|
||||
GlobalOverride.Remove(uid);
|
||||
// Not bothering to clear _hasOverride, as we'd have to check all the other collections, and at that point we
|
||||
// might as well just do that when the entity gets deleted anyways.
|
||||
@@ -160,9 +154,8 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
/// This causes an entity and all of its parents to always be sent to all players.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This differs from <see cref="AddGlobalOverride"/> as it does not send children, will ignore a players usual
|
||||
/// PVS budget, and ignores visibility masks. You generally shouldn't use this unless an entity absolutely always
|
||||
/// needs to be sent to all clients.
|
||||
/// This differs from <see cref="AddGlobalOverride"/> as it does not send children, and will ignore a players usual
|
||||
/// PVS budget. You generally shouldn't use this unless an entity absolutely always needs to be sent to all clients.
|
||||
/// </remarks>
|
||||
public void AddForceSend(EntityUid uid)
|
||||
{
|
||||
@@ -178,12 +171,11 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This causes an entity and all of its parents to always be sent to a player.
|
||||
/// This causes an entity and all of its parents to always be sent to a player..
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This differs from <see cref="AddSessionOverride"/> as it does not send children, will ignore a players usual
|
||||
/// PVS budget, and ignores visibility masks. You generally shouldn't use this unless an entity absolutely always
|
||||
/// needs to be sent to a client.
|
||||
/// This differs from <see cref="AddSessionOverride"/> as it does not send children, and will ignore a players usual
|
||||
/// PVS budget. You generally shouldn't use this unless an entity absolutely always needs to be sent to a client.
|
||||
/// </remarks>
|
||||
public void AddForceSend(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
@@ -209,12 +201,10 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
|
||||
/// <summary>
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations for a
|
||||
/// specific session. This will still respect visibility masks, it only overrides the range.
|
||||
/// specific session.
|
||||
/// </summary>
|
||||
public override void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
public void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
base.AddSessionOverride(uid, session);
|
||||
|
||||
if (SessionOverrides.GetOrNew(session).Add(uid))
|
||||
_hasOverride.Add(uid);
|
||||
}
|
||||
@@ -222,10 +212,8 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
/// <summary>
|
||||
/// Removes an entity from a session's overrides.
|
||||
/// </summary>
|
||||
public override void RemoveSessionOverride(EntityUid uid, ICommonSession session)
|
||||
public void RemoveSessionOverride(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
base.RemoveSessionOverride(uid, session);
|
||||
|
||||
if (!SessionOverrides.TryGetValue(session, out var overrides))
|
||||
return;
|
||||
|
||||
@@ -238,17 +226,13 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
|
||||
/// <summary>
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
|
||||
/// causing them to always be sent to the specified clients. This will still respect visibility masks, it only
|
||||
/// overrides the range.
|
||||
/// causing them to always be sent to all clients.
|
||||
/// </summary>
|
||||
public override void AddSessionOverrides(EntityUid uid, Filter filter)
|
||||
public void AddSessionOverrides(EntityUid uid, Filter filter)
|
||||
{
|
||||
_hasOverride.Add(uid);
|
||||
base.AddSessionOverrides(uid, filter);
|
||||
|
||||
foreach (var session in filter.Recipients)
|
||||
{
|
||||
SessionOverrides.GetOrNew(session).Add(uid);
|
||||
AddSessionOverride(uid, session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +259,14 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
|
||||
#region Map/Grid Events
|
||||
|
||||
private void OnMapChanged(MapChangedEvent ev)
|
||||
{
|
||||
if (ev.Created)
|
||||
OnMapCreated(ev);
|
||||
else
|
||||
OnMapDestroyed(ev);
|
||||
}
|
||||
|
||||
private void OnGridRemoved(GridRemovalEvent ev)
|
||||
{
|
||||
RemoveForceSend(ev.EntityUid);
|
||||
@@ -287,12 +279,12 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
AddForceSend(ev.EntityUid);
|
||||
}
|
||||
|
||||
private void OnMapRemoved(MapRemovedEvent ev)
|
||||
private void OnMapDestroyed(MapChangedEvent ev)
|
||||
{
|
||||
RemoveForceSend(ev.Uid);
|
||||
}
|
||||
|
||||
private void OnMapCreated(MapCreatedEvent ev)
|
||||
private void OnMapCreated(MapChangedEvent ev)
|
||||
{
|
||||
// TODO PVS remove this requirement.
|
||||
// I think this just required refactoring client game state logic so it doesn't sending maps/grids to nullspace.
|
||||
|
||||
@@ -303,8 +303,11 @@ internal sealed partial class PvsSystem
|
||||
RemoveRoot(ev.EntityUid);
|
||||
}
|
||||
|
||||
private void OnMapChanged(MapRemovedEvent ev)
|
||||
private void OnMapChanged(MapChangedEvent ev)
|
||||
{
|
||||
if (!ev.Destroyed)
|
||||
return;
|
||||
|
||||
RemoveRoot(ev.Uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,15 +19,14 @@ internal sealed partial class PvsSystem
|
||||
|
||||
private void AddAllOverrides(PvsSession session)
|
||||
{
|
||||
var mask = session.VisMask;
|
||||
var fromTick = session.FromTick;
|
||||
var mask = RaiseExpandEvent(session, fromTick);
|
||||
RaiseExpandEvent(session, fromTick);
|
||||
|
||||
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedGlobalOverride))
|
||||
{
|
||||
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
|
||||
meta.Validate(ent.Meta);
|
||||
|
||||
// PVS overrides still respect visibility masks
|
||||
if ((mask & meta.VisMask) == meta.VisMask)
|
||||
AddEntity(session, ref ent, ref meta, fromTick);
|
||||
}
|
||||
@@ -37,7 +36,7 @@ internal sealed partial class PvsSystem
|
||||
|
||||
foreach (var uid in sessionOverrides)
|
||||
{
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: true, mask);
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,23 +45,22 @@ internal sealed partial class PvsSystem
|
||||
/// </summary>
|
||||
private void AddForcedEntities(PvsSession session)
|
||||
{
|
||||
// Forced overrides do not respect visibility masks, so we set all bits.
|
||||
var mask = -1;
|
||||
|
||||
// Ignore PVS budgets
|
||||
session.Budget = new() {NewLimit = int.MaxValue, EnterLimit = int.MaxValue};
|
||||
|
||||
var mask = session.VisMask;
|
||||
var fromTick = session.FromTick;
|
||||
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedForceOverride))
|
||||
{
|
||||
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
|
||||
meta.Validate(ent.Meta);
|
||||
AddEntity(session, ref ent, ref meta, fromTick);
|
||||
if ((mask & meta.VisMask) == meta.VisMask)
|
||||
AddEntity(session, ref ent, ref meta, fromTick);
|
||||
}
|
||||
|
||||
foreach (var uid in session.Viewers)
|
||||
{
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: false, mask);
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: false);
|
||||
}
|
||||
|
||||
if (!_pvsOverride.SessionForceSend.TryGetValue(session.Session, out var sessionForce))
|
||||
@@ -70,13 +68,13 @@ internal sealed partial class PvsSystem
|
||||
|
||||
foreach (var uid in sessionForce)
|
||||
{
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: false, mask);
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: false);
|
||||
}
|
||||
}
|
||||
|
||||
private int RaiseExpandEvent(PvsSession session, GameTick fromTick)
|
||||
private void RaiseExpandEvent(PvsSession session, GameTick fromTick)
|
||||
{
|
||||
var expandEvent = new ExpandPvsEvent(session.Session, session.VisMask);
|
||||
var expandEvent = new ExpandPvsEvent(session.Session);
|
||||
|
||||
if (session.Session.AttachedEntity != null)
|
||||
RaiseLocalEvent(session.Session.AttachedEntity.Value, ref expandEvent, true);
|
||||
@@ -87,25 +85,23 @@ internal sealed partial class PvsSystem
|
||||
{
|
||||
foreach (var uid in expandEvent.Entities)
|
||||
{
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: false, expandEvent.VisMask);
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: false);
|
||||
}
|
||||
}
|
||||
|
||||
if (expandEvent.RecursiveEntities == null)
|
||||
return expandEvent.VisMask;
|
||||
return;
|
||||
|
||||
foreach (var uid in expandEvent.RecursiveEntities)
|
||||
{
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: true, expandEvent.VisMask);
|
||||
RecursivelyAddOverride(session, uid, fromTick, addChildren: true);
|
||||
}
|
||||
|
||||
return expandEvent.VisMask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively add an entity and all of its parents to the to-send set. This optionally also adds all children.
|
||||
/// </summary>
|
||||
private bool RecursivelyAddOverride(PvsSession session, EntityUid uid, GameTick fromTick, bool addChildren, int mask)
|
||||
private bool RecursivelyAddOverride(PvsSession session, EntityUid uid, GameTick fromTick, bool addChildren)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(uid, out var xform))
|
||||
{
|
||||
@@ -120,20 +116,17 @@ internal sealed partial class PvsSystem
|
||||
// to the toSend set, it doesn't guarantee that its parents have been. E.g., if a player ghost just teleported
|
||||
// to follow a far away entity, the player's own entity is still being sent, but we need to ensure that we also
|
||||
// send the new parents, which may otherwise be delayed because of the PVS budget.
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(session, parent, fromTick, false, mask))
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(session, parent, fromTick, false))
|
||||
return false;
|
||||
|
||||
if (!_metaQuery.TryGetComponent(uid, out var meta))
|
||||
return false;
|
||||
|
||||
if ((mask & meta.VisibilityMask) != meta.VisibilityMask)
|
||||
return false;
|
||||
|
||||
if (!AddEntity(session, (uid, meta), fromTick))
|
||||
return false;
|
||||
|
||||
if (addChildren)
|
||||
RecursivelyAddChildren(session, xform, fromTick, mask);
|
||||
RecursivelyAddChildren(session, xform, fromTick);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -141,7 +134,7 @@ internal sealed partial class PvsSystem
|
||||
/// <summary>
|
||||
/// Recursively add an entity and all of its children to the to-send set.
|
||||
/// </summary>
|
||||
private void RecursivelyAddChildren(PvsSession session, TransformComponent xform, GameTick fromTick, int mask)
|
||||
private void RecursivelyAddChildren(PvsSession session, TransformComponent xform, GameTick fromTick)
|
||||
{
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
@@ -152,14 +145,10 @@ internal sealed partial class PvsSystem
|
||||
}
|
||||
|
||||
var metadata = _metaQuery.GetComponent(child);
|
||||
|
||||
if ((mask & metadata.VisibilityMask) != metadata.VisibilityMask)
|
||||
continue;
|
||||
|
||||
if (!AddEntity(session, (child, metadata), fromTick))
|
||||
return; // Budget was exceeded (or some error occurred) -> return instead of continue.
|
||||
return;
|
||||
|
||||
RecursivelyAddChildren(session, childXform, fromTick, mask);
|
||||
RecursivelyAddChildren(session, childXform, fromTick);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,32 +142,18 @@ internal sealed partial class PvsSystem
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
var rep = new EntityStringRepresentation(entity);
|
||||
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Deletion queued: {EntityManager.IsQueuedForDeletion(uid)}. Trace:\n{Environment.StackTrace}");
|
||||
|
||||
// This can happen if some entity was some removed from it's parent while that parent was being deleted.
|
||||
// As a result the entity was marked for deletion but was never actually properly deleted.
|
||||
|
||||
bool queued;
|
||||
lock (_toDelete)
|
||||
{
|
||||
queued = EntityManager.IsQueuedForDeletion(uid) || _toDelete.Contains(uid);
|
||||
if (!queued)
|
||||
_toDelete.Add(uid);
|
||||
}
|
||||
|
||||
var rep = new EntityStringRepresentation(entity);
|
||||
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Deletion queued: {queued}. Trace:\n{Environment.StackTrace}");
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
data.LastSeen = _gameTiming.CurTick;
|
||||
session.ToSend!.Add(entity.Comp.PvsData);
|
||||
|
||||
// TODO PVS PERFORMANCE
|
||||
// Investigate whether its better to defer actually creating the entity state & populating session.States here?
|
||||
// I.e., should be be constructing the to-send list & to-get-states lists, and then separately getting all states
|
||||
// after we have gotten all entities? If the CPU can focus on only processing data in session.DataMemory without
|
||||
// having to access miscellaneous component info, maybe it will be faster?
|
||||
// Though for that to work I guess it also has to avoid accessing the metadata component's lifestage?
|
||||
|
||||
if (session.RequestedFull)
|
||||
{
|
||||
var state = GetFullEntityState(session.Session, uid, meta);
|
||||
|
||||
@@ -94,8 +94,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
/// </summary>
|
||||
private readonly List<GameTick> _deletedTick = new();
|
||||
|
||||
private readonly HashSet<EntityUid> _toDelete = new();
|
||||
|
||||
/// <summary>
|
||||
/// The sessions that are currently being processed. Note that this is in general used by parallel & async tasks.
|
||||
/// Hence player disconnection processing is deferred and only run via <see cref="ProcessDisconnections"/>.
|
||||
@@ -129,7 +127,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<MapRemovedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
|
||||
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
|
||||
|
||||
@@ -197,12 +195,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
// Construct & serialize the game state for each player (and for the replay).
|
||||
SerializeStates();
|
||||
|
||||
foreach (var uid in _toDelete)
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
}
|
||||
_toDelete.Clear();
|
||||
|
||||
// Compress & send the states.
|
||||
SendStates();
|
||||
|
||||
@@ -310,9 +302,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
// Process all entities in visible PVS chunks
|
||||
AddPvsChunks(session);
|
||||
|
||||
#if DEBUG
|
||||
VerifySessionData(session);
|
||||
#endif
|
||||
|
||||
var toSend = session.ToSend!;
|
||||
session.ToSend = null;
|
||||
@@ -342,12 +332,11 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
session.Overflow = oldEntry.Value;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Conditional("DEBUG")]
|
||||
private void VerifySessionData(PvsSession pvsSession)
|
||||
{
|
||||
var toSend = pvsSession.ToSend!;
|
||||
var toSendSet = pvsSession.ToSendSet;
|
||||
toSendSet.Clear();
|
||||
var toSend = pvsSession.ToSend;
|
||||
var toSendSet = new HashSet<NetEntity>(toSend!.Count);
|
||||
|
||||
foreach (var intPtr in toSend)
|
||||
{
|
||||
@@ -371,7 +360,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|| data.LastSeen == _gameTiming.CurTick - 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private (Vector2 worldPos, float range, EntityUid? map) CalcViewBounds(Entity<TransformComponent, EyeComponent?> eye)
|
||||
{
|
||||
@@ -473,27 +461,18 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public struct ExpandPvsEvent(ICommonSession session, int mask)
|
||||
public struct ExpandPvsEvent(ICommonSession session)
|
||||
{
|
||||
public readonly ICommonSession Session = session;
|
||||
|
||||
/// <summary>
|
||||
/// List of entities that will get added to this session's PVS set. This will still respect visibility masks.
|
||||
/// List of entities that will get added to this session's PVS set.
|
||||
/// </summary>
|
||||
public List<EntityUid>? Entities;
|
||||
|
||||
/// <summary>
|
||||
/// List of entities that will get added to this session's PVS set. Unlike <see cref="Entities"/> this will also
|
||||
/// recursively add all children of the given entity. This will still respect visibility masks.
|
||||
/// recursively add all children of the given entity.
|
||||
/// </summary>
|
||||
public List<EntityUid>? RecursiveEntities;
|
||||
|
||||
/// <summary>
|
||||
/// Visibility mask to use when adding entities. Defaults to the usual visibility mask for that client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this mask will affect all global & session overrides from <see cref="PvsOverrideSystem"/> for this
|
||||
/// client, not just the entities in <see cref="Entities"/> and <see cref="RecursiveEntities"/>.
|
||||
/// </remarks>
|
||||
public int VisMask = mask;
|
||||
}
|
||||
|
||||
12
Robust.Server/Maps/LoadedMapComponent.cs
Normal file
12
Robust.Server/Maps/LoadedMapComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Maps;
|
||||
|
||||
/// <summary>
|
||||
/// Added to Maps that were loaded by MapLoaderSystem. If not present then this map was created externally.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class LoadedMapComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -14,25 +14,19 @@ using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
namespace Robust.Server.Maps;
|
||||
|
||||
[TypeSerializer]
|
||||
internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingDataNode>, ITypeCopyCreator<MapChunk>
|
||||
{
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null)
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public MapChunk Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<MapChunk>? instantiationDelegate = null)
|
||||
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate<MapChunk>? instantiationDelegate = null)
|
||||
{
|
||||
var ind = (Vector2i) serializationManager.Read(typeof(Vector2i), node["ind"], hookCtx, context)!;
|
||||
var tileNode = (ValueDataNode)node["tiles"];
|
||||
@@ -56,8 +50,10 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
|
||||
IReadOnlyDictionary<int, string>? tileMap = null;
|
||||
|
||||
if (context is EntityDeserializer serContext)
|
||||
if (context is MapSerializationContext serContext)
|
||||
{
|
||||
tileMap = serContext.TileMap;
|
||||
}
|
||||
|
||||
if (tileMap == null)
|
||||
{
|
||||
@@ -77,7 +73,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
|
||||
var flags = reader.ReadByte();
|
||||
var flags = (TileRenderFlag)reader.ReadByte();
|
||||
var variant = reader.ReadByte();
|
||||
|
||||
var defName = tileMap[id];
|
||||
@@ -108,12 +104,16 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
|
||||
root.Add("version", new ValueDataNode("6"));
|
||||
|
||||
gridNode.Value = SerializeTiles(value, context as EntitySerializer);
|
||||
Dictionary<int, int>? tileWriteMap = null;
|
||||
if (context is MapSerializationContext mapContext)
|
||||
tileWriteMap = mapContext.TileWriteMap;
|
||||
|
||||
gridNode.Value = SerializeTiles(value, tileWriteMap);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string SerializeTiles(MapChunk chunk, EntitySerializer? serializer)
|
||||
private static string SerializeTiles(MapChunk chunk, Dictionary<int, int>? tileWriteMap)
|
||||
{
|
||||
// number of bytes written per tile, because sizeof(Tile) is useless.
|
||||
const int structSize = 6;
|
||||
@@ -124,34 +124,17 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
using (var stream = new MemoryStream(barr))
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
if (serializer == null)
|
||||
{
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
writer.Write(tile.TypeId);
|
||||
writer.Write((byte) tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
}
|
||||
}
|
||||
return Convert.ToBase64String(barr);
|
||||
}
|
||||
|
||||
var lastTile = -1;
|
||||
var yamlId = -1;
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
if (tile.TypeId != lastTile)
|
||||
yamlId = serializer.GetYamlTileId(tile.TypeId);
|
||||
var typeId = tile.TypeId;
|
||||
if (tileWriteMap != null)
|
||||
typeId = tileWriteMap[typeId];
|
||||
|
||||
lastTile = tile.TypeId;
|
||||
writer.Write(yamlId);
|
||||
writer.Write((byte) tile.Flags);
|
||||
writer.Write(typeId);
|
||||
writer.Write((byte)tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
}
|
||||
}
|
||||
@@ -160,12 +143,8 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
return Convert.ToBase64String(barr);
|
||||
}
|
||||
|
||||
public MapChunk CreateCopy(
|
||||
ISerializationManager serializationManager,
|
||||
MapChunk source,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null)
|
||||
public MapChunk CreateCopy(ISerializationManager serializationManager, MapChunk source,
|
||||
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var mapManager = dependencies.Resolve<IMapManager>();
|
||||
mapManager.SuppressOnTileChanged = true;
|
||||
59
Robust.Server/Maps/MapLoadOptions.cs
Normal file
59
Robust.Server/Maps/MapLoadOptions.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Server.Maps
|
||||
{
|
||||
[PublicAPI]
|
||||
public sealed class MapLoadOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, UID components will be created for loaded entities
|
||||
/// to maintain consistency upon subsequent savings.
|
||||
/// </summary>
|
||||
public bool StoreMapUids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset to apply to the loaded objects.
|
||||
/// </summary>
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => _offset;
|
||||
set
|
||||
{
|
||||
TransformMatrix = Matrix3Helpers.CreateTransform(value, Rotation);
|
||||
_offset = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation to apply to the loaded objects as a collective, around 0, 0.
|
||||
/// </summary>
|
||||
/// <remarks>Setting this overrides </remarks>
|
||||
public Angle Rotation
|
||||
{
|
||||
get => _rotation;
|
||||
set
|
||||
{
|
||||
TransformMatrix = Matrix3Helpers.CreateTransform(Offset, value);
|
||||
_rotation = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Angle _rotation = Angle.Zero;
|
||||
|
||||
public Matrix3x2 TransformMatrix { get; set; } = Matrix3x2.Identity;
|
||||
|
||||
/// <summary>
|
||||
/// If there is a map entity serialized should we also load it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be set to false if you want to load a map file onto an existing map and do not wish to overwrite the existing entity.
|
||||
/// </remarks>
|
||||
public bool LoadMap { get; set; } = true;
|
||||
|
||||
public bool DoMapInit = false;
|
||||
}
|
||||
}
|
||||
0
Robust.Server/Maps/YamlGridSerializer.cs
Normal file
0
Robust.Server/Maps/YamlGridSerializer.cs
Normal file
@@ -67,6 +67,7 @@ namespace Robust.Server
|
||||
deps.Register<IResourceManagerInternal, ResourceManager>();
|
||||
deps.Register<EntityManager, ServerEntityManager>();
|
||||
deps.Register<IServerEntityManager, ServerEntityManager>();
|
||||
deps.Register<IServerEntityManagerInternal, ServerEntityManager>();
|
||||
deps.Register<IServerGameStateManager, ServerGameStateManager>();
|
||||
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
|
||||
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
|
||||
|
||||
@@ -50,9 +50,6 @@ public sealed class GamePrototypeLoadManager : SharedPrototypeLoadManager
|
||||
|
||||
internal void SendToNewUser(INetChannel channel)
|
||||
{
|
||||
if (LoadedPrototypes.Count == 0)
|
||||
return;
|
||||
|
||||
// Just dump all the prototypes on connect, before them missing could be an issue.
|
||||
var msg = new GamePrototypeLoadMessage
|
||||
{
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Robust.Shared.Maths
|
||||
/// <param name="dir"></param>
|
||||
public Angle(Vector2 dir)
|
||||
{
|
||||
dir = dir.Normalized();
|
||||
Theta = Math.Atan2(dir.Y, dir.X);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
|
||||
|
||||
private const float AudioDespawnBuffer = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Default max range at which the sound can be heard.
|
||||
/// </summary>
|
||||
@@ -236,7 +234,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
var timed = EnsureComp<TimedDespawnComponent>(entity.Value);
|
||||
var audioLength = GetAudioLength(component.FileName);
|
||||
timed.Lifetime = (float) audioLength.TotalSeconds + AudioDespawnBuffer;
|
||||
timed.Lifetime = (float) audioLength.TotalSeconds + 0.01f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -324,7 +322,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
|
||||
var despawn = AddComp<TimedDespawnComponent>(uid);
|
||||
// Don't want to clip audio too short due to imprecision.
|
||||
despawn.Lifetime = (float) length.Value.TotalSeconds + AudioDespawnBuffer;
|
||||
despawn.Lifetime = (float) length.Value.TotalSeconds + 0.01f;
|
||||
}
|
||||
|
||||
if (comp.Params.Variation != null && comp.Params.Variation.Value != 0f)
|
||||
|
||||
@@ -1349,10 +1349,10 @@ namespace Robust.Shared
|
||||
/// MaxLinVelocity is compared to the dot product of linearVelocity * frameTime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default is 400 m/s in-line with Box2c. Box2d used 120m/s.
|
||||
/// Default is 35 m/s. Around half a tile per tick at 60 ticks per second.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> MaxLinVelocity =
|
||||
CVarDef.Create("physics.maxlinvelocity", 400f, CVar.SERVER | CVar.REPLICATED);
|
||||
CVarDef.Create("physics.maxlinvelocity", 35f, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum angular velocity in full rotations per second.
|
||||
@@ -1364,6 +1364,7 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> MaxAngVelocity =
|
||||
CVarDef.Create("physics.maxangvelocity", 15f);
|
||||
|
||||
|
||||
/*
|
||||
* User interface
|
||||
*/
|
||||
|
||||
@@ -59,7 +59,7 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
|
||||
UpdatesAfter.Add(typeof(SharedTransformSystem));
|
||||
UpdatesAfter.Add(typeof(SharedPhysicsSystem));
|
||||
|
||||
SubscribeLocalEvent<MapCreatedEvent>(MapManagerOnMapCreated);
|
||||
SubscribeLocalEvent<MapChangedEvent>(MapManagerOnMapCreated);
|
||||
SubscribeLocalEvent<GridInitializeEvent>(MapManagerOnGridCreated);
|
||||
|
||||
SubscribeLocalEvent<TComp, ComponentStartup>(OnCompStartup);
|
||||
@@ -143,8 +143,11 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
|
||||
RemComp(uid, component);
|
||||
}
|
||||
|
||||
private void MapManagerOnMapCreated(MapCreatedEvent e)
|
||||
private void MapManagerOnMapCreated(MapChangedEvent e)
|
||||
{
|
||||
if (e.Destroyed || e.Map == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
EnsureComp<TTreeComp>(e.Uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,58 +8,6 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Configuration
|
||||
{
|
||||
public static class CVarCommandUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a string into an object of the given type.
|
||||
/// </summary>
|
||||
/// <exception cref="FormatException">Thrown if the string could not be parsed into the given type.</exception>
|
||||
/// <exception cref="NotSupportedException">Thrown if the type is not supported.</exception>
|
||||
public static object ParseObject(Type type, string input)
|
||||
{
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(input, out var val))
|
||||
return val;
|
||||
|
||||
if (Parse.TryInt32(input, out var intVal))
|
||||
{
|
||||
if (intVal == 0) return false;
|
||||
if (intVal == 1) return true;
|
||||
}
|
||||
|
||||
throw new FormatException($"Could not parse bool value: {input}");
|
||||
}
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
return Parse.Int32(input);
|
||||
}
|
||||
|
||||
if (type == typeof(float))
|
||||
{
|
||||
return Parse.Float(input);
|
||||
}
|
||||
|
||||
if (type == typeof(long))
|
||||
{
|
||||
return long.Parse(input);
|
||||
}
|
||||
|
||||
if (type == typeof(ushort))
|
||||
{
|
||||
return ushort.Parse(input);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
internal sealed class CVarCommand : LocalizedCommands
|
||||
{
|
||||
@@ -103,7 +51,7 @@ namespace Robust.Shared.Configuration
|
||||
var type = _cfg.GetCVarType(name);
|
||||
try
|
||||
{
|
||||
var parsed = CVarCommandUtil.ParseObject(type, value);
|
||||
var parsed = ParseObject(type, value);
|
||||
_cfg.SetCVar(name, parsed);
|
||||
}
|
||||
catch (FormatException)
|
||||
@@ -147,6 +95,50 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static object ParseObject(Type type, string input)
|
||||
{
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(input, out var val))
|
||||
return val;
|
||||
|
||||
if (Parse.TryInt32(input, out var intVal))
|
||||
{
|
||||
if (intVal == 0) return false;
|
||||
if (intVal == 1) return true;
|
||||
}
|
||||
|
||||
throw new FormatException($"Could not parse bool value: {input}");
|
||||
}
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
return Parse.Int32(input);
|
||||
}
|
||||
|
||||
if (type == typeof(float))
|
||||
{
|
||||
return Parse.Float(input);
|
||||
}
|
||||
|
||||
if (type == typeof(long))
|
||||
{
|
||||
return long.Parse(input);
|
||||
}
|
||||
|
||||
if (type == typeof(ushort))
|
||||
{
|
||||
return ushort.Parse(input);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CVarSubsCommand : LocalizedCommands
|
||||
|
||||
@@ -138,7 +138,7 @@ internal sealed class ListMapsCommand : LocalizedEntityCommands
|
||||
{
|
||||
var msg = new StringBuilder();
|
||||
|
||||
foreach (var mapId in _mapSystem.GetAllMapIds().OrderBy(id => id.Value))
|
||||
foreach (var mapId in _map.GetAllMapIds().OrderBy(id => id.Value))
|
||||
{
|
||||
if (!_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
continue;
|
||||
|
||||
@@ -197,10 +197,6 @@ public abstract partial class SharedContainerSystem
|
||||
{
|
||||
if (entity.Comp2 is { } physics)
|
||||
{
|
||||
// TODO CONTAINER
|
||||
// Is this actually needed?
|
||||
// I.e., shouldn't this just do a if (_timing.ApplyingState) return
|
||||
|
||||
// Here we intentionally don't dirty the physics comp. Client-side state handling will apply these same
|
||||
// changes. This also ensures that the server doesn't have to send the physics comp state to every
|
||||
// player for any entity inside of a container during init.
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,11 +14,7 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
void Initialize(string? userData);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -64,27 +63,5 @@ namespace Robust.Shared.ContentPack
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
|
||||
internal static string SafeGetResourcePath(string baseDir, ResPath path)
|
||||
{
|
||||
var relSysPath = path.ToRelativeSystemPath();
|
||||
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
|
||||
// better safe than sorry check
|
||||
if (!retPath.StartsWith(baseDir))
|
||||
{
|
||||
// Allow path to match if it's just missing the directory separator at the end.
|
||||
if (retPath != baseDir.TrimEnd('\\'))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
public virtual void Initialize(string? userData)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -379,13 +379,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
if (root is DirLoader loader)
|
||||
{
|
||||
var rootDir = loader.GetPath(new ResPath(@"/"));
|
||||
|
||||
// TODO: GET RID OF THIS.
|
||||
// This code shouldn't be passing OS disk paths through ResPath.
|
||||
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
yield return new ResPath(rootDir);
|
||||
yield return new ResPath(loader.GetPath(new ResPath(@"/")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,17 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string RootDir { get; }
|
||||
|
||||
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="WritableDirProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootDir">Root file system directory to allow writing.</param>
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -124,7 +119,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
return new WritableDirProvider(dirInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -185,7 +180,20 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
return GetFullPath(RootDir, path);
|
||||
}
|
||||
|
||||
private static string GetFullPath(string root, ResPath path)
|
||||
{
|
||||
var relPath = path.ToRelativeSystemPath();
|
||||
if (relPath.Contains("\\..") || relPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(root, relPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to Maps that were loaded by <see cref="MapLoaderSystem"/>. If not present then this map was created externally.
|
||||
/// </summary>
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
public sealed partial class LoadedMapComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component is optionally added to entities that get loaded from yaml files. It stores the UID that the entity
|
||||
/// had within the yaml file. This is used when saving the entity back to a yaml file so that it re-uses the same UID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is primarily intended to reduce the diff sizes when modifying yaml maps. Note that there is no guarantee that
|
||||
/// the given uid will be used when writing the entity. E.g., if more than one entity have this component with the
|
||||
/// same uid, only one of those entities will be saved with the requested id.
|
||||
/// </remarks>
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
public sealed partial class YamlUidComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Uid;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,985 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides methods for serializing entities into yaml. It provides some more control over
|
||||
/// serialization than the methods provided by <see cref="MapLoaderSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are several methods (e.g., <see cref="SerializeEntityRecursive"/> that serialize entities into a
|
||||
/// per-entity <see cref="MappingDataNode"/> stored in the <see cref="EntityData"/> dictionary, which is indexed by the
|
||||
/// entity's assigned yaml id (see <see cref="GetYamlUid"/>. The generated data can then be written to a larger yaml
|
||||
/// document using the various "Write" methods. (e.g., <see cref="WriteEntitySection"/>). After a one has finished using
|
||||
/// the generated data, the serializer needs to be reset (<see cref="Reset"/>) using it again to serialize other entities.
|
||||
/// </remarks>
|
||||
public sealed class EntitySerializer : ISerializationContext,
|
||||
ITypeSerializer<EntityUid, ValueDataNode>,
|
||||
ITypeSerializer<NetEntity, ValueDataNode>
|
||||
{
|
||||
public const int MapFormatVersion = 7;
|
||||
// v6->v7: PR #5572 - Added more metadata, List maps/grids/orphans, include some life-stage information
|
||||
// v5->v6: PR #4307 - Converted Tile.TypeId from ushort to int
|
||||
// v4->v5: PR #3992 - Removed name & author fields
|
||||
// v3->v4: PR #3913 - Grouped entities by prototype
|
||||
// v2->v3: PR #3468
|
||||
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
|
||||
[Dependency] public readonly EntityManager EntMan = default!;
|
||||
[Dependency] public readonly IGameTiming Timing = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
|
||||
[Dependency] private readonly IConfigurationManager _conf = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
private readonly ISawmill _log;
|
||||
public readonly Dictionary<EntityUid, int> YamlUidMap = new();
|
||||
public readonly HashSet<int> YamlIds = new();
|
||||
|
||||
|
||||
public string? CurrentComponent { get; private set; }
|
||||
public Entity<MetaDataComponent>? CurrentEntity { get; private set; }
|
||||
public int CurrentEntityYamlUid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tile ID -> yaml tile ID mapping.
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, int> _tileMap = new();
|
||||
private readonly HashSet<int> _yamlTileIds = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WritingReadingPrototypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, the serializer will refuse to serialize the given entity and will orphan any entity that is parented to
|
||||
/// it. This is useful for serializing things like a grid (or multiple grids & entities) that are parented to a map
|
||||
/// without actually serializing the map itself.
|
||||
/// </summary>
|
||||
public EntityUid Truncate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of all entities that have previously been ignored via <see cref="Truncate"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is tracked in case somebody does something weird, like trying to save a grid w/o its map, and then later on
|
||||
/// including the map in the file. AFAIK, that should work in principle, though it would lead to a weird file where
|
||||
/// the grid is orphaned and not on the map where it should be.
|
||||
/// </remarks>
|
||||
public readonly HashSet<EntityUid> Truncated = new();
|
||||
|
||||
public readonly SerializationOptions Options;
|
||||
|
||||
/// <summary>
|
||||
/// Cached prototype data. This is used to avoid writing redundant data that is already specified in an entity's
|
||||
/// prototype.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, Dictionary<string, MappingDataNode>> PrototypeCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// The serialized entity data.
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, (EntityUid Uid, MappingDataNode Node)> EntityData = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="EntityData"/> indices grouped by their entity prototype ids.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, List<int>> Prototypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized map entities.
|
||||
/// </summary>
|
||||
public readonly List<int> Maps = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized null-space entities.
|
||||
/// This only includes entities that were initially in null-space, it does not include entities that were
|
||||
/// serialized without their parents. Those are in <see cref="Orphans"/>.
|
||||
/// </summary>
|
||||
public readonly List<int> Nullspace = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized grid entities.
|
||||
/// </summary>
|
||||
public readonly List<int> Grids = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized entities in the file whose parents were not serialized. This does not include
|
||||
/// entities that did not have a parent (e.g., maps or null-space entities). I.e., these are the entities that
|
||||
/// need to be attached to a new parent when loading the file, unless you want to load them into null-space.
|
||||
/// </summary>
|
||||
public readonly List<int> Orphans = new();
|
||||
|
||||
private readonly string _metaName;
|
||||
private readonly string _xformName;
|
||||
private readonly MappingDataNode _emptyMetaNode;
|
||||
private readonly MappingDataNode _emptyXformNode;
|
||||
private int _nextYamlUid = 1;
|
||||
private int _nextYamlTileId;
|
||||
|
||||
private readonly List<EntityUid> _autoInclude = new();
|
||||
private readonly EntityQuery<YamlUidComponent> _yamlQuery;
|
||||
private readonly EntityQuery<MapGridComponent> _gridQuery;
|
||||
private readonly EntityQuery<MapComponent> _mapQuery;
|
||||
private readonly EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private readonly EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
/// <summary>
|
||||
/// C# event for checking whether an entity is serializable. Can be used by content to prevent specific entities
|
||||
/// from getting serialized.
|
||||
/// </summary>
|
||||
public event IsSerializableDelegate? OnIsSerializeable;
|
||||
public delegate void IsSerializableDelegate(Entity<MetaDataComponent> ent, ref bool serializable);
|
||||
|
||||
public EntitySerializer(IDependencyCollection _dependency, SerializationOptions options)
|
||||
{
|
||||
_dependency.InjectDependencies(this);
|
||||
|
||||
_log = _logMan.GetSawmill("entity_serializer");
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
|
||||
_metaName = _factory.GetComponentName(typeof(MetaDataComponent));
|
||||
_xformName = _factory.GetComponentName(typeof(TransformComponent));
|
||||
_emptyMetaNode = _serialization.WriteValueAs<MappingDataNode>(typeof(MetaDataComponent), new MetaDataComponent(), alwaysWrite: true, context: this);
|
||||
|
||||
CurrentComponent = _xformName;
|
||||
_emptyXformNode = _serialization.WriteValueAs<MappingDataNode>(typeof(TransformComponent), new TransformComponent(), alwaysWrite: true, context: this);
|
||||
CurrentComponent = null;
|
||||
|
||||
_yamlQuery = EntMan.GetEntityQuery<YamlUidComponent>();
|
||||
_gridQuery = EntMan.GetEntityQuery<MapGridComponent>();
|
||||
_mapQuery = EntMan.GetEntityQuery<MapComponent>();
|
||||
_metaQuery = EntMan.GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = EntMan.GetEntityQuery<TransformComponent>();
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public bool IsSerializable(Entity<MetaDataComponent?> ent)
|
||||
{
|
||||
if (ent.Comp == null && !EntMan.TryGetComponent(ent.Owner, out ent.Comp))
|
||||
return false;
|
||||
|
||||
if (ent.Comp.EntityPrototype?.MapSavable == false)
|
||||
return false;
|
||||
|
||||
bool serializable = true;
|
||||
OnIsSerializeable?.Invoke(ent!, ref serializable);
|
||||
return serializable;
|
||||
}
|
||||
|
||||
#region Serialize API
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a single entity. This does not automatically include
|
||||
/// children, though depending on the setting of <see cref="SerializationOptions.MissingEntityBehaviour"/> it may
|
||||
/// auto-include additional entities aside from the one provided.
|
||||
/// </summary>
|
||||
public void SerializeEntity(EntityUid uid)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
throw new Exception($"{EntMan.ToPrettyString(uid)} is not serializable");
|
||||
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
ReserveYamlId(uid);
|
||||
SerializeEntityInternal(uid);
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
if (_autoInclude.Count != 0)
|
||||
ProcessAutoInclude();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a set of entities. This does not automatically include children or parents, though depending on the
|
||||
/// setting of <see cref="SerializationOptions.MissingEntityBehaviour"/> it may auto-include additional entities
|
||||
/// aside from the one provided.
|
||||
/// </summary>
|
||||
public void SerializeEntities(HashSet<EntityUid> entities)
|
||||
{
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
throw new Exception($"{EntMan.ToPrettyString(uid)} is not serializable");
|
||||
}
|
||||
|
||||
ReserveYamlIds(entities);
|
||||
SerializeEntitiesInternal(entities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes an entity and all of its serializable children. Note that this will not automatically serialize the
|
||||
/// entity's parents.
|
||||
/// </summary>
|
||||
public void SerializeEntityRecursive(EntityUid root)
|
||||
{
|
||||
if (!IsSerializable(root))
|
||||
throw new Exception($"{EntMan.ToPrettyString(root)} is not serializable");
|
||||
|
||||
Truncate = _xformQuery.GetComponent(root).ParentUid;
|
||||
Truncated.Add(Truncate);
|
||||
InitializeTileMap(root);
|
||||
HashSet<EntityUid> entities = new();
|
||||
RecursivelyIncludeChildren(root, entities);
|
||||
ReserveYamlIds(entities);
|
||||
SerializeEntitiesInternal(entities);
|
||||
Truncate = EntityUid.Invalid;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="_tileMap"/> that is used to serialize grid chunks using
|
||||
/// <see cref="MapChunkSerializer"/>. This initialization just involves checking to see if any of the entities being
|
||||
/// serialized were previously deserialized. If they were, it will re-use the old tile map. This is not actually required,
|
||||
/// and is just meant to prevent large map file diffs when the internal tile ids change. I.e., you can serialize entities
|
||||
/// without initializing the tile map.
|
||||
/// </summary>
|
||||
private void InitializeTileMap(EntityUid root)
|
||||
{
|
||||
if (!FindSavedTileMap(root, out var savedMap))
|
||||
return;
|
||||
|
||||
// Note: some old maps were saved with duplicate id strings.
|
||||
// I.e, multiple integers that correspond to the same prototype id.
|
||||
// Hence the TryAdd()
|
||||
//
|
||||
// Though now we also need to use TryAdd in case InitializeTileMap() is called multiple times.
|
||||
// E.g., if different grids get added separately to a single save file, in which case the
|
||||
// tile map may already be partially populated.
|
||||
foreach (var (origId, prototypeId) in savedMap)
|
||||
{
|
||||
if (_tileDef.TryGetDefinition(prototypeId, out var definition))
|
||||
_tileMap.TryAdd(definition.TileId, origId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FindSavedTileMap(EntityUid root, [NotNullWhen(true)] out Dictionary<int, string>? map)
|
||||
{
|
||||
// Try and fetch the mapping directly
|
||||
if (EntMan.TryGetComponent(root, out MapSaveTileMapComponent? comp))
|
||||
{
|
||||
map = comp.TileMap;
|
||||
return true;
|
||||
}
|
||||
|
||||
// iterate over all of its children and grab the first grid with a mapping
|
||||
var xform = _xformQuery.GetComponent(root);
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (!EntMan.TryGetComponent(child, out MapSaveTileMapComponent? cComp))
|
||||
continue;
|
||||
map = cComp.TileMap;
|
||||
return true;
|
||||
}
|
||||
|
||||
map = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
#region AutoInclude
|
||||
|
||||
private void ProcessAutoInclude()
|
||||
{
|
||||
DebugTools.AssertEqual(_autoInclude.ToHashSet().Count, _autoInclude.Count);
|
||||
|
||||
var ents = new HashSet<EntityUid>();
|
||||
|
||||
switch (Options.MissingEntityBehaviour)
|
||||
{
|
||||
case MissingEntityBehaviour.PartialInclude:
|
||||
// Include the entity and any of its direct parents
|
||||
foreach (var uid in _autoInclude)
|
||||
{
|
||||
RecursivelyIncludeParents(uid, ents);
|
||||
}
|
||||
break;
|
||||
case MissingEntityBehaviour.IncludeNullspace:
|
||||
case MissingEntityBehaviour.AutoInclude:
|
||||
// Find the root transform of all the included entities
|
||||
var roots = new HashSet<EntityUid>();
|
||||
foreach (var uid in _autoInclude)
|
||||
{
|
||||
GetRootNode(uid, roots);
|
||||
}
|
||||
|
||||
// Recursively include all children of these root nodes.
|
||||
foreach (var root in roots)
|
||||
{
|
||||
RecursivelyIncludeChildren(root, ents);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_autoInclude.Clear();
|
||||
SerializeEntitiesInternal(ents);
|
||||
}
|
||||
|
||||
private void RecursivelyIncludeChildren(EntityUid uid, HashSet<EntityUid> ents)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
return;
|
||||
|
||||
ents.Add(uid);
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
RecursivelyIncludeChildren(child, ents);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetRootNode(EntityUid uid, HashSet<EntityUid> ents)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
throw new NotSupportedException($"Attempted to auto-include an unserializable entity: {EntMan.ToPrettyString(uid)}");
|
||||
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
while (xform.ParentUid.IsValid() && xform.ParentUid != Truncate)
|
||||
{
|
||||
uid = xform.ParentUid;
|
||||
xform = _xformQuery.GetComponent(uid);
|
||||
|
||||
if (!IsSerializable(uid))
|
||||
throw new NotSupportedException($"Encountered an un-serializable parent entity: {EntMan.ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
ents.Add(uid);
|
||||
}
|
||||
|
||||
private void RecursivelyIncludeParents(EntityUid uid, HashSet<EntityUid> ents)
|
||||
{
|
||||
while (uid.IsValid() && uid != Truncate)
|
||||
{
|
||||
if (!ents.Add(uid))
|
||||
break;
|
||||
|
||||
if (!IsSerializable(uid))
|
||||
throw new NotSupportedException($"Encountered an un-serializable parent entity: {EntMan.ToPrettyString(uid)}");
|
||||
|
||||
uid = _xformQuery.GetComponent(uid).ParentUid;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void SerializeEntitiesInternal(HashSet<EntityUid> entities)
|
||||
{
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
SerializeEntityInternal(uid);
|
||||
}
|
||||
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
if (_autoInclude.Count != 0)
|
||||
ProcessAutoInclude();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a single entity, and store the results in <see cref="EntityData"/>.
|
||||
/// </summary>
|
||||
private void SerializeEntityInternal(EntityUid uid)
|
||||
{
|
||||
var saveId = GetYamlUid(uid);
|
||||
DebugTools.Assert(!EntityData.ContainsKey(saveId));
|
||||
|
||||
// It might be possible that something could cause an entity to be included twice.
|
||||
// E.g., if someone serializes a grid w/o its map, and then tries to separately include the map and all its children.
|
||||
// In that case, the grid would already have been serialized as a orphan.
|
||||
// uhhh.... I guess its fine?
|
||||
if (EntityData.ContainsKey(saveId))
|
||||
return;
|
||||
|
||||
var meta = _metaQuery.GetComponent(uid);
|
||||
var protoId = meta.EntityPrototype?.ID ?? string.Empty;
|
||||
|
||||
switch (meta.EntityLifeStage)
|
||||
{
|
||||
case <= EntityLifeStage.Initializing:
|
||||
_log.Error($"Encountered an uninitialized entity: {EntMan.ToPrettyString(uid)}");
|
||||
break;
|
||||
case >= EntityLifeStage.Terminating:
|
||||
_log.Error($"Encountered terminating or deleted entity: {EntMan.ToPrettyString(uid)}");
|
||||
break;
|
||||
}
|
||||
|
||||
CurrentEntityYamlUid = saveId;
|
||||
CurrentEntity = (uid, meta);
|
||||
|
||||
Prototypes.GetOrNew(protoId).Add(saveId);
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
|
||||
if (_mapQuery.HasComp(uid))
|
||||
Maps.Add(saveId);
|
||||
else if (xform.ParentUid == EntityUid.Invalid)
|
||||
Nullspace.Add(saveId);
|
||||
|
||||
if (_gridQuery.HasComp(uid))
|
||||
{
|
||||
// The current assumption is that grids cannot be in null-space, because the rest of the code
|
||||
// (broadphase, etc) don't support grids without maps.
|
||||
DebugTools.Assert(xform.ParentUid != EntityUid.Invalid || _mapQuery.HasComp(uid));
|
||||
Grids.Add(saveId);
|
||||
}
|
||||
|
||||
var entData = new MappingDataNode
|
||||
{
|
||||
{"uid", saveId.ToString(CultureInfo.InvariantCulture)}
|
||||
};
|
||||
|
||||
EntityData[saveId] = (uid, entData);
|
||||
var cache = GetProtoCache(meta.EntityPrototype);
|
||||
|
||||
// Store information about whether a given entity has been map-initialized.
|
||||
// In principle, if a map has been map-initialized, then all entities on that map should also be map-initialized.
|
||||
// But technically there is nothing that prevents someone from moving a post-init entity onto a pre-init map and vice-versa.
|
||||
// Also, we need to record this information even if the map is not being serialized.
|
||||
// In 99% of cases, this data is probably redundant and just bloats the file, but I can't think of a better way of handling it.
|
||||
// At least it should only bloat post-init maps, which aren't really getting used so far.
|
||||
if (meta.EntityLifeStage == EntityLifeStage.MapInitialized)
|
||||
{
|
||||
if (Options.ExpectPreInit)
|
||||
_log.Error($"Expected all entities to be pre-mapinit, but encountered post-init entity: {EntMan.ToPrettyString(uid)}");
|
||||
entData.Add("mapInit", "true");
|
||||
|
||||
// If an entity has been map-initialized, we assume it is un-paused.
|
||||
// If it is paused, we have to specify it.
|
||||
if (meta.EntityPaused)
|
||||
entData.Add("paused", "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
// If an entity has not yet been map-initialized, we assume it is paused.
|
||||
// I don't know in what situations it wouldn't be, but might as well future proof this.
|
||||
if (!meta.EntityPaused)
|
||||
entData.Add("paused", "false");
|
||||
}
|
||||
|
||||
var components = new SequenceDataNode();
|
||||
if (xform.NoLocalRotation && xform.LocalRotation != 0)
|
||||
{
|
||||
_log.Error($"Encountered a no-rotation entity with non-zero local rotation: {EntMan.ToPrettyString(uid)}");
|
||||
xform._localRotation = 0;
|
||||
}
|
||||
|
||||
foreach (var component in EntMan.GetComponentsInternal(uid))
|
||||
{
|
||||
var compType = component.GetType();
|
||||
|
||||
var reg = _factory.GetRegistration(compType);
|
||||
if (reg.Unsaved)
|
||||
continue;
|
||||
|
||||
CurrentComponent = reg.Name;
|
||||
MappingDataNode? compMapping;
|
||||
MappingDataNode? protoMapping = null;
|
||||
if (cache != null && cache.TryGetValue(reg.Name, out protoMapping))
|
||||
{
|
||||
// If this has a prototype, we need to use alwaysWrite: true.
|
||||
// E.g., an anchored prototype might have anchored: true. If we we are saving an un-anchored
|
||||
// instance of this entity, and if we have alwaysWrite: false, then compMapping would not include
|
||||
// the anchored data-field (as false is the default for this bool data field), so the entity would
|
||||
// implicitly be saved as anchored.
|
||||
compMapping = _serialization.WriteValueAs<MappingDataNode>(compType, component, alwaysWrite: true, context: this);
|
||||
|
||||
// This will not recursively call Except() on the values of the mapping. It will only remove
|
||||
// key-value pairs if both the keys and values are equal.
|
||||
compMapping = compMapping.Except(protoMapping);
|
||||
if(compMapping == null)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
compMapping = _serialization.WriteValueAs<MappingDataNode>(compType, component, alwaysWrite: false, context: this);
|
||||
}
|
||||
|
||||
// Don't need to write it if nothing was written! Note that if this entity has no associated
|
||||
// prototype, we ALWAYS want to write the component, because merely the fact that it exists is
|
||||
// information that needs to be written.
|
||||
if (compMapping.Children.Count != 0 || protoMapping == null)
|
||||
{
|
||||
compMapping.InsertAt(0, "type", new ValueDataNode(reg.Name));
|
||||
components.Add(compMapping);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentComponent = null;
|
||||
if (components.Count != 0)
|
||||
entData.Add("components", components);
|
||||
|
||||
// TODO ENTITY SERIALIZATION
|
||||
// Consider adding a Action<EntityUid, MappingDataNode>? OnEntitySerialized
|
||||
// I.e., allow content to modify the per-entity data? I don't know if that would actually be useful, as content
|
||||
// could just as easily append a separate entity dictionary to the output that has the extra per-entity data they
|
||||
// want to serialize.
|
||||
|
||||
if (meta.EntityPrototype == null)
|
||||
{
|
||||
CurrentEntityYamlUid = 0;
|
||||
CurrentEntity = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// an entity may have less components than the original prototype, so we need to check if any are missing.
|
||||
SequenceDataNode? missingComponents = null;
|
||||
foreach (var (name, comp) in meta.EntityPrototype.Components)
|
||||
{
|
||||
// try comp instead of has-comp as it checks whether the component is supposed to have been
|
||||
// deleted.
|
||||
if (EntMan.TryGetComponent(uid, comp.Component.GetType(), out _))
|
||||
continue;
|
||||
|
||||
missingComponents ??= new();
|
||||
missingComponents.Add(new ValueDataNode(name));
|
||||
}
|
||||
|
||||
if (missingComponents != null)
|
||||
entData.Add("missingComponents", missingComponents);
|
||||
|
||||
CurrentEntityYamlUid = 0;
|
||||
CurrentEntity = null;
|
||||
}
|
||||
|
||||
private Dictionary<string, MappingDataNode>? GetProtoCache(EntityPrototype? proto)
|
||||
{
|
||||
if (proto == null)
|
||||
return null;
|
||||
|
||||
if (PrototypeCache.TryGetValue(proto.ID, out var cache))
|
||||
return cache;
|
||||
|
||||
PrototypeCache[proto.ID] = cache = new(proto.Components.Count);
|
||||
WritingReadingPrototypes = true;
|
||||
|
||||
foreach (var (compName, comp) in proto.Components)
|
||||
{
|
||||
CurrentComponent = compName;
|
||||
cache.Add(compName, _serialization.WriteValueAs<MappingDataNode>(comp.Component.GetType(), comp.Component, alwaysWrite: true, context: this));
|
||||
}
|
||||
|
||||
CurrentComponent = null;
|
||||
WritingReadingPrototypes = false;
|
||||
cache.TryAdd(_metaName, _emptyMetaNode);
|
||||
cache.TryAdd(_xformName, _emptyXformNode);
|
||||
return cache;
|
||||
}
|
||||
|
||||
#region Write
|
||||
|
||||
public MappingDataNode Write()
|
||||
{
|
||||
DebugTools.AssertEqual(Maps.ToHashSet().Count, Maps.Count, "Duplicate maps?");
|
||||
DebugTools.AssertEqual(Grids.ToHashSet().Count, Grids.Count, "Duplicate frids?");
|
||||
DebugTools.AssertEqual(Orphans.ToHashSet().Count, Orphans.Count, "Duplicate orphans?");
|
||||
DebugTools.AssertEqual(Nullspace.ToHashSet().Count, Nullspace.Count, "Duplicate nullspace?");
|
||||
|
||||
return new MappingDataNode
|
||||
{
|
||||
{"meta", WriteMetadata()},
|
||||
{"maps", WriteIds(Maps)},
|
||||
{"grids", WriteIds(Grids)},
|
||||
{"orphans", WriteIds(Orphans)},
|
||||
{"nullspace", WriteIds(Nullspace)},
|
||||
{"tilemap", WriteTileMap()},
|
||||
{"entities", WriteEntitySection()},
|
||||
};
|
||||
}
|
||||
|
||||
public MappingDataNode WriteMetadata()
|
||||
{
|
||||
return new MappingDataNode
|
||||
{
|
||||
{"format", MapFormatVersion.ToString(CultureInfo.InvariantCulture)},
|
||||
{"category", GetCategory().ToString()},
|
||||
{"engineVersion", _conf.GetCVar(CVars.BuildEngineVersion) },
|
||||
{"forkId", _conf.GetCVar(CVars.BuildForkId)},
|
||||
{"forkVersion", _conf.GetCVar(CVars.BuildVersion)},
|
||||
{"time", DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)},
|
||||
{"entityCount", EntityData.Count.ToString(CultureInfo.InvariantCulture)}
|
||||
};
|
||||
}
|
||||
|
||||
public SequenceDataNode WriteIds(List<int> ids)
|
||||
{
|
||||
var result = new SequenceDataNode();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
result.Add(new ValueDataNode(id.ToString(CultureInfo.InvariantCulture)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize the <see cref="_tileMap"/> to yaml. This data is required to deserialize any serialized grid chunks using <see cref="MapChunkSerializer"/>.
|
||||
/// </summary>
|
||||
public MappingDataNode WriteTileMap()
|
||||
{
|
||||
var map = new MappingDataNode();
|
||||
foreach (var (tileId, yamlTileId) in _tileMap.OrderBy(x => x.Key))
|
||||
{
|
||||
// This can come up if tests try to serialize test maps with custom / placeholder tile ids without registering them with the tile def manager..
|
||||
if (!_tileDef.TryGetDefinition(tileId, out var def))
|
||||
throw new Exception($"Attempting to serialize a tile {tileId} with no valid tile definition.");
|
||||
|
||||
var yamlId = yamlTileId.ToString(CultureInfo.InvariantCulture);
|
||||
map.Add(yamlId, def.ID);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public SequenceDataNode WriteEntitySection()
|
||||
{
|
||||
if (YamlIds.Count != YamlUidMap.Count || YamlIds.Count != EntityData.Count)
|
||||
{
|
||||
// Maybe someone reserved a yaml id with ReserveYamlId() or implicitly with GetId() without actually
|
||||
// ever serializing the entity, This can lead to references to non-existent entities.
|
||||
throw new Exception($"Entity count mismatch");
|
||||
}
|
||||
|
||||
var prototypes = new SequenceDataNode();
|
||||
var protos = Prototypes.Keys.ToList();
|
||||
protos.Sort(StringComparer.InvariantCulture);
|
||||
|
||||
foreach (var protoId in protos)
|
||||
{
|
||||
var entities = new SequenceDataNode();
|
||||
var node = new MappingDataNode
|
||||
{
|
||||
{ "proto", protoId },
|
||||
{ "entities", entities},
|
||||
};
|
||||
|
||||
prototypes.Add(node);
|
||||
|
||||
var saveIds = Prototypes[protoId];
|
||||
saveIds.Sort();
|
||||
foreach (var saveId in saveIds)
|
||||
{
|
||||
var entData = EntityData[saveId].Node;
|
||||
entities.Add(entData);
|
||||
}
|
||||
}
|
||||
|
||||
return prototypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the category that the serialized data belongs to. If one was specified in the
|
||||
/// <see cref="SerializationOptions"/> it will use that after validating it, otherwise it will attempt to infer a
|
||||
/// category.
|
||||
/// </summary>
|
||||
public FileCategory GetCategory()
|
||||
{
|
||||
switch (Options.Category)
|
||||
{
|
||||
case FileCategory.Save:
|
||||
return FileCategory.Save;
|
||||
|
||||
case FileCategory.Map:
|
||||
return Maps.Count == 1 ? FileCategory.Map : FileCategory.Unknown;
|
||||
|
||||
case FileCategory.Grid:
|
||||
if (Maps.Count > 0 || Grids.Count != 1)
|
||||
return FileCategory.Unknown;
|
||||
return FileCategory.Grid;
|
||||
|
||||
case FileCategory.Entity:
|
||||
if (Maps.Count > 0 || Grids.Count > 0 || Orphans.Count != 1)
|
||||
return FileCategory.Unknown;
|
||||
return FileCategory.Entity;
|
||||
|
||||
default:
|
||||
if (Maps.Count == 1)
|
||||
{
|
||||
// Contains a single map, and no orphaned entities that need reparenting.
|
||||
if (Orphans.Count == 0)
|
||||
return FileCategory.Map;
|
||||
}
|
||||
else if (Grids.Count == 1)
|
||||
{
|
||||
// Contains a single orphaned grid.
|
||||
if (Orphans.Count == 1 && Grids[0] == Orphans[0])
|
||||
return FileCategory.Grid;
|
||||
}
|
||||
else if (Orphans.Count == 1)
|
||||
{
|
||||
// A lone orphaned entity.
|
||||
return FileCategory.Entity;
|
||||
}
|
||||
|
||||
return FileCategory.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region YamlIds
|
||||
|
||||
/// <summary>
|
||||
/// Get (or allocate) the integer id that will be used in the serialized file to refer to the given entity.
|
||||
/// </summary>
|
||||
public int GetYamlUid(EntityUid uid)
|
||||
{
|
||||
return !YamlUidMap.TryGetValue(uid, out var id) ? AllocateYamlUid(uid) : id;
|
||||
}
|
||||
|
||||
private int AllocateYamlUid(EntityUid uid)
|
||||
{
|
||||
if (Truncated.Contains(uid))
|
||||
{
|
||||
_log.Error(
|
||||
"Including a previously truncated entity within the serialization process? Something probably wrong");
|
||||
}
|
||||
|
||||
DebugTools.Assert(!YamlUidMap.ContainsKey(uid));
|
||||
while (!YamlIds.Add(_nextYamlUid))
|
||||
{
|
||||
_nextYamlUid++;
|
||||
}
|
||||
|
||||
YamlUidMap.Add(uid, _nextYamlUid);
|
||||
return _nextYamlUid++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get (or allocate) the integer id that will be used in the serialized file to refer to the given grid tile id.
|
||||
/// </summary>
|
||||
public int GetYamlTileId(int tileId)
|
||||
{
|
||||
if (_tileMap.TryGetValue(tileId, out var yamlId))
|
||||
return yamlId;
|
||||
|
||||
return AllocateYamlTileId(tileId);
|
||||
}
|
||||
|
||||
private int AllocateYamlTileId(int tileId)
|
||||
{
|
||||
while (!_yamlTileIds.Add(_nextYamlTileId))
|
||||
{
|
||||
_nextYamlTileId++;
|
||||
}
|
||||
|
||||
_tileMap[tileId] = _nextYamlTileId;
|
||||
return _nextYamlTileId++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method ensures that the given entities have a yaml ids assigned. If the entities have a
|
||||
/// <see cref="YamlUidComponent"/>, they will attempt to use that id, which exists to prevent large map file diffs
|
||||
/// due to changing yaml ids.
|
||||
/// </summary>
|
||||
public void ReserveYamlIds(HashSet<EntityUid> entities)
|
||||
{
|
||||
List<EntityUid> needIds = new();
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (YamlUidMap.ContainsKey(uid))
|
||||
continue;
|
||||
|
||||
if (_yamlQuery.TryGetComponent(uid, out var comp) && comp.Uid > 0 && YamlIds.Add(comp.Uid))
|
||||
{
|
||||
if (Truncated.Contains(uid))
|
||||
{
|
||||
_log.Error(
|
||||
"Including a previously truncated entity within the serialization process? Something probably wrong");
|
||||
}
|
||||
|
||||
YamlUidMap.Add(uid, comp.Uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
needIds.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var uid in needIds)
|
||||
{
|
||||
AllocateYamlUid(uid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method ensures that the given entity has a yaml id assigned to it. If the entity has a
|
||||
/// <see cref="YamlUidComponent"/>, it will attempt to use that id, which exists to prevent large map file diffs due
|
||||
/// to changing yaml ids.
|
||||
/// </summary>
|
||||
public void ReserveYamlId(EntityUid uid)
|
||||
{
|
||||
if (YamlUidMap.ContainsKey(uid))
|
||||
return;
|
||||
|
||||
if (_yamlQuery.TryGetComponent(uid, out var comp) && comp.Uid > 0 && YamlIds.Add(comp.Uid))
|
||||
{
|
||||
if (Truncated.Contains(uid))
|
||||
{
|
||||
_log.Error(
|
||||
"Including a previously truncated entity within the serialization process? Something probably wrong");
|
||||
}
|
||||
|
||||
YamlUidMap.Add(uid, comp.Uid);
|
||||
}
|
||||
else
|
||||
AllocateYamlUid(uid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITypeSerializer
|
||||
|
||||
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
if (node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
if (!int.TryParse(node.Value, out _))
|
||||
return new ErrorNode(node, "Invalid EntityUid");
|
||||
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
EntityUid value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (YamlUidMap.TryGetValue(value, out var yamlId))
|
||||
return new ValueDataNode(yamlId.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (CurrentComponent == _xformName)
|
||||
{
|
||||
if (value == EntityUid.Invalid)
|
||||
return new ValueDataNode("invalid");
|
||||
|
||||
DebugTools.Assert(!Orphans.Contains(CurrentEntityYamlUid));
|
||||
Orphans.Add(CurrentEntityYamlUid);
|
||||
|
||||
if (Options.ErrorOnOrphan && CurrentEntity != null && value != Truncate)
|
||||
_log.Error($"Serializing entity {EntMan.ToPrettyString(CurrentEntity)} without including its parent {EntMan.ToPrettyString(value)}");
|
||||
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
if (value == EntityUid.Invalid)
|
||||
{
|
||||
if (Options.MissingEntityBehaviour != MissingEntityBehaviour.Ignore)
|
||||
_log.Error($"Encountered an invalid entityUid reference.");
|
||||
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
if (value == Truncate)
|
||||
{
|
||||
_log.Error(
|
||||
$"{EntMan.ToPrettyString(CurrentEntity)}:{CurrentComponent} is attempting to serialize references to a truncated entity {EntMan.ToPrettyString(Truncate)}.");
|
||||
}
|
||||
|
||||
switch (Options.MissingEntityBehaviour)
|
||||
{
|
||||
case MissingEntityBehaviour.Error:
|
||||
_log.Error(EntMan.Deleted(value)
|
||||
? $"Encountered a reference to a deleted entity {value} while serializing {EntMan.ToPrettyString(CurrentEntity)}."
|
||||
: $"Encountered a reference to a missing entity: {value} while serializing {EntMan.ToPrettyString(CurrentEntity)}.");
|
||||
return new ValueDataNode("invalid");
|
||||
case MissingEntityBehaviour.Ignore:
|
||||
return new ValueDataNode("invalid");
|
||||
case MissingEntityBehaviour.IncludeNullspace:
|
||||
if (!EntMan.TryGetComponent(value, out TransformComponent? xform)
|
||||
|| xform.ParentUid != EntityUid.Invalid
|
||||
|| _gridQuery.HasComp(value)
|
||||
|| _mapQuery.HasComp(value))
|
||||
{
|
||||
goto case MissingEntityBehaviour.Error;
|
||||
}
|
||||
goto case MissingEntityBehaviour.AutoInclude;
|
||||
case MissingEntityBehaviour.PartialInclude:
|
||||
case MissingEntityBehaviour.AutoInclude:
|
||||
if (Options.LogAutoInclude is {} level)
|
||||
_log.Log(level, $"Auto-including entity {EntMan.ToPrettyString(value)} referenced by {EntMan.ToPrettyString(CurrentEntity)}");
|
||||
_autoInclude.Add(value);
|
||||
var id = GetYamlUid(value);
|
||||
return new ValueDataNode(id.ToString(CultureInfo.InvariantCulture));
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
EntityUid ITypeReader<EntityUid, ValueDataNode>.Read(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context,
|
||||
ISerializationManager.InstantiationDelegate<EntityUid>? _)
|
||||
{
|
||||
return node.Value == "invalid" ? EntityUid.Invalid : EntityUid.Parse(node.Value);
|
||||
}
|
||||
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
if (!int.TryParse(node.Value, out _))
|
||||
return new ErrorNode(node, "Invalid NetEntity");
|
||||
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
|
||||
public NetEntity Read(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<NetEntity>? instanceProvider = null)
|
||||
{
|
||||
return node.Value == "invalid" ? NetEntity.Invalid : NetEntity.Parse(node.Value);
|
||||
}
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
NetEntity value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
var uid = EntMan.GetEntity(value);
|
||||
return serializationManager.WriteValue(uid, alwaysWrite, context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing information about entities that were loaded from a yaml file.
|
||||
/// </summary>
|
||||
public sealed class LoadResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The file format version.
|
||||
/// </summary>
|
||||
public int Version;
|
||||
|
||||
/// <summary>
|
||||
/// The category of the file that was loaded in.
|
||||
/// This might not match the actual final result. E.g., when loading in a grid file, a map may automatically gets
|
||||
/// generated for it via <see cref="EntityDeserializer.AdoptGrids"/>.
|
||||
/// </summary>
|
||||
public FileCategory Category = FileCategory.Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// The engine version that was used to write the file. See <see cref="CVars.BuildEngineVersion"/>.
|
||||
/// </summary>
|
||||
public string? EngineVersion;
|
||||
|
||||
/// <summary>
|
||||
/// The fork that was used to write the file. See <see cref="CVars.BuildForkId"/>.
|
||||
/// </summary>
|
||||
public string? ForkId;
|
||||
|
||||
/// <summary>
|
||||
/// The fork version that was used to write the file. See <see cref="CVars.BuildVersion"/>.
|
||||
/// </summary>
|
||||
public string? ForkVersion;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DateTime.UtcNow"/> when the file was created.
|
||||
/// </summary>
|
||||
public DateTime? Time;
|
||||
|
||||
/// <summary>
|
||||
/// Set of all entities that were created while the file was being loaded.
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> Entities = new();
|
||||
|
||||
/// <summary>
|
||||
/// Set of entities that are not parented to other entities. This will be a combination of <see cref="Maps"/>,
|
||||
/// <see cref="Orphans"/>, and <see cref="NullspaceEntities"/>.
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> RootNodes = new();
|
||||
|
||||
public readonly HashSet<Entity<MapComponent>> Maps = new();
|
||||
|
||||
public readonly HashSet<Entity<MapGridComponent>> Grids = new();
|
||||
|
||||
/// <summary>
|
||||
/// Deserialized entities that need to be assigned a new parent. These differ from "true" null-space entities.
|
||||
/// E,g, saving a grid without saving the map would make the grid an "orphan".
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> Orphans = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of null-space entities. This contains all entities without a parent that don't have a
|
||||
/// <see cref="MapComponent"/>, and were not listed as orphans
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> NullspaceEntities = new();
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
public record struct SerializationOptions
|
||||
{
|
||||
public static readonly SerializationOptions Default = new();
|
||||
|
||||
/// <summary>
|
||||
/// What to do when serializing the EntityUid of an entity that is not one of entities currently being serialized.
|
||||
/// I.e., What should happen when serializing a map that has entities with components that store references to a
|
||||
/// null-space entity? Note that this does not affect the treatment of <see cref="TransformComponent.ParentUid"/>,
|
||||
/// which will never auto-include parents.
|
||||
/// </summary>
|
||||
public MissingEntityBehaviour MissingEntityBehaviour = MissingEntityBehaviour.IncludeNullspace;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to log an error when serializing an entity without its parent.
|
||||
/// </summary>
|
||||
public bool ErrorOnOrphan = true;
|
||||
|
||||
/// <summary>
|
||||
/// Log level to use when auto-including entities while serializing. Null implies no logs.
|
||||
/// See <see cref="MissingEntityBehaviour"/>.
|
||||
/// </summary>
|
||||
public LogLevel? LogAutoInclude = LogLevel.Info;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the serializer will log an error if it encounters a post map-init entity.
|
||||
/// </summary>
|
||||
public bool ExpectPreInit;
|
||||
|
||||
public FileCategory Category;
|
||||
|
||||
public SerializationOptions()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public record struct DeserializationOptions()
|
||||
{
|
||||
public static readonly DeserializationOptions Default = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true, each loaded entity will get a <see cref="YamlUidComponent"/> that stores the uid that the entity
|
||||
/// had in the yaml file. This is used to maintain consistent entity labelling on subsequent saves.
|
||||
/// </summary>
|
||||
public bool StoreYamlUids = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, all maps that get created while loading this file will get map-initialized.
|
||||
/// </summary>
|
||||
public bool InitializeMaps = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, all maps that get created while loading this file will get paused.
|
||||
/// Note that the converse is not true, paused maps will not get un-paused if this is false.
|
||||
/// Pre-mapinit maps are assumed to be paused.
|
||||
/// </summary>
|
||||
public bool PauseMaps = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to log an error when starting up a grid entity that has no map.
|
||||
/// This usually indicates that someone is attempting to load an incorrect file type (e.g. loading a grid as a map).
|
||||
/// </summary>
|
||||
public bool LogOrphanedGrids = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to log an error when encountering an yaml entity id.
|
||||
/// <see cref="TransformComponent.ParentUid"/> is exempt from this.
|
||||
/// </summary>
|
||||
public bool LogInvalidEntities = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically assign map ids to any deserialized map entities.
|
||||
/// If false, maps need to be manually given ids before entities are initialized.
|
||||
/// </summary>
|
||||
public bool AssignMapids = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Superset of <see cref="EntitySerialization.DeserializationOptions"/> that contain information relevant to loading
|
||||
/// maps & grids, potentially onto other existing maps.
|
||||
/// </summary>
|
||||
public struct MapLoadOptions()
|
||||
{
|
||||
public static readonly MapLoadOptions Default = new();
|
||||
|
||||
/// <summary>
|
||||
/// If specified, all orphaned entities and the children of all loaded maps will be re-parented onto this map.
|
||||
/// I.e., this will merge map contents onto an existing map. This will also cause any maps that get loaded to
|
||||
/// delete themselves after their children have been moved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this option effectively causes <see cref="DeserializationOptions.InitializeMaps"/> and
|
||||
/// <see cref="DeserializationOptions.PauseMaps"/> to have no effect, as the target map is not a map that was
|
||||
/// created by the deserialization.
|
||||
/// </remarks>
|
||||
public MapId? MergeMap = null;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to apply to the position of any loaded entities that are directly parented to a map.
|
||||
/// </summary>
|
||||
public Vector2 Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation to apply to the position & local rotation of any loaded entities that are directly parented to a map.
|
||||
/// </summary>
|
||||
public Angle Rotation;
|
||||
|
||||
/// <summary>
|
||||
/// Options to use when deserializing entities.
|
||||
/// </summary>
|
||||
public DeserializationOptions DeserializationOptions = DeserializationOptions.Default;
|
||||
|
||||
/// <summary>
|
||||
/// When loading a single map, this will attempt to force the map to use the given map id. Generally, it is better
|
||||
/// to allow the map system to auto-allocate a map id, to avoid accidentally re-using an old id.
|
||||
/// </summary>
|
||||
public MapId? ForceMapId;
|
||||
|
||||
/// <summary>
|
||||
/// The expected <see cref="LoadResult.Category"/> for the file currently being read in, at the end of the entity
|
||||
/// creation step. Will log errors if the category doesn't match the expected one (e.g., trying to load a "map" from a file
|
||||
/// that doesn't contain any map entities).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that the effective final category may change by the time the file has fully loaded. E.g., when loading a
|
||||
/// file containing an orphaned grid, a map may be automatically created for the grid, but the category will still
|
||||
/// be <see cref="FileCategory.Grid"/>
|
||||
/// </remarks>
|
||||
public FileCategory? ExpectedCategory;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Upload;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// This enum is used to indicate the type of entity data that was written to a file. The actual format of the file does
|
||||
/// not change, but it helps avoid mistakes like accidentally using a map file when trying to load a single grid.
|
||||
/// </summary>
|
||||
public enum FileCategory : byte
|
||||
{
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// File should contain a single orphaned entity, its children, and maybe some null-space entities.
|
||||
/// </summary>
|
||||
Entity,
|
||||
|
||||
/// <summary>
|
||||
/// File should contain a single grid, its children, and maybe some null-space entities.
|
||||
/// </summary>
|
||||
Grid,
|
||||
|
||||
/// <summary>
|
||||
/// File should contain a single map, its children, and maybe some null-space entities.
|
||||
/// </summary>
|
||||
Map,
|
||||
|
||||
/// <summary>
|
||||
/// File is a full game save, and will likely contain at least one map and a few null-space entities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The file might also contain additional yaml entries for things like prototypes uploaded via
|
||||
/// <see cref="IGamePrototypeLoadManager"/>, and might contain references to additional resources that need to be
|
||||
/// loaded (e.g., files uploaded using <see cref="SharedNetworkResourceManager"/>).
|
||||
/// </remarks>
|
||||
Save,
|
||||
}
|
||||
|
||||
public enum MissingEntityBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Log an error and replace the reference with <see cref="EntityUid.Invalid"/>
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Ignore the reference, replace it with <see cref="EntityUid.Invalid"/>
|
||||
/// </summary>
|
||||
Ignore,
|
||||
|
||||
/// <summary>
|
||||
/// Automatically include & serialize any referenced null-space entities and their children.
|
||||
/// I.e., entities that are not attached to any parent and are not maps. Any non-nullspace entities will result in
|
||||
/// an error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is primarily intended to make it easy to auto-include information carrying null-space entities. E.g., the
|
||||
/// "minds" of players, or entities that represent power or gas networks on a grid. Note that a full game save
|
||||
/// should still try to explicitly include all relevant entities, as this could still easily fail to auto-include
|
||||
/// relevant entities if they are not explicitly referenced in a data-field by some other entity.
|
||||
/// </remarks>
|
||||
IncludeNullspace,
|
||||
|
||||
/// <summary>
|
||||
/// Automatically include & serialize any referenced entity. Note that this means that the missing entity's
|
||||
/// parents will (generally) also be included, however this will not include other children. E.g., if serializing a
|
||||
/// grid that references an entity on the map, this will also cause the map to get serialized, but will not necessarily
|
||||
/// serialize everything on the map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If trying to serialize an entity without its parent (i.e., its parent is truncated via
|
||||
/// <see cref="EntitySerializer.Truncate"/>), this will try to respect that. E.g., if a referenced entity is on the
|
||||
/// same map as a grid that is getting serialized, it should include the entity without including the map.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Note that this might unexpectedly change the <see cref="FileCategory"/>. I.e., trying to serialize a grid might
|
||||
/// accidentally lead to serializing a (partial?) map file.
|
||||
/// </remarks>
|
||||
PartialInclude,
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="PartialInclude"/> that will also automatically include the children of any entities that
|
||||
/// that are automatically included. Note that because auto-inclusion generally needs to include an entity's
|
||||
/// parents, this will include more than just the missing entity's direct children.
|
||||
/// </summary>
|
||||
AutoInclude,
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
// This partial class file contains methods for loading generic entities and grids. Map specific methods are in another
|
||||
// file
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to load entities from a yaml file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
public bool TryLoadGeneric(
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapComponent>>? maps,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
MapLoadOptions? options = null)
|
||||
{
|
||||
grids = null;
|
||||
maps = null;
|
||||
if (!TryLoadGeneric(file, out var data, options))
|
||||
return false;
|
||||
|
||||
maps = data.Maps;
|
||||
grids = data.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load entities from a yaml file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to load.</param>
|
||||
/// <param name="result">Data class containing information about the loaded entities</param>
|
||||
/// <param name="options">Optional Options for configuring loading behaviour.</param>
|
||||
public bool TryLoadGeneric(ResPath file, [NotNullWhen(true)] out LoadResult? result, MapLoadOptions? options = null)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!TryReadFile(file, out var data))
|
||||
return false;
|
||||
|
||||
_stopwatch.Restart();
|
||||
var ev = new BeforeEntityReadEvent();
|
||||
RaiseLocalEvent(ev);
|
||||
|
||||
var opts = options ?? MapLoadOptions.Default;
|
||||
|
||||
// If we are forcing a map id, we cannot auto-assign ids.
|
||||
opts.DeserializationOptions.AssignMapids = opts.ForceMapId == null;
|
||||
|
||||
if (opts.MergeMap is { } targetId && !_mapSystem.MapExists(targetId))
|
||||
throw new Exception($"Target map {targetId} does not exist");
|
||||
|
||||
if (opts.MergeMap != null && opts.ForceMapId != null)
|
||||
throw new Exception($"Invalid combination of MapLoadOptions");
|
||||
|
||||
if (_mapSystem.MapExists(opts.ForceMapId))
|
||||
throw new Exception($"Target map already exists");
|
||||
|
||||
// Using a local deserializer instead of a cached value, both to ensure that we don't accidentally carry over
|
||||
// data from a previous serializations, and because some entities cause other maps/grids to be loaded during
|
||||
// during mapinit.
|
||||
var deserializer = new EntityDeserializer(
|
||||
_dependency,
|
||||
data,
|
||||
opts.DeserializationOptions,
|
||||
ev.RenamedPrototypes,
|
||||
ev.DeletedPrototypes);
|
||||
|
||||
if (!deserializer.TryProcessData())
|
||||
{
|
||||
Log.Debug($"Failed to process entity data in {file}");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
deserializer.CreateEntities();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while creating entities: {e}");
|
||||
Delete(deserializer.Result);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (opts.ExpectedCategory is { } exp && exp != deserializer.Result.Category)
|
||||
{
|
||||
// Did someone try to load a map file as a grid or vice versa?
|
||||
Log.Error($"File does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reparent entities if loading entities onto an existing map.
|
||||
var merged = new HashSet<EntityUid>();
|
||||
MergeMaps(deserializer, opts, merged);
|
||||
|
||||
if (!SetMapId(deserializer, opts))
|
||||
return false;
|
||||
|
||||
// Apply any offsets & rotations specified by the load options
|
||||
ApplyTransform(deserializer, opts);
|
||||
|
||||
try
|
||||
{
|
||||
deserializer.StartEntities();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while starting entities: {e}");
|
||||
Delete(deserializer.Result);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (opts.MergeMap is {} map)
|
||||
MapInitalizeMerged(merged, map);
|
||||
|
||||
result = deserializer.Result;
|
||||
Log.Debug($"Loaded map in {_stopwatch.Elapsed}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a regular (non-map, non-grid) entity from a file.
|
||||
/// The loaded entity will initially be in null-space.
|
||||
/// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadEntity(
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<TransformComponent>? entity,
|
||||
DeserializationOptions? options = null)
|
||||
{
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Entity
|
||||
};
|
||||
|
||||
entity = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Orphans.Count == 1)
|
||||
{
|
||||
var uid = result.Orphans.Single();
|
||||
entity = (uid, Transform(uid));
|
||||
return true;
|
||||
}
|
||||
|
||||
Delete(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a grid entity from a file and parent it to the given map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadGrid(
|
||||
MapId map,
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
MergeMap = map,
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Grid
|
||||
};
|
||||
|
||||
grid = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Grids.Count == 1)
|
||||
{
|
||||
grid = result.Grids.Single();
|
||||
return true;
|
||||
}
|
||||
|
||||
Delete(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ApplyTransform(EntityDeserializer deserializer, MapLoadOptions opts)
|
||||
{
|
||||
if (opts.Rotation == Angle.Zero && opts.Offset == Vector2.Zero)
|
||||
return;
|
||||
|
||||
// If merging onto a single map, the transformation was already applied by SwapRootNode()
|
||||
if (opts.MergeMap != null)
|
||||
return;
|
||||
|
||||
var matrix = Matrix3Helpers.CreateTransform(opts.Offset, opts.Rotation);
|
||||
|
||||
// We want to apply the transforms to all children of any loaded maps. However, we can't just iterate over the
|
||||
// children of loaded maps, as transform component has not yet been initialized. I.e. xform.Children is empty.
|
||||
// Hence we iterate over all entities and check which ones are attached to maps.
|
||||
foreach (var uid in deserializer.Result.Entities)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (!_mapQuery.HasComp(xform.ParentUid))
|
||||
continue;
|
||||
|
||||
// The original comment around this bit of logic was just:
|
||||
// > Smelly
|
||||
// I don't know what sloth meant by that, but I guess applying transforms to grid-maps is a no-no?
|
||||
// Or more generally, loading a mapgrid onto another (potentially non-mapgrid) map is just generally kind of weird.
|
||||
if (_gridQuery.HasComponent(xform.ParentUid))
|
||||
continue;
|
||||
|
||||
var rot = xform.LocalRotation + opts.Rotation;
|
||||
var pos = Vector2.Transform(xform.LocalPosition, matrix);
|
||||
_xform.SetLocalPositionRotation(uid, pos, rot, xform);
|
||||
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
// This partial class file contains methods specific to loading maps
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map.
|
||||
/// If the file does not contain exactly one map, this will return false and delete all loaded entities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMap(
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Map
|
||||
};
|
||||
|
||||
map = null;
|
||||
grids = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Maps.Count == 1)
|
||||
{
|
||||
map = result.Maps.First();
|
||||
grids = result.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
Delete(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map, assign it the given map id.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If possible, it is better to use <see cref="TryLoadMap"/> which automatically assigns a <see cref="MapId"/>.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMapWithId(
|
||||
MapId mapId,
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
map = null;
|
||||
grids = null;
|
||||
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Map
|
||||
};
|
||||
|
||||
if (_mapSystem.MapExists(mapId))
|
||||
throw new Exception($"Target map already exists");
|
||||
|
||||
opts.ForceMapId = mapId;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))
|
||||
return false;
|
||||
|
||||
map = new(uid.Value, comp);
|
||||
grids = result.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map, and merge its children onto another map. After which the
|
||||
/// loaded map gets deleted.
|
||||
/// </summary>
|
||||
public bool TryMergeMap(
|
||||
MapId mapId,
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
grids = null;
|
||||
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Map
|
||||
};
|
||||
|
||||
if (!_mapSystem.MapExists(mapId))
|
||||
throw new Exception($"Target map {mapId} does not exist");
|
||||
|
||||
opts.MergeMap = mapId;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))
|
||||
return false;
|
||||
|
||||
grids = result.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MergeMaps(EntityDeserializer deserializer, MapLoadOptions opts, HashSet<EntityUid> merged)
|
||||
{
|
||||
if (opts.MergeMap is not {} targetId)
|
||||
return;
|
||||
|
||||
if (!_mapSystem.TryGetMap(targetId, out var targetUid))
|
||||
throw new Exception($"Target map {targetId} does not exist");
|
||||
|
||||
deserializer.Result.Category = FileCategory.Unknown;
|
||||
var rotation = opts.Rotation;
|
||||
var matrix = Matrix3Helpers.CreateTransform(opts.Offset, rotation);
|
||||
var target = new Entity<TransformComponent>(targetUid.Value, Transform(targetUid.Value));
|
||||
|
||||
// We want to apply the transforms to all children of any loaded maps. However, we can't just iterate over the
|
||||
// children of loaded maps, as transform component has not yet been initialized. I.e. xform.Children is empty.
|
||||
// Hence we iterate over all entities and check which ones are attached to maps.
|
||||
HashSet<EntityUid> maps = new();
|
||||
HashSet<EntityUid> logged = new();
|
||||
foreach (var uid in deserializer.Result.Entities)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
if (!_mapQuery.HasComp(xform.ParentUid))
|
||||
continue;
|
||||
|
||||
if (_gridQuery.HasComponent(xform.ParentUid) && logged.Add(xform.ParentUid))
|
||||
{
|
||||
Log.Error($"Merging a grid-map onto another map is not supported.");
|
||||
continue;
|
||||
}
|
||||
|
||||
maps.Add(xform.ParentUid);
|
||||
Merge(merged, uid, target, matrix, rotation);
|
||||
}
|
||||
|
||||
deserializer.ToDelete.UnionWith(maps);
|
||||
deserializer.Result.Maps.RemoveWhere(x => maps.Contains(x.Owner));
|
||||
|
||||
foreach (var uid in deserializer.Result.Orphans)
|
||||
{
|
||||
Merge(merged, uid, target, matrix, rotation);
|
||||
}
|
||||
|
||||
deserializer.Result.Orphans.Clear();
|
||||
}
|
||||
|
||||
private void Merge(
|
||||
HashSet<EntityUid> merged,
|
||||
EntityUid uid,
|
||||
Entity<TransformComponent> target,
|
||||
in Matrix3x2 matrix,
|
||||
Angle rotation)
|
||||
{
|
||||
merged.Add(uid);
|
||||
var xform = Transform(uid);
|
||||
var angle = xform.LocalRotation + rotation;
|
||||
var pos = Vector2.Transform(xform.LocalPosition, matrix);
|
||||
var coords = new EntityCoordinates(target.Owner, pos);
|
||||
_xform.SetCoordinates((uid, xform, MetaData(uid)), coords, rotation: angle, newParent: target.Comp);
|
||||
}
|
||||
|
||||
private void MapInitalizeMerged(HashSet<EntityUid> merged, MapId targetId)
|
||||
{
|
||||
// fuck me I hate this map merging bullshit.
|
||||
// loading a map "onto" another map shouldn't need to be supported by the generic map loading methods.
|
||||
// If something needs to do that, it should implement it itself.
|
||||
// AFAIK this only exists for the loadgamemap command?
|
||||
|
||||
if (!_mapSystem.TryGetMap(targetId, out var targetUid))
|
||||
throw new Exception($"Target map {targetId} does not exist");
|
||||
|
||||
if (_mapSystem.IsInitialized(targetUid.Value))
|
||||
{
|
||||
foreach (var uid in merged)
|
||||
{
|
||||
_mapSystem.RecursiveMapInit(uid);
|
||||
}
|
||||
}
|
||||
|
||||
var paused = _mapSystem.IsPaused(targetUid.Value);
|
||||
foreach (var uid in merged)
|
||||
{
|
||||
_mapSystem.RecursiveSetPaused(uid, paused);
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetMapId(EntityDeserializer deserializer, MapLoadOptions opts)
|
||||
{
|
||||
if (opts.ForceMapId is not { } id)
|
||||
return true;
|
||||
|
||||
if (deserializer.Result.Maps.Count != 1)
|
||||
{
|
||||
Log.Error(
|
||||
$"The {nameof(MapLoadOptions.ForceMapId)} option is only supported when loading a file containing a single map.");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
|
||||
var map = deserializer.Result.Maps.Single();
|
||||
_mapSystem.AssignMapId(map, id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
// This partial class file contains methods for serializing and saving entities, grids, and maps.
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <inheritdoc cref="EntitySerializer.OnIsSerializeable"/>
|
||||
public event EntitySerializer.IsSerializableDelegate? OnIsSerializable;
|
||||
|
||||
/// <summary>
|
||||
/// Recursively serialize the given entity and its children.
|
||||
/// </summary>
|
||||
public (MappingDataNode Node, FileCategory Category) SerializeEntitiesRecursive(
|
||||
HashSet<EntityUid> entities,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
if (!entities.All(Exists))
|
||||
throw new Exception($"Cannot serialize deleted entities");
|
||||
|
||||
Log.Info($"Serializing entities: {string.Join(", ", entities.Select(x => ToPrettyString(x).ToString()))}");
|
||||
|
||||
var maps = entities.Select(x => Transform(x).MapID).ToHashSet();
|
||||
var ev = new BeforeSerializationEvent(entities, maps);
|
||||
RaiseLocalEvent(ev);
|
||||
|
||||
// In case no options were provided, we assume that if all of the starting entities are pre-init, we should
|
||||
// expect that **all** entities that get serialized should be pre-init.
|
||||
var opts = options ?? SerializationOptions.Default with
|
||||
{
|
||||
ExpectPreInit = (entities.All(x => LifeStage(x) < EntityLifeStage.MapInitialized))
|
||||
};
|
||||
|
||||
var serializer = new EntitySerializer(_dependency, opts);
|
||||
serializer.OnIsSerializeable += OnIsSerializable;
|
||||
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
serializer.SerializeEntityRecursive(ent);
|
||||
}
|
||||
|
||||
var data = serializer.Write();
|
||||
var cat = serializer.GetCategory();
|
||||
|
||||
var ev2 = new AfterSerializationEvent(entities, data, cat);
|
||||
RaiseLocalEvent(ev2);
|
||||
|
||||
Log.Debug($"Serialized {serializer.EntityData.Count} entities in {_stopwatch.Elapsed}");
|
||||
return (data, cat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a
|
||||
/// yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveEntity(EntityUid entity, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (_mapQuery.HasComp(entity))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(entity)} is a map. Use {nameof(TrySaveMap)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_gridQuery.HasComp(entity))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(entity)} is a grid. Use {nameof(TrySaveGrid)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
opts.Category = FileCategory.Entity;
|
||||
|
||||
MappingDataNode data;
|
||||
FileCategory cat;
|
||||
try
|
||||
{
|
||||
(data, cat) = SerializeEntitiesRecursive([entity], opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize entity {ToPrettyString(entity)}:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cat != FileCategory.Entity)
|
||||
{
|
||||
Log.Error($"Failed to save {ToPrettyString(entity)} as a singular entity. Output: {cat}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a map and all of its children and write the result to a yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveMap(MapId mapId, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
return TrySaveMap(mapUid.Value, path, options);
|
||||
|
||||
Log.Error($"Unable to find map {mapId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a map and all of its children and write the result to a yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveMap(EntityUid map, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (!_mapQuery.HasComp(map))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(map)} is not a map.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
opts.Category = FileCategory.Map;
|
||||
|
||||
MappingDataNode data;
|
||||
FileCategory cat;
|
||||
try
|
||||
{
|
||||
(data, cat) = SerializeEntitiesRecursive([map], opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize map {ToPrettyString(map)}:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cat != FileCategory.Map)
|
||||
{
|
||||
Log.Error($"Failed to save {ToPrettyString(map)} as a map. Output: {cat}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a grid and all of its children and write the result to a yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveGrid(EntityUid grid, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (!_gridQuery.HasComp(grid))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(grid)} is not a grid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_mapQuery.HasComp(grid))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(grid)} is a map, not (just) a grid. Use {nameof(TrySaveMap)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
opts.Category = FileCategory.Grid;
|
||||
|
||||
MappingDataNode data;
|
||||
FileCategory cat;
|
||||
try
|
||||
{
|
||||
(data, cat) = SerializeEntitiesRecursive([grid], opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize grid {ToPrettyString(grid)}:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cat != FileCategory.Grid)
|
||||
{
|
||||
Log.Error($"Failed to save {ToPrettyString(grid)} as a grid. Output: {cat}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an entities and all of their children to a yaml file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
EntityUid uid,
|
||||
ResPath path,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
return TrySaveGeneric([uid], path, out category, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize one or more entities and all of their children to a yaml file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
HashSet<EntityUid> entities,
|
||||
ResPath path,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
category = FileCategory.Unknown;
|
||||
if (entities.Count == 0)
|
||||
return false;
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
|
||||
MappingDataNode data;
|
||||
try
|
||||
{
|
||||
(data, category) = SerializeEntitiesRecursive(entities, opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize entities:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides methods for saving and loading maps and grids.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The save & load methods are basically wrappers around <see cref="EntitySerializer"/> and
|
||||
/// <see cref="EntityDeserializer"/>, which can be used for more control over serialization.
|
||||
/// </remarks>
|
||||
public sealed partial class MapLoaderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependency = default!;
|
||||
|
||||
private Stopwatch _stopwatch = new();
|
||||
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_mapQuery = GetEntityQuery<MapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
}
|
||||
|
||||
private void Write(ResPath path, MappingDataNode data)
|
||||
{
|
||||
Log.Info($"Saving serialized results to {path}");
|
||||
path = path.ToRootedPath();
|
||||
var document = new YamlDocument(data.ToYaml());
|
||||
using var writer = _resourceManager.UserData.OpenWriteText(path);
|
||||
{
|
||||
var stream = new YamlStream {document};
|
||||
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryReadFile(ResPath file, [NotNullWhen(true)] out MappingDataNode? data)
|
||||
{
|
||||
var resPath = file.ToRootedPath();
|
||||
data = null;
|
||||
|
||||
if (!TryGetReader(resPath, out var reader))
|
||||
return false;
|
||||
|
||||
Log.Info($"Loading file: {resPath}");
|
||||
_stopwatch.Restart();
|
||||
|
||||
using var textReader = reader;
|
||||
var documents = DataNodeParser.ParseYamlStream(reader).ToArray();
|
||||
Log.Debug($"Loaded yml stream in {_stopwatch.Elapsed}");
|
||||
|
||||
// Yes, logging errors in a "try" method is kinda shit, but it was throwing exceptions when I found it and it does
|
||||
// make sense to at least provide some kind of feedback for why it failed.
|
||||
switch (documents.Length)
|
||||
{
|
||||
case < 1:
|
||||
Log.Error("Stream has no YAML documents.");
|
||||
return false;
|
||||
case > 1:
|
||||
Log.Error("Stream too many YAML documents. Map files store exactly one.");
|
||||
return false;
|
||||
default:
|
||||
data = (MappingDataNode) documents[0].Root;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetReader(ResPath resPath, [NotNullWhen(true)] out TextReader? reader)
|
||||
{
|
||||
if (_resourceManager.UserData.Exists(resPath))
|
||||
{
|
||||
// Log warning if file exists in both user and content data.
|
||||
if (_resourceManager.ContentFileExists(resPath))
|
||||
Log.Warning("Reading map user data instead of content");
|
||||
|
||||
reader = _resourceManager.UserData.OpenText(resPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_resourceManager.TryContentFileRead(resPath, out var contentReader))
|
||||
{
|
||||
reader = new StreamReader(contentReader);
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Error($"File not found: {resPath}");
|
||||
reader = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for deleting all loaded entities.
|
||||
/// </summary>
|
||||
public void Delete(LoadResult result)
|
||||
{
|
||||
foreach (var uid in result.Maps)
|
||||
{
|
||||
Del(uid);
|
||||
}
|
||||
|
||||
foreach (var uid in result.Orphans)
|
||||
{
|
||||
Del(uid);
|
||||
}
|
||||
|
||||
foreach (var uid in result.Entities)
|
||||
{
|
||||
Del(uid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,11 +54,6 @@ namespace Robust.Shared.Enums
|
||||
/// <summary>
|
||||
/// Overlay will be rendered below grids, entities, and everything else. In world space.
|
||||
/// </summary>
|
||||
WorldSpaceBelowWorld = 1 << 8,
|
||||
|
||||
/// <summary>
|
||||
/// Called after GLClear but before FOV applied to the lighting buffer.
|
||||
/// </summary>
|
||||
BeforeLighting = 1 << 9,
|
||||
WorldSpaceBelowWorld = 1 << 8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public abstract class BoundUserInterface : IDisposable
|
||||
{
|
||||
[Dependency] protected internal readonly IEntityManager EntMan = default!;
|
||||
[Dependency] protected readonly IEntityManager EntMan = default!;
|
||||
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
|
||||
protected readonly SharedUserInterfaceSystem UiSystem;
|
||||
|
||||
public bool IsOpened { get; protected set; }
|
||||
|
||||
public readonly Enum UiKey;
|
||||
public EntityUid Owner { get; }
|
||||
|
||||
@@ -30,6 +28,13 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
protected internal BoundUserInterfaceState? State { get; internal set; }
|
||||
|
||||
// Bandaid just for storage :)
|
||||
/// <summary>
|
||||
/// Defers state handling
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public virtual bool DeferredClose { get; } = true;
|
||||
|
||||
protected BoundUserInterface(EntityUid owner, Enum uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -43,13 +48,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// Invoked when the UI is opened.
|
||||
/// Do all creation and opening of things like windows in here.
|
||||
/// </summary>
|
||||
[MustCallBase]
|
||||
protected internal virtual void Open()
|
||||
{
|
||||
if (IsOpened)
|
||||
return;
|
||||
|
||||
IsOpened = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,10 +100,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (!IsOpened)
|
||||
return;
|
||||
|
||||
IsOpened = false;
|
||||
UiSystem.CloseUi(Owner, UiKey, PlayerManager.LocalEntity, predicted: true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -39,14 +38,11 @@ public abstract partial class EntityManager
|
||||
Dirty(uid, comp, metadata);
|
||||
}
|
||||
|
||||
public virtual void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
|
||||
public void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
|
||||
|
||||
// TODO
|
||||
// consider storing this on MetaDataComponent?
|
||||
// We alsready store other dirtying information there anyways, and avoids having to fetch the registration.
|
||||
if (!compReg.NetworkedFieldLookup.TryGetValue(fieldName, out var idx))
|
||||
{
|
||||
_sawmill.Error($"Tried to dirty delta field {fieldName} on {ToPrettyString(uid)} that isn't implemented.");
|
||||
@@ -58,24 +54,6 @@ public abstract partial class EntityManager
|
||||
comp.LastModifiedFields[idx] = curTick;
|
||||
Dirty(uid, comp, metadata);
|
||||
}
|
||||
|
||||
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
|
||||
|
||||
var curTick = _gameTiming.CurTick;
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (!compReg.NetworkedFieldLookup.TryGetValue(field, out var idx))
|
||||
_sawmill.Error($"Tried to dirty delta field {field} on {ToPrettyString(uid)} that isn't implemented.");
|
||||
else
|
||||
comp.LastModifiedFields[idx] = curTick;
|
||||
}
|
||||
|
||||
comp.LastFieldUpdate = curTick;
|
||||
Dirty(uid, comp, meta);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -197,23 +197,17 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var reg = _componentFactory.GetRegistration(name);
|
||||
|
||||
if (removeExisting)
|
||||
if (HasComponent(target, reg.Type))
|
||||
{
|
||||
var comp = _componentFactory.GetComponent(reg);
|
||||
_serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
|
||||
AddComponentInternal(target, comp, reg, overwrite: true, metadata: metadata);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HasComponent(target, reg))
|
||||
{
|
||||
if (!removeExisting)
|
||||
continue;
|
||||
}
|
||||
|
||||
var comp = _componentFactory.GetComponent(reg);
|
||||
_serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
|
||||
AddComponentInternal(target, comp, reg, overwrite: false, metadata: metadata);
|
||||
RemoveComponent(target, reg.Type, metadata);
|
||||
}
|
||||
|
||||
var comp = _componentFactory.GetComponent(reg);
|
||||
_serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
|
||||
AddComponent(target, comp, metadata: metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,22 +315,6 @@ namespace Robust.Shared.GameObjects
|
||||
AddComponentInternal(uid, component, overwrite, false, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(
|
||||
EntityUid uid,
|
||||
T component,
|
||||
ComponentRegistration compReg,
|
||||
bool overwrite = false,
|
||||
MetaDataComponent? metadata = null) where T : IComponent
|
||||
{
|
||||
if (!MetaQuery.Resolve(uid, ref metadata, false))
|
||||
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
|
||||
|
||||
DebugTools.Assert(component.Owner == default);
|
||||
component.Owner = uid;
|
||||
|
||||
AddComponentInternal(uid, component, compReg, overwrite, skipInit: false, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent
|
||||
{
|
||||
if (!MetaQuery.ResolveInternal(uid, ref metadata, false))
|
||||
@@ -753,14 +731,6 @@ namespace Robust.Shared.GameObjects
|
||||
return uid.HasValue && HasComponent<T>(uid.Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool HasComponent(EntityUid uid, ComponentRegistration reg)
|
||||
{
|
||||
var dict = _entTraitArray[reg.Idx.Value];
|
||||
return dict.TryGetValue(uid, out var comp) && !comp.Deleted;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
@@ -973,23 +943,6 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
var dict = _entTraitArray[reg.Idx.Value];
|
||||
if (dict.TryGetValue(uid, out var comp))
|
||||
{
|
||||
if (!comp.Deleted)
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
@@ -1741,6 +1694,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
|
||||
{
|
||||
if (component != null)
|
||||
@@ -1763,7 +1717,7 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
|
||||
public bool Resolve(ref Entity<TComp1?> entity, bool logMissing = true)
|
||||
{
|
||||
return Resolve(entity.Owner, ref entity.Comp, logMissing);
|
||||
|
||||
@@ -311,16 +311,11 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
|
||||
{
|
||||
return CreateEntity(prototypeName, out _, overrides);
|
||||
}
|
||||
|
||||
public EntityUid CreateEntityUninitialized(string? prototypeName, out MetaDataComponent meta, ComponentRegistry? overrides = null)
|
||||
{
|
||||
return CreateEntity(prototypeName, out meta, overrides);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
|
||||
{
|
||||
@@ -553,28 +548,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
TransformComponent? parentXform = null;
|
||||
if (xform.ParentUid.IsValid())
|
||||
{
|
||||
if (xform.LifeStage < ComponentLifeStage.Initialized)
|
||||
{
|
||||
// Entity is being deleted before initialization ever finished.
|
||||
// The entity will not yet have been added to the parent's transform component.
|
||||
// This is seemingly pretty error prone ATM, and I'm not even sure if it should be supported?
|
||||
|
||||
// Just in case it HAS somehow been added, make sure we remove it.
|
||||
if (TransformQuery.TryComp(xform.ParentUid, out parentXform) && parentXform._children.Remove(e))
|
||||
DebugTools.Assert($"Child entity {ToPrettyString(e)} was added to the parent's child set prior to being initialized?");
|
||||
|
||||
parentXform = null;
|
||||
xform._parent = EntityUid.Invalid;
|
||||
xform._anchored = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use resolve for automatic error logging.
|
||||
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
|
||||
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
|
||||
}
|
||||
}
|
||||
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
|
||||
|
||||
// Then actually delete them
|
||||
RecursiveDeleteEntity(e, meta, xform, parentXform);
|
||||
@@ -809,7 +783,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Allocates an entity and stores it but does not load components or do initialization.
|
||||
/// </summary>
|
||||
protected internal EntityUid AllocEntity(
|
||||
private protected EntityUid AllocEntity(
|
||||
EntityPrototype? prototype,
|
||||
out MetaDataComponent metadata)
|
||||
{
|
||||
@@ -819,9 +793,6 @@ namespace Robust.Shared.GameObjects
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AllocEntity(Robust.Shared.Prototypes.EntityPrototype?,out Robust.Shared.GameObjects.MetaDataComponent)"/>
|
||||
internal EntityUid AllocEntity(EntityPrototype? prototype) => AllocEntity(prototype, out _);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates an entity and stores it but does not load components or do initialization.
|
||||
/// </summary>
|
||||
@@ -901,9 +872,21 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
|
||||
{
|
||||
EntityPrototype.LoadEntity((entity, MetaQuery.GetComponent(entity)), ComponentFactory, this, _serManager, context);
|
||||
}
|
||||
|
||||
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
|
||||
{
|
||||
var meta = MetaQuery.GetComponent(entity);
|
||||
DebugTools.Assert(meta.EntityPrototype == prototype);
|
||||
EntityPrototype.LoadEntity((entity, meta), ComponentFactory, this, _serManager, context);
|
||||
}
|
||||
|
||||
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
|
||||
{
|
||||
var doMapInit = _mapSystem.IsInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
|
||||
var doMapInit = _mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
|
||||
InitializeAndStartEntity(entity, doMapInit);
|
||||
}
|
||||
|
||||
|
||||
@@ -171,13 +171,6 @@ public partial class EntitySystem
|
||||
EntityManager.DirtyField(uid, component, fieldName, meta);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
EntityManager.DirtyFields(uid, comp, meta);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,11 +22,11 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntitySystemManager : IEntitySystemManager, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ProfManager _profManager = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[IoC.Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[IoC.Dependency] private readonly ProfManager _profManager = default!;
|
||||
[IoC.Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[IoC.Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
@@ -35,18 +35,6 @@ namespace Robust.Shared.GameObjects
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
internal DependencyCollection SystemDependencyCollection = default!;
|
||||
|
||||
public IDependencyCollection DependencyCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_initialized)
|
||||
return SystemDependencyCollection;
|
||||
|
||||
throw new InvalidOperationException($"{nameof(EntitySystemManager)} has not been initialized.");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Type> _systemTypes = new();
|
||||
|
||||
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
|
||||
|
||||
@@ -176,14 +176,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>True if the entity has the component type, otherwise false.</returns>
|
||||
bool HasComponent<T>([NotNullWhen(true)] EntityUid? uid) where T : IComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component type.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="reg">The component registration to check for.</param>
|
||||
/// <returns>True if the entity has the component type, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid uid, ComponentRegistration reg);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component type.
|
||||
/// </summary>
|
||||
@@ -302,15 +294,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component) where T : IComponent?;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="reg">The component registration to check for.</param>
|
||||
/// <param name="component">Component of the specified type (if exists).</param>
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -132,10 +131,5 @@ namespace Robust.Shared.GameObjects
|
||||
IEnumerable<Type> GetEntitySystemTypes();
|
||||
bool TryGetEntitySystem(Type sysType, [NotNullWhen(true)] out object? system);
|
||||
object GetEntitySystem(Type sysType);
|
||||
|
||||
/// <summary>
|
||||
/// Dependency collection that contains all the loaded systems.
|
||||
/// </summary>
|
||||
public IDependencyCollection DependencyCollection { get; }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user