mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
28 Commits
v239.0.2
...
serializat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
190c35dbdf | ||
|
|
b52c26d0eb | ||
|
|
e03aec47ef | ||
|
|
35c983d2fd | ||
|
|
88cfd04f01 | ||
|
|
ee906af16e | ||
|
|
e205ae3627 | ||
|
|
d818c5aa0c | ||
|
|
aa8fe8ac92 | ||
|
|
4ba6687b9d | ||
|
|
8f2817aa4e | ||
|
|
89c7839fe2 | ||
|
|
9799132001 | ||
|
|
34ffa56c57 | ||
|
|
f8410a4674 | ||
|
|
43b991c690 | ||
|
|
c463fc5e78 | ||
|
|
e21b3e069a | ||
|
|
c8f94ab40d | ||
|
|
2a882b5555 | ||
|
|
32d8a1cba9 | ||
|
|
5d84be9c78 | ||
|
|
eaaa70437a | ||
|
|
448e8b0c2c | ||
|
|
42948d8f8e | ||
|
|
039468f4b6 | ||
|
|
6a3f88b1c6 | ||
|
|
a314c5f797 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,70 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 240.1.3-source-gen-debug
|
||||
|
||||
|
||||
## 240.1.2-source-gen-debug
|
||||
|
||||
|
||||
## 240.1.2
|
||||
|
||||
|
||||
## 240.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed one of the `IOverlayManager.RemoveOverlay` overrides not fully removing the overlay.
|
||||
|
||||
|
||||
## 240.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added an `AsNullable` extension method for converting an `Entity<T>` into an `Entity<T?>`
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception in `PhysicsSystem.DestroyContacts()` that could result in entities getting stuck with broken physics.
|
||||
|
||||
### Other
|
||||
|
||||
* `GamePrototypeLoadManager` will now send all uploaded prototypes to connecting players in a single `GamePrototypeLoadMessage`, as opposed to one message per upload.
|
||||
|
||||
|
||||
## 240.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `SharedBroadphaseSystem.GetBroadphases()` not returning the map itself, which was causing physics to not work properly off-grid.
|
||||
|
||||
|
||||
## 240.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `ComponentRegistry` no longer implements `ISerializationContext`
|
||||
* Tickrate values are now `ushort`, allowing them to go up to 65535.
|
||||
|
||||
### New features
|
||||
|
||||
* Console completion options now have new flags for preventing suggestions from being escaped or quoted.
|
||||
* Added `ILocalizationManager.HasCulture()`.
|
||||
* Static `EntProtoId<T>` fields are now validated to exist.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a state handling bug in replays, which was causing exceptions to be thrown when applying delta states.
|
||||
|
||||
### Other
|
||||
|
||||
* Reduced amount of `DynamicMethod`s used by serialization system. This should improve performance somewhat.
|
||||
|
||||
### Internal
|
||||
|
||||
* Avoided sorting overlays every render frame.
|
||||
* Various clean up to grid fixture code/adding asserts.
|
||||
|
||||
## 239.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -66,6 +66,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -106,6 +107,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -147,6 +149,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -188,6 +191,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
|
||||
@@ -88,10 +88,10 @@ namespace Robust.Client
|
||||
{
|
||||
if (GameInfo != null)
|
||||
{
|
||||
GameInfo.TickRate = (byte) tickrate;
|
||||
GameInfo.TickRate = (ushort) tickrate;
|
||||
}
|
||||
|
||||
_timing.SetTickRateAt((byte) tickrate, info.TickChanged);
|
||||
_timing.SetTickRateAt((ushort) tickrate, info.TickChanged);
|
||||
_logger.Info($"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
|
||||
}
|
||||
|
||||
@@ -395,6 +395,6 @@ namespace Robust.Client
|
||||
/// </summary>
|
||||
public int ServerMaxPlayers { get; set; }
|
||||
|
||||
public byte TickRate { get; internal set; }
|
||||
public uint TickRate { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected override void OnAppearanceGetState(EntityUid uid, AppearanceComponent component, ref ComponentGetState args)
|
||||
{
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
var clone = CloneAppearanceData(component.AppearanceData);
|
||||
args.State = new AppearanceComponentState(clone);
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("MergeImplicitData"))
|
||||
{
|
||||
MergeImplicitData(createdEntities);
|
||||
GenerateImplicitStates(createdEntities);
|
||||
}
|
||||
|
||||
if (_lastProcessedInput < curState.LastProcessedInput)
|
||||
@@ -671,7 +671,7 @@ 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>
|
||||
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
|
||||
public void GenerateImplicitStates(IEnumerable<NetEntity> createdEntities)
|
||||
{
|
||||
var bus = _entityManager.EventBus;
|
||||
|
||||
|
||||
@@ -82,6 +82,12 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
|
||||
@@ -215,8 +215,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
_overlays.Sort(OverlayComparer.Instance);
|
||||
|
||||
return _overlays;
|
||||
}
|
||||
|
||||
@@ -574,17 +572,5 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
return new Box2Rotated(aabb, rotation, aabb.Center);
|
||||
}
|
||||
|
||||
private sealed class OverlayComparer : IComparer<Overlay>
|
||||
{
|
||||
public static readonly OverlayComparer Instance = new();
|
||||
|
||||
public int Compare(Overlay? x, Overlay? y)
|
||||
{
|
||||
var zX = x?.ZIndex ?? 0;
|
||||
var zY = y?.ZIndex ?? 0;
|
||||
return zX.CompareTo(zY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
baseline += new Vector2(metrics.Value.BearingX, -metrics.Value.BearingY);
|
||||
if(handle is DrawingHandleWorld worldhandle)
|
||||
worldhandle.DrawTextureRect(texture, Box2.FromDimensions(baseline, texture.Size));
|
||||
worldhandle.DrawTextureRect(texture, Box2.FromDimensions(baseline, texture.Size), color);
|
||||
else
|
||||
handle.DrawTexture(texture, baseline, color);
|
||||
return metrics.Value.Advance;
|
||||
|
||||
@@ -13,10 +13,22 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list that duplicates a value from <see cref="_overlays"/>,
|
||||
/// but already sorted, by invoking <see cref="Sort"/>
|
||||
/// in <see cref="AddOverlay"/> and <see cref="RemoveOverlay(System.Type)"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private readonly List<Overlay> _sortedOverlays = [];
|
||||
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
|
||||
/// <summary>
|
||||
/// Returns a list of all overlays sorted by <see cref="Overlay.ZIndex"/>
|
||||
/// </summary>
|
||||
public IEnumerable<Overlay> AllOverlays => _sortedOverlays;
|
||||
|
||||
public void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
@@ -28,9 +40,10 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
|
||||
public bool AddOverlay(Overlay overlay)
|
||||
{
|
||||
if (_overlays.ContainsKey(overlay.GetType()))
|
||||
if (!_overlays.TryAdd(overlay.GetType(), overlay))
|
||||
return false;
|
||||
_overlays.Add(overlay.GetType(), overlay);
|
||||
|
||||
Sort();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -42,7 +55,9 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
return false;
|
||||
}
|
||||
|
||||
return _overlays.Remove(overlayClass);
|
||||
var result = _overlays.Remove(overlayClass);
|
||||
Sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool RemoveOverlay<T>() where T : Overlay
|
||||
@@ -52,7 +67,7 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
|
||||
public bool RemoveOverlay(Overlay overlay)
|
||||
{
|
||||
return _overlays.Remove(overlay.GetType());
|
||||
return RemoveOverlay(overlay.GetType());
|
||||
}
|
||||
|
||||
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
|
||||
@@ -104,8 +119,27 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
return _overlays.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
_sortedOverlays.Clear();
|
||||
_sortedOverlays.AddRange(_overlays.Values);
|
||||
_sortedOverlays.Sort(OverlayComparer.Instance);
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_logger = _logMan.GetSawmill("overlay");
|
||||
}
|
||||
|
||||
private sealed class OverlayComparer : IComparer<Overlay>
|
||||
{
|
||||
public static readonly OverlayComparer Instance = new();
|
||||
|
||||
public int Compare(Overlay? x, Overlay? y)
|
||||
{
|
||||
var zX = x?.ZIndex ?? 0;
|
||||
var zY = y?.ZIndex ?? 0;
|
||||
return zX.CompareTo(zY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +95,21 @@ public sealed partial class ReplayLoadManager
|
||||
_gameState.ClearDetachQueue();
|
||||
_gameState.ApplyGameState(checkpoint.State, next);
|
||||
|
||||
// Sort entities to ensure that we initialize parents before children
|
||||
var sorted = new List<EntityUid>(entities.Count);
|
||||
var added = new HashSet<EntityUid>(entities.Count);
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
AddSorted(uid, sorted, added, xformQuery);
|
||||
}
|
||||
DebugTools.AssertEqual(sorted.Count, entities.Count);
|
||||
DebugTools.AssertEqual(added.Count, entities.Count);
|
||||
await callback(i, total, LoadingState.Initializing, false);
|
||||
|
||||
i = 0;
|
||||
var query = _entMan.GetEntityQuery<MetaDataComponent>();
|
||||
foreach (var uid in entities)
|
||||
foreach (var uid in sorted)
|
||||
{
|
||||
_entMan.InitializeEntity(uid, query.GetComponent(uid));
|
||||
if (i++ % 50 == 0)
|
||||
@@ -109,7 +121,7 @@ public sealed partial class ReplayLoadManager
|
||||
|
||||
i = 0;
|
||||
await callback(0, total, LoadingState.Starting, true);
|
||||
foreach (var uid in entities)
|
||||
foreach (var uid in sorted)
|
||||
{
|
||||
_entMan.StartEntity(uid);
|
||||
if (i++ % 50 == 0)
|
||||
@@ -132,4 +144,16 @@ public sealed partial class ReplayLoadManager
|
||||
_replayPlayback.StartReplay(data);
|
||||
_timing.Paused = false;
|
||||
}
|
||||
|
||||
private void AddSorted(EntityUid uid, List<EntityUid> sortedList, HashSet<EntityUid> added, EntityQuery<TransformComponent> query)
|
||||
{
|
||||
if (!added.Add(uid))
|
||||
return;
|
||||
|
||||
var parent = query.Comp(uid).ParentUid;
|
||||
if (parent != EntityUid.Invalid)
|
||||
AddSorted(parent, sortedList, added, query);
|
||||
|
||||
sortedList.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,8 @@ internal sealed partial class ReplayPlaybackManager
|
||||
_gameState.UpdateFullRep(state, cloneDelta: true);
|
||||
var next = Replay.NextState;
|
||||
BeforeApplyState?.Invoke((state, next));
|
||||
_gameState.ApplyGameState(state, next);
|
||||
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;
|
||||
|
||||
@@ -220,7 +220,7 @@ internal partial class UserInterfaceManager
|
||||
continue;
|
||||
|
||||
var typeDict = _assignerRegistry.GetOrNew(fieldInfo.FieldType);
|
||||
var assigner = (InternalReflectionUtils.AssignField<UIController, object?>)InternalReflectionUtils.EmitFieldAssigner<UIController>(backingField, true);
|
||||
var assigner = (InternalReflectionUtils.AssignField<UIController, object?>)InternalReflectionUtils.EmitFieldAssigner(typeof(UIController), backingField, true);
|
||||
typeDict.Add(controllerType, assigner);
|
||||
}
|
||||
|
||||
|
||||
@@ -276,10 +276,14 @@ public sealed partial class DebugConsole
|
||||
// This means that letter casing will match the completion suggestion.
|
||||
CommandBar.CursorPosition = lastRange.end;
|
||||
CommandBar.SelectionStart = lastRange.start;
|
||||
var insertValue = CommandParsing.Escape(completion);
|
||||
|
||||
var insertValue = (completionFlags & CompletionOptionFlags.NoEscape) == 0
|
||||
? CommandParsing.Escape(completion)
|
||||
: completion;
|
||||
|
||||
// If the replacement contains a space, we must quote it to treat it as a single argument.
|
||||
var mustQuote = insertValue.Contains(' ');
|
||||
var mustQuote = (completionFlags & CompletionOptionFlags.NoQuote) == 0 && insertValue.Contains(' ');
|
||||
|
||||
if ((completionFlags & CompletionOptionFlags.PartialCompletion) == 0)
|
||||
{
|
||||
if (mustQuote)
|
||||
|
||||
@@ -153,6 +153,7 @@ public sealed class ComponentPauseGenerator : IIncrementalGenerator
|
||||
|
||||
builder.AppendLine($$"""
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class {{info.PartialTypeInfo.Name}}_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -586,7 +586,7 @@ namespace Robust.Server
|
||||
{
|
||||
_config.OnValueChanged(CVars.NetTickrate, i =>
|
||||
{
|
||||
var b = (byte) i;
|
||||
var b = (ushort) i;
|
||||
_time.TickRate = b;
|
||||
|
||||
_logger.Info($"Tickrate changed to: {b} on tick {_time.CurTick}");
|
||||
@@ -594,7 +594,7 @@ namespace Robust.Server
|
||||
|
||||
var startOffset = TimeSpan.FromSeconds(_config.GetCVar(CVars.NetTimeStartOffset));
|
||||
_time.TimeBase = (startOffset, GameTick.First);
|
||||
_time.TickRate = (byte) _config.GetCVar(CVars.NetTickrate);
|
||||
_time.TickRate = (ushort) _config.GetCVar(CVars.NetTickrate);
|
||||
|
||||
_logger.Info($"Name: {ServerName}");
|
||||
_logger.Info($"TickRate: {_time.TickRate}({_time.TickPeriod.TotalMilliseconds:0.00}ms)");
|
||||
|
||||
@@ -336,6 +336,7 @@ namespace Robust.Server.ServerStatus
|
||||
{
|
||||
RespondShared();
|
||||
|
||||
_context.Response.StatusCode = (int)code;
|
||||
_context.Response.ContentType = "application/json";
|
||||
|
||||
await JsonSerializer.SerializeAsync(_context.Response.OutputStream, jsonData);
|
||||
|
||||
@@ -51,13 +51,10 @@ public sealed class GamePrototypeLoadManager : SharedPrototypeLoadManager
|
||||
internal void SendToNewUser(INetChannel channel)
|
||||
{
|
||||
// Just dump all the prototypes on connect, before them missing could be an issue.
|
||||
foreach (var prototype in LoadedPrototypes)
|
||||
var msg = new GamePrototypeLoadMessage
|
||||
{
|
||||
var msg = new GamePrototypeLoadMessage
|
||||
{
|
||||
PrototypeData = prototype
|
||||
};
|
||||
channel.SendMessage(msg);
|
||||
}
|
||||
PrototypeData = string.Join("\n\n", LoadedPrototypes)
|
||||
};
|
||||
channel.SendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,8 +346,8 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
|
||||
if (value is GlobalEntityUidName or GlobalNullableEntityUidName)
|
||||
{
|
||||
networkedType = $"Dictionary<{key}, {value}>";
|
||||
value = valueNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
|
||||
networkedType = $"Dictionary<{key}, {value}>";
|
||||
|
||||
stateFields.Append($@"
|
||||
public {networkedType} {name} = default!;");
|
||||
@@ -575,12 +575,14 @@ public partial class {componentName}{deltaInterface}
|
||||
{deltaCompFields}
|
||||
|
||||
[System.Serializable, NetSerializable]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class {stateName} : IComponentState
|
||||
{{{stateFields}
|
||||
{cloneMethod}
|
||||
}}
|
||||
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class {componentName}_AutoNetworkSystem : EntitySystem
|
||||
{{
|
||||
public override void Initialize()
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class CommandBuffer
|
||||
_commandBuffer.AddFirst(command);
|
||||
}
|
||||
|
||||
public void Tick(byte tickRate)
|
||||
public void Tick(ushort tickRate)
|
||||
{
|
||||
_tickrate = tickRate;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
@@ -74,4 +75,14 @@ public enum CompletionOptionFlags
|
||||
/// (instead of adding a space to go to the next one).
|
||||
/// </summary>
|
||||
PartialCompletion = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Prevents suggestions containing spaces from being automatically wrapped in quotes.
|
||||
/// </summary>
|
||||
NoQuote = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Prevents suggestions from being escaped using <see cref="CommandParsing.Escape"/>.
|
||||
/// </summary>
|
||||
NoEscape = 1 << 2,
|
||||
}
|
||||
|
||||
@@ -467,6 +467,8 @@ Types:
|
||||
IComponent: { All: True }
|
||||
IContainer: { All: True }
|
||||
ITypeDescriptorContext: { All: True }
|
||||
EditorBrowsableAttribute: { All: True }
|
||||
EditorBrowsableState: { All: True }
|
||||
System.Diagnostics.CodeAnalysis:
|
||||
AllowNullAttribute: { All: True }
|
||||
DisallowNullAttribute: { All: True }
|
||||
|
||||
@@ -40,17 +40,17 @@ public interface IComponentDeltaState<TState> : IComponentDeltaState where TStat
|
||||
|
||||
void IComponentDeltaState.ApplyToFullState(IComponentState fullState)
|
||||
{
|
||||
if (fullState is TState state)
|
||||
ApplyToFullState(state);
|
||||
else
|
||||
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
|
||||
if (fullState is not TState state)
|
||||
throw new Exception($"Unexpected type. Expected {typeof(TState).Name} but got {fullState.GetType().Name}");
|
||||
|
||||
ApplyToFullState(state);
|
||||
}
|
||||
|
||||
IComponentState IComponentDeltaState.CreateNewFullState(IComponentState fullState)
|
||||
{
|
||||
if (fullState is TState state)
|
||||
return CreateNewFullState(state);
|
||||
else
|
||||
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
|
||||
if (fullState is not TState state)
|
||||
throw new Exception($"Unexpected type. Expected {typeof(TState).Name} but got {fullState.GetType().Name}");
|
||||
|
||||
return CreateNewFullState(state);
|
||||
}
|
||||
}
|
||||
|
||||
104
Robust.Shared/GameObjects/EntityExt.cs
Normal file
104
Robust.Shared/GameObjects/EntityExt.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public static class EntityExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an Entity{T} to Entity{T?}, making it compatible with methods that take nullable components.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component type. Must implement IComponent.</typeparam>
|
||||
/// <param name="ent">The source entity to convert.</param>
|
||||
/// <returns>An Entity{T?} with the same owner and component as the source entity.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Instead of:
|
||||
/// Entity{MyComponent?} nullable = (ent, ent.Comp);
|
||||
///
|
||||
/// // You can write:
|
||||
/// Entity{MyComponent?} nullable = ent.AsNullable();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Entity<T?> AsNullable<T>(this Entity<T> ent) where T : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?> AsNullable<T1, T2>(this Entity<T1, T2> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?> AsNullable<T1, T2, T3>(this Entity<T1, T2, T3> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?> AsNullable<T1, T2, T3, T4>(this Entity<T1, T2, T3, T4> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?> AsNullable<T1, T2, T3, T4, T5>(this Entity<T1, T2, T3, T4, T5> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?> AsNullable<T1, T2, T3, T4, T5, T6>(
|
||||
this Entity<T1, T2, T3, T4, T5, T6> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
where T6 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?> AsNullable<T1, T2, T3, T4, T5, T6, T7>(
|
||||
this Entity<T1, T2, T3, T4, T5, T6, T7> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
where T6 : IComponent
|
||||
where T7 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6, ent.Comp7);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?> AsNullable<T1, T2, T3, T4, T5, T6, T7, T8>(
|
||||
this Entity<T1, T2, T3, T4, T5, T6, T7, T8> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
where T6 : IComponent
|
||||
where T7 : IComponent
|
||||
where T8 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6, ent.Comp7, ent.Comp8);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,6 @@ namespace Robust.Shared.GameObjects
|
||||
newFixtures.Add(($"grid_chunk-{bounds.Left}-{bounds.Bottom}", newFixture));
|
||||
}
|
||||
|
||||
var toRemove = new ValueList<(string Id, Fixture Fixture)>();
|
||||
// Check if we even need to issue an eventbus event
|
||||
var updated = false;
|
||||
|
||||
@@ -167,6 +166,13 @@ namespace Robust.Shared.GameObjects
|
||||
for (var i = newFixtures.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var fixture = newFixtures[i].Fixture;
|
||||
|
||||
// TODO GRIDS
|
||||
// Fix this
|
||||
// This **only** works if we assume the density is always the default (PhysicsConstants.DefaultDensity).
|
||||
// Hence, this always fails in SS14 because ShuttleSystem.OnGridFixtureChange changes the density.
|
||||
// So it constantly creats & destroys fixtures unnecessarily
|
||||
// AAAAA
|
||||
if (!oldFixture.Equals(fixture))
|
||||
continue;
|
||||
|
||||
@@ -175,21 +181,16 @@ namespace Robust.Shared.GameObjects
|
||||
break;
|
||||
}
|
||||
|
||||
if (existing)
|
||||
continue;
|
||||
|
||||
// Doesn't align with any new fixtures so delete
|
||||
if (existing) continue;
|
||||
|
||||
toRemove.Add((oldId, oldFixture));
|
||||
chunk.Fixtures.Remove(oldId);
|
||||
_fixtures.DestroyFixture(uid, oldId, oldFixture, false, body: body, manager: manager, xform: xform);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
foreach (var (id, fixture) in toRemove.Span)
|
||||
{
|
||||
// TODO add a DestroyFixture() override that takes in a list.
|
||||
// reduced broadphase lookups
|
||||
chunk.Fixtures.Remove(id);
|
||||
_fixtures.DestroyFixture(uid, id, fixture, false, body: body, manager: manager, xform: xform);
|
||||
}
|
||||
|
||||
if (newFixtures.Count > 0 || toRemove.Count > 0)
|
||||
if (newFixtures.Count > 0)
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
@@ -200,10 +201,11 @@ namespace Robust.Shared.GameObjects
|
||||
chunk.Fixtures.Add(id);
|
||||
var existingFixture = _fixtures.GetFixtureOrNull(uid, id, manager: manager);
|
||||
// Check if it's the same (otherwise remove anyway).
|
||||
// TODO GRIDS
|
||||
// wasn't this already checked?
|
||||
if (existingFixture?.Shape is PolygonShape poly &&
|
||||
poly.EqualsApprox((PolygonShape) fixture.Shape))
|
||||
{
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -245,13 +245,10 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
HashSet<MapChunk> modifiedChunks;
|
||||
|
||||
switch (args.Current)
|
||||
{
|
||||
case MapGridComponentDeltaState delta:
|
||||
{
|
||||
modifiedChunks = new();
|
||||
DebugTools.Assert(component.ChunkSize == delta.ChunkSize || component.Chunks.Count == 0,
|
||||
"Can't modify chunk size of an existing grid.");
|
||||
|
||||
@@ -261,7 +258,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
foreach (var (index, chunkData) in delta.ChunkData)
|
||||
{
|
||||
ApplyChunkData(uid, component, index, chunkData, modifiedChunks);
|
||||
ApplyChunkData(uid, component, index, chunkData);
|
||||
}
|
||||
|
||||
component.LastTileModifiedTick = delta.LastTileModifiedTick;
|
||||
@@ -269,7 +266,6 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
case MapGridComponentState state:
|
||||
{
|
||||
modifiedChunks = new();
|
||||
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
|
||||
"Can't modify chunk size of an existing grid.");
|
||||
|
||||
@@ -279,12 +275,13 @@ public abstract partial class SharedMapSystem
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.FullGridData.ContainsKey(index))
|
||||
ApplyChunkData(uid, component, index, ChunkDatum.Empty, modifiedChunks);
|
||||
ApplyChunkData(uid, component, index, ChunkDatum.Empty);
|
||||
}
|
||||
|
||||
foreach (var (index, data) in state.FullGridData)
|
||||
{
|
||||
ApplyChunkData(uid, component, index, new(data), modifiedChunks);
|
||||
DebugTools.Assert(!data.IsDeleted());
|
||||
ApplyChunkData(uid, component, index, data);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -309,10 +306,8 @@ public abstract partial class SharedMapSystem
|
||||
EntityUid uid,
|
||||
MapGridComponent component,
|
||||
Vector2i index,
|
||||
ChunkDatum data,
|
||||
HashSet<MapChunk> modifiedChunks)
|
||||
ChunkDatum data)
|
||||
{
|
||||
bool shapeChanged = false;
|
||||
var counter = 0;
|
||||
|
||||
if (data.IsDeleted())
|
||||
@@ -326,7 +321,7 @@ public abstract partial class SharedMapSystem
|
||||
{
|
||||
for (ushort y = 0; y < component.ChunkSize; y++)
|
||||
{
|
||||
if (!deletedChunk.TrySetTile(x, y, Tile.Empty, out var oldTile, out var chunkShapeChanged))
|
||||
if (!deletedChunk.TrySetTile(x, y, Tile.Empty, out var oldTile, out _))
|
||||
continue;
|
||||
|
||||
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
|
||||
@@ -336,9 +331,6 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
|
||||
component.Chunks.Remove(index);
|
||||
|
||||
// TODO is this required?
|
||||
modifiedChunks.Add(deletedChunk);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -351,30 +343,26 @@ public abstract partial class SharedMapSystem
|
||||
for (ushort y = 0; y < component.ChunkSize; y++)
|
||||
{
|
||||
var tile = data.TileData[counter++];
|
||||
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out var tileShapeChanged))
|
||||
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out _))
|
||||
continue;
|
||||
|
||||
shapeChanged |= tileShapeChanged;
|
||||
var gridIndices = chunk.ChunkTileToGridTile((x, y));
|
||||
var newTileRef = new TileRef(uid, gridIndices, tile);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Fixtures != null && !chunk.Fixtures.SetEquals(data.Fixtures))
|
||||
// These should never refer to the same object
|
||||
DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures);
|
||||
|
||||
if (!chunk.Fixtures.SetEquals(data.Fixtures))
|
||||
{
|
||||
chunk.Fixtures.Clear();
|
||||
|
||||
if (data.Fixtures != null)
|
||||
chunk.Fixtures.UnionWith(data.Fixtures);
|
||||
chunk.Fixtures.UnionWith(data.Fixtures);
|
||||
}
|
||||
|
||||
chunk.CachedBounds = data.CachedBounds!.Value;
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
if (shapeChanged)
|
||||
{
|
||||
modifiedChunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGridGetState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
|
||||
@@ -429,7 +417,15 @@ public abstract partial class SharedMapSystem
|
||||
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
|
||||
}
|
||||
}
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
|
||||
|
||||
// The client needs to clone the fixture set instead of storing a reference.
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
var fixtures = chunk.Fixtures;
|
||||
if (_netManager.IsClient)
|
||||
fixtures = new(fixtures);
|
||||
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, fixtures, chunk.CachedBounds));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +462,15 @@ public abstract partial class SharedMapSystem
|
||||
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
|
||||
}
|
||||
}
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
|
||||
|
||||
// The client needs to clone the fixture set instead of storing a reference.
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
var fixtures = chunk.Fixtures;
|
||||
if (_netManager.IsClient)
|
||||
fixtures = new(fixtures);
|
||||
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, fixtures, chunk.CachedBounds));
|
||||
}
|
||||
|
||||
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
|
||||
@@ -644,10 +648,12 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
foreach (var id in mapChunk.Fixtures)
|
||||
{
|
||||
mapChunk.Fixtures.Remove(id);
|
||||
_fixtures.DestroyFixture(uid, id, false, manager: manager, body: body, xform: xform);
|
||||
}
|
||||
|
||||
RemoveChunk(uid, grid, mapChunk.Indices);
|
||||
DebugTools.AssertEqual(mapChunk.Fixtures.Count, 0);
|
||||
removedChunks.Add(mapChunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,12 +322,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
}
|
||||
case 1 << 2:
|
||||
{
|
||||
var state = new UserInterfaceStatesDeltaState()
|
||||
{
|
||||
States = new Dictionary<Enum, BoundUserInterfaceState>(ent.Comp.States),
|
||||
};
|
||||
var states = ent.Comp.States;
|
||||
|
||||
args.State = state;
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
if (_netManager.IsClient)
|
||||
states = new(states);
|
||||
|
||||
args.State = new UserInterfaceStatesDeltaState {States = states};
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -336,6 +338,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
var actors = new Dictionary<Enum, List<NetEntity>>();
|
||||
var dataCopy = new Dictionary<Enum, InterfaceData>(ent.Comp.Interfaces.Count);
|
||||
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
foreach (var (weh, a) in ent.Comp.Interfaces)
|
||||
{
|
||||
dataCopy[weh] = new InterfaceData(a);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Shared.GameStates
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct ChunkDatum
|
||||
{
|
||||
public static readonly ChunkDatum Empty = new ChunkDatum();
|
||||
public static readonly ChunkDatum Empty = new();
|
||||
|
||||
public readonly HashSet<string>? Fixtures;
|
||||
|
||||
@@ -21,28 +21,12 @@ namespace Robust.Shared.GameStates
|
||||
|
||||
public readonly Box2i? CachedBounds;
|
||||
|
||||
[MemberNotNullWhen(false, nameof(TileData))]
|
||||
[MemberNotNullWhen(false, nameof(TileData), nameof(Fixtures))]
|
||||
public bool IsDeleted()
|
||||
{
|
||||
return TileData == null;
|
||||
}
|
||||
|
||||
internal ChunkDatum(ChunkDatum data)
|
||||
{
|
||||
if (data.TileData != null)
|
||||
{
|
||||
TileData = new Tile[data.TileData.Length];
|
||||
data.TileData.CopyTo(TileData, 0);
|
||||
}
|
||||
|
||||
if (data.Fixtures != null)
|
||||
{
|
||||
Fixtures = new HashSet<string>(data.Fixtures);
|
||||
}
|
||||
|
||||
CachedBounds = data.CachedBounds;
|
||||
}
|
||||
|
||||
private ChunkDatum(Tile[] tileData, HashSet<string> fixtures, Box2i cachedBounds)
|
||||
{
|
||||
TileData = tileData;
|
||||
|
||||
@@ -94,6 +94,12 @@ namespace Robust.Shared.Localization
|
||||
/// </summary>
|
||||
CultureInfo? DefaultCulture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the culture has been loaded.
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
bool HasCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Load data for a culture.
|
||||
/// </summary>
|
||||
|
||||
@@ -337,6 +337,11 @@ namespace Robust.Shared.Localization
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCulture(CultureInfo culture)
|
||||
{
|
||||
return _contexts.ContainsKey(culture);
|
||||
}
|
||||
|
||||
public void LoadCulture(CultureInfo culture)
|
||||
{
|
||||
var bundle = LinguiniBuilder.Builder()
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace Robust.Shared.Map.Components
|
||||
if (data.IsDeleted())
|
||||
state.FullGridData.Remove(index);
|
||||
else
|
||||
state.FullGridData[index] = new(data);
|
||||
state.FullGridData[index] = data;
|
||||
}
|
||||
|
||||
state.LastTileModifiedTick = LastTileModifiedTick;
|
||||
@@ -327,14 +327,10 @@ namespace Robust.Shared.Map.Components
|
||||
|
||||
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
|
||||
{
|
||||
var fullGridData = new Dictionary<Vector2i, ChunkDatum>(state.FullGridData.Count);
|
||||
if (ChunkData == null)
|
||||
return new(ChunkSize, state.FullGridData, state.LastTileModifiedTick);
|
||||
|
||||
foreach (var (key, value) in state.FullGridData)
|
||||
{
|
||||
fullGridData[key] = new(value);
|
||||
}
|
||||
|
||||
var newState = new MapGridComponentState(ChunkSize, fullGridData, LastTileModifiedTick);
|
||||
var newState = new MapGridComponentState(ChunkSize, state.FullGridData.ShallowClone(), LastTileModifiedTick);
|
||||
ApplyToFullState(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
@@ -406,5 +406,10 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// Set right before the contact is deleted
|
||||
/// </summary>
|
||||
Deleting = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Set after a contact has been deleted and returned to the contact pool.
|
||||
/// </summary>
|
||||
Deleted = 1 << 5,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Systems
|
||||
@@ -21,6 +23,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
private EntityQuery<PhysicsMapComponent> _mapQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<FixturesComponent> _fixtureQuery;
|
||||
@@ -180,6 +183,18 @@ namespace Robust.Shared.Physics.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporary debug block for trying to help catch a bug where grid fixtures disappear without the chunk's
|
||||
// fixture set being updated
|
||||
#if DEBUG
|
||||
if (TryComp(uid, out MapGridComponent? grid) && !_timing.ApplyingState)
|
||||
{
|
||||
foreach (var chunk in grid.Chunks.Values)
|
||||
{
|
||||
DebugTools.Assert(!chunk.Fixtures.Contains(fixtureId), $"A grid fixture is being deleted without first removing it from the chunk. Please report this bug.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var contact in fixture.Contacts.Values.ToArray())
|
||||
{
|
||||
_physics.DestroyContact(contact);
|
||||
|
||||
@@ -472,46 +472,59 @@ namespace Robust.Shared.Physics.Systems
|
||||
TouchProxies(xform.MapUid.Value, matrix, fixture);
|
||||
}
|
||||
|
||||
internal void GetBroadphases(MapId mapId, Box2 aabb,BroadphaseCallback callback)
|
||||
internal void GetBroadphases(MapId mapId, Box2 aabb, BroadphaseCallback callback)
|
||||
{
|
||||
var internalState = (callback, _broadphaseQuery);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId,
|
||||
if (!_map.TryGetMap(mapId, out var map))
|
||||
return;
|
||||
|
||||
if (_broadphaseQuery.TryGetComponent(map.Value, out var mapBroadphase))
|
||||
callback((map.Value, mapBroadphase));
|
||||
|
||||
_mapManager.FindGridsIntersecting(map.Value,
|
||||
aabb,
|
||||
ref internalState,
|
||||
static (
|
||||
EntityUid uid,
|
||||
MapGridComponent grid,
|
||||
MapGridComponent _,
|
||||
ref (BroadphaseCallback callback, EntityQuery<BroadphaseComponent> _broadphaseQuery) tuple) =>
|
||||
{
|
||||
if (!tuple._broadphaseQuery.TryComp(uid, out var broadphase))
|
||||
return true;
|
||||
if (tuple._broadphaseQuery.TryComp(uid, out var broadphase))
|
||||
tuple.callback((uid, broadphase));
|
||||
|
||||
tuple.callback((uid, broadphase));
|
||||
return true;
|
||||
// Approx because we don't really need accurate checks for these most of the time.
|
||||
}, approx: true, includeMap: true);
|
||||
},
|
||||
// Approx because we don't really need accurate checks for these most of the time.
|
||||
approx: true,
|
||||
includeMap: false);
|
||||
}
|
||||
|
||||
internal void GetBroadphases<TState>(MapId mapId, Box2 aabb, ref TState state, BroadphaseCallback<TState> callback)
|
||||
{
|
||||
var internalState = (state, callback, _broadphaseQuery);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId,
|
||||
if (!_map.TryGetMap(mapId, out var map))
|
||||
return;
|
||||
|
||||
if (_broadphaseQuery.TryGetComponent(map.Value, out var mapBroadphase))
|
||||
callback((map.Value, mapBroadphase), ref state);
|
||||
|
||||
_mapManager.FindGridsIntersecting(map.Value,
|
||||
aabb,
|
||||
ref internalState,
|
||||
static (
|
||||
EntityUid uid,
|
||||
MapGridComponent grid,
|
||||
MapGridComponent _,
|
||||
ref (TState state, BroadphaseCallback<TState> callback, EntityQuery<BroadphaseComponent> _broadphaseQuery) tuple) =>
|
||||
{
|
||||
if (!tuple._broadphaseQuery.TryComp(uid, out var broadphase))
|
||||
return true;
|
||||
|
||||
tuple.callback((uid, broadphase), ref tuple.state);
|
||||
if (tuple._broadphaseQuery.TryComp(uid, out var broadphase))
|
||||
tuple.callback((uid, broadphase), ref tuple.state);
|
||||
return true;
|
||||
// Approx because we don't really need accurate checks for these most of the time.
|
||||
}, approx: true, includeMap: true);
|
||||
},
|
||||
// Approx because we don't really need accurate checks for these most of the time.
|
||||
approx: true,
|
||||
includeMap: false);
|
||||
|
||||
state = internalState.state;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -32,6 +33,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -242,27 +244,31 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void DestroyContacts(PhysicsComponent body)
|
||||
{
|
||||
if (body.Contacts.Count == 0) return;
|
||||
if (body.Contacts.Count == 0)
|
||||
return;
|
||||
|
||||
// This variable is only used in edge-case scenario when contact flagged Deleting raises
|
||||
// EndCollideEvent which will QueueDelete contact's entity
|
||||
ushort contactsFlaggedDeleting = 0;
|
||||
var node = body.Contacts.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
var contact = node.Value;
|
||||
node = node.Next;
|
||||
|
||||
// Destroy last so the linked-list doesn't get touched.
|
||||
if (!DestroyContact(contact))
|
||||
{
|
||||
contactsFlaggedDeleting++;
|
||||
}
|
||||
// The Start/End collide events can result in other contacts in this list being destroyed, and maybe being
|
||||
// created elsewhere. We want to ensure that the "next" node from a previous iteration wasn't somehow
|
||||
// destroyed, returned to the pool, and then re-assigned to a new body.
|
||||
// AFAIK this shouldn't be possible anymore, now that the next node is returned by DestroyContacts() after
|
||||
// all events were raised.
|
||||
DebugTools.Assert(contact.BodyA == body || contact.BodyB == body || contact.Flags == ContactFlags.Deleted);
|
||||
DebugTools.AssertNotEqual(node, node.Next);
|
||||
|
||||
DestroyContact(contact, node, out var next);
|
||||
DebugTools.AssertNotEqual(node, next);
|
||||
node = next;
|
||||
}
|
||||
|
||||
// This contact will be deleted before SimulateWorld runs since it is already set to be Deleted
|
||||
DebugTools.Assert(body.Contacts.Count == contactsFlaggedDeleting);
|
||||
// It is possible that this DestroyContacts was called while another DestroyContacts was still being processed.
|
||||
// The only remaining contacts should be those that are still getting deleted.
|
||||
DebugTools.Assert(body.Contacts.All(x => (x.Flags & ContactFlags.Deleting) != 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -123,6 +123,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
public bool Return(Contact obj)
|
||||
{
|
||||
DebugTools.Assert(obj.Flags is ContactFlags.None or ContactFlags.Deleted);
|
||||
SetContact(obj,
|
||||
false,
|
||||
EntityUid.Invalid, EntityUid.Invalid,
|
||||
@@ -145,7 +146,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
contact.Enabled = enabled;
|
||||
contact.IsTouching = false;
|
||||
contact.Flags = ContactFlags.None | ContactFlags.PreInit;
|
||||
DebugTools.Assert(contact.Flags is ContactFlags.None or ContactFlags.PreInit or ContactFlags.Deleted);
|
||||
// TOIFlag = false;
|
||||
|
||||
contact.EntityA = uidA;
|
||||
@@ -229,6 +230,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
// Pull out a spare contact object
|
||||
var contact = _contactPool.Get();
|
||||
DebugTools.Assert(contact.Flags is ContactFlags.None or ContactFlags.Deleted);
|
||||
contact.Flags = ContactFlags.PreInit;
|
||||
|
||||
// Edge+Polygon is non-symmetrical due to the way Erin handles collision type registration.
|
||||
if ((type1 >= type2 || (type1 == ShapeType.Edge && type2 == ShapeType.Polygon)) && !(type2 == ShapeType.Edge && type1 == ShapeType.Polygon))
|
||||
@@ -314,13 +317,26 @@ public abstract partial class SharedPhysicsSystem
|
||||
(fixtureB.CollisionMask & fixtureA.CollisionLayer) == 0x0);
|
||||
}
|
||||
|
||||
public bool DestroyContact(Contact contact)
|
||||
public void DestroyContact(Contact contact)
|
||||
{
|
||||
if ((contact.Flags & ContactFlags.Deleting) != 0x0)
|
||||
return false;
|
||||
DestroyContact(contact, null, out _);
|
||||
}
|
||||
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
Fixture fixtureB = contact.FixtureB!;
|
||||
internal void DestroyContact(Contact contact, LinkedListNode<Contact>? node, out LinkedListNode<Contact>? next)
|
||||
{
|
||||
// EndCollideEvent may cause knock on effects that cause contacts to be destroyed.
|
||||
// This check prevents us from trying to destroy a contact that is already being, or already has been, destroyed.
|
||||
if ((contact.Flags & (ContactFlags.Deleting | ContactFlags.Deleted)) != 0x0)
|
||||
{
|
||||
next = node?.Next;
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert((contact.Flags & ContactFlags.PreInit) == 0);
|
||||
// Contact flag might be None here as CollideContacts() might destroy the contact after having removed the PreInit flag
|
||||
|
||||
var fixtureA = contact.FixtureA!;
|
||||
var fixtureB = contact.FixtureB!;
|
||||
var bodyA = contact.BodyA!;
|
||||
var bodyB = contact.BodyB!;
|
||||
var aUid = contact.EntityA;
|
||||
@@ -344,6 +360,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
SetAwake((bUid, bodyB), true);
|
||||
}
|
||||
|
||||
// Fetch next node AFTER all event raising has finished.
|
||||
// This ensures that we actually get the next node in case the linked list was modified by the events that were
|
||||
// raised
|
||||
next = node?.Next;
|
||||
|
||||
// Remove from the world
|
||||
_activeContacts.Remove(contact.MapNode);
|
||||
|
||||
@@ -359,10 +380,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
DebugTools.Assert(bodyB.Contacts.Contains(contact.BodyBNode.Value));
|
||||
bodyB.Contacts.Remove(contact.BodyBNode);
|
||||
|
||||
// Insert into the pool.
|
||||
contact.Flags = ContactFlags.Deleted;
|
||||
_contactPool.Return(contact);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void CollideContacts()
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Shared.Physics.Systems;
|
||||
|
||||
public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
private void InitializeFixturesChange()
|
||||
{
|
||||
SubscribeLocalEvent<FixturesChangeComponent, ComponentStartup>(OnChangeStartup);
|
||||
SubscribeLocalEvent<FixturesChangeComponent, ComponentShutdown>(OnChangeShutdown);
|
||||
}
|
||||
|
||||
private void OnChangeStartup(Entity<FixturesChangeComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
foreach (var (id, fixture) in ent.Comp.Fixtures)
|
||||
{
|
||||
_fixtures.TryCreateFixture(ent.Owner,
|
||||
fixture.Shape,
|
||||
id,
|
||||
fixture.Density,
|
||||
fixture.Hard,
|
||||
fixture.CollisionLayer,
|
||||
fixture.CollisionMask,
|
||||
fixture.Friction,
|
||||
fixture.Restitution);
|
||||
}
|
||||
|
||||
// TODO: Fixture creation should be handling this.
|
||||
WakeBody(ent.Owner);
|
||||
}
|
||||
|
||||
private void OnChangeShutdown(Entity<FixturesChangeComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
foreach (var id in ent.Comp.Fixtures.Keys)
|
||||
{
|
||||
_fixtures.DestroyFixture(ent.Owner, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +402,7 @@ namespace Robust.Shared.Prototypes
|
||||
}*/
|
||||
}
|
||||
|
||||
public sealed class ComponentRegistry : Dictionary<string, EntityPrototype.ComponentRegistryEntry>, IEntityLoadContext, ISerializationContext
|
||||
public sealed class ComponentRegistry : Dictionary<string, EntityPrototype.ComponentRegistryEntry>, IEntityLoadContext
|
||||
{
|
||||
public ComponentRegistry()
|
||||
{
|
||||
@@ -429,8 +429,5 @@ namespace Robust.Shared.Prototypes
|
||||
{
|
||||
return false; //Registries cannot represent the "remove this component" state.
|
||||
}
|
||||
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
public bool WritingReadingPrototypes { get; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using BindingFlags = System.Reflection.BindingFlags;
|
||||
@@ -73,9 +75,8 @@ public partial class PrototypeManager
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetIds(field, proto, out var ids))
|
||||
if (!TryGetIds(field, out var ids))
|
||||
{
|
||||
TryGetIds(field, proto, out _);
|
||||
DebugTools.Assert($"Failed to get ids, despite resolving the field into a prototype kind?");
|
||||
return;
|
||||
}
|
||||
@@ -90,7 +91,7 @@ public partial class PrototypeManager
|
||||
/// <summary>
|
||||
/// Extract prototype ids from a string, IEnumerable{string}, EntProtoId, IEnumerable{EntProtoId}, ProtoId{T}, or IEnumerable{ProtoId{T}} field.
|
||||
/// </summary>
|
||||
private bool TryGetIds(FieldInfo field, Type proto, [NotNullWhen(true)] out string[]? ids)
|
||||
private bool TryGetIds(FieldInfo field, [NotNullWhen(true)] out string[]? ids)
|
||||
{
|
||||
ids = null;
|
||||
var value = field.GetValue(null);
|
||||
@@ -121,10 +122,14 @@ public partial class PrototypeManager
|
||||
return true;
|
||||
}
|
||||
|
||||
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
if (field.FieldType.IsGenericType)
|
||||
{
|
||||
ids = [value.ToString()!];
|
||||
return true;
|
||||
var genDef = field.FieldType.GetGenericTypeDefinition();
|
||||
if (genDef == typeof(ProtoId<>) || genDef == typeof(EntProtoId<>))
|
||||
{
|
||||
ids = [value.ToString()!];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var iface in field.FieldType.GetInterfaces())
|
||||
@@ -139,20 +144,27 @@ public partial class PrototypeManager
|
||||
if (!enumType.IsGenericType)
|
||||
continue;
|
||||
|
||||
if (enumType.GetGenericTypeDefinition() != typeof(ProtoId<>))
|
||||
var genDef = enumType.GetGenericTypeDefinition();
|
||||
if (genDef != typeof(ProtoId<>) && genDef != typeof(EntProtoId<>))
|
||||
continue;
|
||||
|
||||
ids = GetIdsMethod.MakeGenericMethod(proto).Invoke(null, [value]) as string[];
|
||||
return ids != null;
|
||||
ids = GetEnumerableIds((IEnumerable)value).ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static MethodInfo GetIdsMethod = typeof(PrototypeManager).GetMethod(nameof(GetIds), BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
private static string[] GetIds<T>(IEnumerable<ProtoId<T>> enumerable) where T : class, IPrototype
|
||||
/// <summary>
|
||||
/// Helper method for <see cref="TryGetIds"/> that converts an IEnumerable of <see cref="EntProtoId{T}"/> and
|
||||
/// <see cref="ProtoId{T}"/> into their id strings.
|
||||
/// </summary>
|
||||
private IEnumerable<string> GetEnumerableIds(IEnumerable ids)
|
||||
{
|
||||
return enumerable.Select(x => x.Id).ToArray();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
yield return id!.ToString()!;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetFieldPrototype(FieldInfo field, [NotNullWhen(true)] out Type? proto)
|
||||
@@ -191,7 +203,21 @@ public partial class PrototypeManager
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
proto = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.GetGenericTypeDefinition() == typeof(EntProtoId<>))
|
||||
{
|
||||
// TODO Static field validation
|
||||
// Check that the given prototype has the T component.
|
||||
proto = typeof(EntityPrototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
{
|
||||
proto = type.GetGenericArguments().Single();
|
||||
DebugTools.Assert(proto != typeof(EntityPrototype), "Use EntProtoId instead of ProtoId<EntityPrototype>");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -476,15 +477,32 @@ namespace Robust.Shared.Serialization.Manager.Definition
|
||||
|
||||
private Expression AssignIfNotDefaultExpression(int i, Expression obj, Expression value)
|
||||
{
|
||||
var assigner = FieldAssigners[i];
|
||||
Expression assignerExpr;
|
||||
|
||||
if (assigner is FieldInfo fieldInfo)
|
||||
assignerExpr = Expression.Assign(Expression.Field(obj, fieldInfo), value);
|
||||
else if (assigner is MethodInfo methodInfo)
|
||||
assignerExpr = Expression.Call(obj, methodInfo, value);
|
||||
else
|
||||
assignerExpr = Expression.Invoke(Expression.Constant(assigner), obj, value);
|
||||
|
||||
return Expression.IfThen(
|
||||
Expression.Not(ExpressionUtils.EqualExpression(
|
||||
Expression.Constant(DefaultValues[i], BaseFieldDefinitions[i].FieldType), value)),
|
||||
Expression.Invoke(Expression.Constant(FieldAssigners[i]), obj, value));
|
||||
assignerExpr);
|
||||
}
|
||||
|
||||
private Expression AccessExpression(int i, Expression obj)
|
||||
{
|
||||
return Expression.Invoke(Expression.Constant(FieldAccessors[i]), obj);
|
||||
var accessor = FieldAccessors[i];
|
||||
if (accessor is FieldInfo fieldInfo)
|
||||
return Expression.Field(obj, fieldInfo);
|
||||
|
||||
if (accessor is MethodInfo methodInfo)
|
||||
return Expression.Call(obj, methodInfo);
|
||||
|
||||
return Expression.Invoke(Expression.Constant(accessor), obj);
|
||||
}
|
||||
|
||||
private Expression IsDefault(int i, Expression left, FieldDefinition fieldDefinition)
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
|
||||
for (var i = 0; i < BaseFieldDefinitions.Length; i++)
|
||||
{
|
||||
var fieldDefinition = BaseFieldDefinitions[i];
|
||||
fieldAssigners[i] = InternalReflectionUtils.EmitFieldAssigner<T>(fieldDefinition.BackingField);
|
||||
fieldAssigners[i] = InternalReflectionUtils.EmitFieldAssigner(typeof(T), fieldDefinition.BackingField);
|
||||
fieldAccessors[i] = InternalReflectionUtils.EmitFieldAccessor(typeof(T), fieldDefinition);
|
||||
|
||||
if (fieldDefinition.Attribute.CustomTypeSerializer != null)
|
||||
|
||||
@@ -123,13 +123,13 @@ namespace Robust.Shared.Timing
|
||||
/// </summary>
|
||||
public TimeSpan LastTick { get; set; }
|
||||
|
||||
private byte _tickRate;
|
||||
private ushort _tickRate;
|
||||
private TimeSpan _tickRemainder;
|
||||
|
||||
/// <summary>
|
||||
/// The target ticks/second of the simulation.
|
||||
/// </summary>
|
||||
public byte TickRate
|
||||
public ushort TickRate
|
||||
{
|
||||
get => _tickRate;
|
||||
set => SetTickRateAt(value, CurTick);
|
||||
@@ -230,7 +230,7 @@ namespace Robust.Shared.Timing
|
||||
Paused = true;
|
||||
}
|
||||
|
||||
public void SetTickRateAt(byte tickRate, GameTick atTick)
|
||||
public void SetTickRateAt(ushort tickRate, GameTick atTick)
|
||||
{
|
||||
// Check this, because TickRate is a divisor in the cache calculation
|
||||
// The first time TickRate is set, no time will have passed anyways
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Robust.Shared.Timing
|
||||
/// <summary>
|
||||
/// The target ticks/second of the simulation.
|
||||
/// </summary>
|
||||
byte TickRate { get; set; }
|
||||
ushort TickRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The baseline time value that CurTime is calculated relatively to.
|
||||
@@ -167,7 +167,7 @@ namespace Robust.Shared.Timing
|
||||
void ResetSimTime();
|
||||
void ResetSimTime((TimeSpan, GameTick) timeBase);
|
||||
|
||||
void SetTickRateAt( byte tickRate, GameTick atTick);
|
||||
void SetTickRateAt(ushort tickRate, GameTick atTick);
|
||||
|
||||
TimeSpan RealLocalToServer(TimeSpan local);
|
||||
TimeSpan RealServerToLocal(TimeSpan server);
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Robust.Shared.Toolshed.TypeParsers;
|
||||
|
||||
internal sealed class StringTypeParser : TypeParser<string>
|
||||
{
|
||||
// Completion option for hinting that all strings must start with an quote
|
||||
private static readonly CompletionOption[] Option = [new("\"", Flags: CompletionOptionFlags.PartialCompletion)];
|
||||
// Completion option for hinting that all strings must start with a quote
|
||||
private static readonly CompletionOption[] Option = [new("\"", Flags: CompletionOptionFlags.PartialCompletion | CompletionOptionFlags.NoEscape)];
|
||||
|
||||
public override bool TryParse(ParserContext ctx, [NotNullWhen(true)] out string? result)
|
||||
{
|
||||
|
||||
@@ -39,6 +39,12 @@ internal static class InternalReflectionUtils
|
||||
|
||||
internal static object EmitFieldAccessor(Type obj, FieldDefinition fieldDefinition)
|
||||
{
|
||||
if (fieldDefinition.BackingField is SpecificFieldInfo fieldInfo)
|
||||
return fieldInfo.FieldInfo;
|
||||
|
||||
if (fieldDefinition.BackingField is SpecificPropertyInfo propertyInfo)
|
||||
return propertyInfo.PropertyInfo.GetGetMethod(true) ?? throw new InvalidOperationException("Property has no getter");
|
||||
|
||||
var method = new DynamicMethod(
|
||||
"AccessField",
|
||||
fieldDefinition.BackingField.FieldType,
|
||||
@@ -71,14 +77,29 @@ internal static class InternalReflectionUtils
|
||||
return method.CreateDelegate(typeof(AccessField<,>).MakeGenericType(obj, fieldDefinition.BackingField.FieldType));
|
||||
}
|
||||
|
||||
internal static object EmitFieldAssigner<TObj>(AbstractFieldInfo backingField, bool boxing = false)
|
||||
internal static object EmitFieldAssigner(Type objType, AbstractFieldInfo backingField, bool boxing = false)
|
||||
{
|
||||
if (!boxing)
|
||||
{
|
||||
if (backingField is SpecificFieldInfo { FieldInfo.IsInitOnly: false } fieldInfo)
|
||||
return fieldInfo.FieldInfo;
|
||||
|
||||
if (backingField is SpecificPropertyInfo propertyInfo)
|
||||
{
|
||||
if (propertyInfo.TryGetBackingField(out var propertyBackingField) && !propertyBackingField.FieldInfo.IsInitOnly)
|
||||
return propertyBackingField.FieldInfo;
|
||||
|
||||
if (propertyInfo.PropertyInfo.GetSetMethod(true) is { } setMethod)
|
||||
return setMethod;
|
||||
}
|
||||
}
|
||||
|
||||
var fieldType = backingField.FieldType;
|
||||
|
||||
var method = new DynamicMethod(
|
||||
"AssignField",
|
||||
typeof(void),
|
||||
new[] {typeof(TObj).MakeByRefType(), boxing ? typeof(object) : fieldType},
|
||||
new[] {objType.MakeByRefType(), boxing ? typeof(object) : fieldType},
|
||||
true);
|
||||
|
||||
method.DefineParameter(1, ParameterAttributes.Out, "target");
|
||||
@@ -88,7 +109,7 @@ internal static class InternalReflectionUtils
|
||||
|
||||
generator.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
if(!typeof(TObj).IsValueType)
|
||||
if(!objType.IsValueType)
|
||||
generator.Emit(OpCodes.Ldind_Ref);
|
||||
|
||||
generator.Emit(OpCodes.Ldarg_1);
|
||||
@@ -102,6 +123,6 @@ internal static class InternalReflectionUtils
|
||||
|
||||
generator.Emit(OpCodes.Ret);
|
||||
|
||||
return method.CreateDelegate(typeof(AssignField<,>).MakeGenericType(typeof(TObj), boxing ? typeof(object) : fieldType));
|
||||
return method.CreateDelegate(typeof(AssignField<,>).MakeGenericType(objType, boxing ? typeof(object) : fieldType));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user