Compare commits

...

25 Commits

Author SHA1 Message Date
DrSmugleaf
35c983d2fd Version: 240.1.2-source-gen-debug 2025-01-21 18:47:40 -08:00
DrSmugleaf
88cfd04f01 Source gen changes 2025-01-21 18:47:25 -08:00
DrSmugleaf
ee906af16e Version: 240.1.2 2025-01-21 18:43:28 -08:00
wixoa
e205ae3627 Use the color arg when drawing a font character in world space (#5626) 2025-01-21 22:39:07 +01:00
Jerry
d818c5aa0c fix StatusHost RespondJsonAsync method (#5622) 2025-01-21 16:58:37 +01:00
ElectroJr
aa8fe8ac92 Version: 240.1.1 2025-01-20 14:16:39 +13:00
Leon Friedrich
4ba6687b9d Fix OverlayManager.RemoveOverlay (#5623) 2025-01-20 12:14:58 +11:00
ElectroJr
8f2817aa4e Version: 240.1.0 2025-01-20 13:04:19 +13:00
Leon Friedrich
89c7839fe2 Fix exception in DestroyContacts (#5619) 2025-01-19 16:25:58 +11:00
DrSmugleaf
9799132001 Make GamePrototypeLoadManager send a single message with all uploaded prototypes in a single message (#5616) 2025-01-19 11:49:27 +11:00
Milon
34ffa56c57 add AsNullable() extension method to Entity<T> (#5617)
* just works first try

* use a constructor instead
2025-01-18 20:52:18 +01:00
ElectroJr
f8410a4674 Version: 240.0.1 2025-01-18 16:54:03 +13:00
Leon Friedrich
43b991c690 Fix SharedBroadphaseSystem.GetBroadphases (#5615) 2025-01-18 14:37:54 +11:00
PJB3005
c463fc5e78 Make source generators emit EditorBrowsable(Never)
This hides the generated types from intellisense if you have the relevant option in Rider enabled.

Good because honestly these just bloat IntelliSense, and if you want to show them there's an IDE setting for it.
2025-01-18 01:23:35 +01:00
PJB3005
e21b3e069a Version: 240.0.0 2025-01-17 17:57:31 +01:00
PJB3005
c8f94ab40d Update release notes 2025-01-17 17:57:14 +01:00
Tornado Tech
2a882b5555 Moved Overlay sorting to OverlayManager (#5614)
* Moved Overlay sorting to OverlayManager

* Changed Add to AddRange
2025-01-17 17:47:13 +01:00
Leon Friedrich
32d8a1cba9 Misc grid state changes (#5597) 2025-01-17 17:07:09 +01:00
Leon Friedrich
5d84be9c78 Make ComponentRegistry not implement ISerializationContext (#5613) 2025-01-17 15:01:25 +01:00
PJB3005
eaaa70437a Reduce DynamicMethod use in Serv3
Data definitions created individual read/write methods for every single field that can be serialized. This was extremely inefficient and likely caused lots of overhead.

These methods are necessary because, in some cases, we can't directly use expression trees to write fields. But... we can still do it most of the time! So now for most data fields we can avoid a DynamicMethod entirely.
2025-01-16 15:10:35 +01:00
Leon Friedrich
448e8b0c2c Validate static EntProtoId<T> fields (#5593) 2025-01-16 10:36:47 +01:00
Leon Friedrich
42948d8f8e Fix autocompletion hint for toolshed strings (#5584)
* Fix autocompletion hint for toolshed strings

* Split functionality

* Remove combined flag
2025-01-16 10:30:42 +01:00
c4llv07e
039468f4b6 Add function to check if localization culture was already loaded. (#5603) 2025-01-16 10:28:15 +01:00
Leon Friedrich
6a3f88b1c6 Fix replay playback bugs (#5604)
* Sort replay entities

* Fix nameof(TState)

* I forgot to generate implicit states

* release notes
2025-01-16 10:26:53 +01:00
PJB3005
a314c5f797 Tickrate is now a ushort
Fixes #5592

This allows net.tickrate to be set to a max of 65535 instead of 255.

I didn't raise it fully to a uint because there are many places it's cast to an int, so uint would cause various compiler errors and compat issues I don't wanna deal with.
2025-01-16 01:13:50 +01:00
47 changed files with 692 additions and 366 deletions

View File

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

View File

@@ -54,6 +54,67 @@ END TEMPLATE-->
*None yet*
## 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

View File

@@ -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()

View File

@@ -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; }
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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!!

View File

@@ -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;
}
}

View File

@@ -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)");

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -22,7 +22,7 @@ public sealed class CommandBuffer
_commandBuffer.AddFirst(command);
}
public void Tick(byte tickRate)
public void Tick(ushort tickRate)
{
_tickrate = tickRate;

View File

@@ -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,
}

View File

@@ -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 }

View File

@@ -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);
}
}

View 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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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>

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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,
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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()

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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>");

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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));
}
}