Add and update a lot of documentation (#6337)

* Serialization docs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* ECS docs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* scattered docs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* Fixes

---------

Co-authored-by: Moony <moonheart08@users.noreply.github.com>
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
This commit is contained in:
slarticodefast
2025-12-15 20:26:17 +01:00
committed by GitHub
parent cfefd760e2
commit 4747e5a05a
63 changed files with 1204 additions and 211 deletions

View File

@@ -9,38 +9,44 @@ namespace Robust.Shared.Configuration
public enum CVar : short
{
/// <summary>
/// No special flags.
/// No special flags.
/// </summary>
NONE = 0,
/// <summary>
/// Debug vars that are considered 'cheating' to change.
/// Debug vars that are considered 'cheating' to change.
/// </summary>
CHEAT = 1,
/// <summary>
/// Only the server can change this variable.
/// Only the server can change this variable.
/// </summary>
SERVER = 2,
/// <summary>
/// This can only be changed when not connected to a server.
/// This can only be changed when not connected to a server.
/// </summary>
NOT_CONNECTED = 4,
/// <summary>
/// Changing this var syncs between clients and server.
/// Changing this var syncs between clients and server.
/// </summary>
/// <remarks>
/// Should only ever be used on shared CVars.
/// </remarks>
REPLICATED = 8,
/// <summary>
/// Non-default values are saved to the configuration file.
/// Non-default values are saved to the configuration file.
/// </summary>
ARCHIVE = 16,
/// <summary>
/// Changing this var on the server notifies all clients, does nothing client-side.
/// Changing this var on the server notifies all clients, does nothing client-side.
/// </summary>
/// <remarks>
/// Should only ever be used on shared CVars.
/// </remarks>
NOTIFY = 32,
/// <summary>
@@ -60,15 +66,15 @@ namespace Robust.Shared.Configuration
CLIENTONLY = 128,
/// <summary>
/// CVar contains sensitive data that should not be accidentally leaked.
/// CVar contains sensitive data that should not be accidentally leaked.
/// </summary>
/// <remarks>
/// This currently hides the content of the cvar in the "cvar" command completions.
/// This currently hides the content of the cvar in the "cvar" command completions.
/// </remarks>
CONFIDENTIAL = 256,
/// <summary>
/// Only the client can change this variable.
/// Only the client can change this variable.
/// </summary>
CLIENT = 512,
}

View File

@@ -3,11 +3,29 @@ using JetBrains.Annotations;
namespace Robust.Shared.Configuration
{
/// <summary>
/// Abstract base class for <see cref="CVarDef{T}"/>. You shouldn't inherit this yourself and may be looking for
/// <see cref="M:Robust.Shared.Configuration.CVarDef.Create``1(System.String,``0,Robust.Shared.Configuration.CVar,System.String)"/>
/// </summary>
public abstract class CVarDef
{
/// <summary>
/// The default value of this CVar when no override is specified by configuration or the user.
/// </summary>
public object DefaultValue { get; }
/// <summary>
/// Flags for this CVar.
/// </summary>
public CVar Flags { get; }
/// <summary>
/// The name of this CVar. This needs to contain only printable characters.
/// Periods '.' are reserved. Everything before the last period is a nested table identifier,
/// everything after is the CVar name in the TOML document.
/// </summary>
public string Name { get; }
/// <summary>
/// The description of this CVar.
/// </summary>
public string? Desc { get; }
private protected CVarDef(string name, object defaultValue, CVar flags, string? desc)
@@ -18,6 +36,14 @@ namespace Robust.Shared.Configuration
Desc = desc;
}
/// <summary>
/// Creates a new CVar definition, for use in <see cref="CVarDefsAttribute"/>-annotated classes.
/// </summary>
/// <param name="name">See <see cref="Name"/>.</param>
/// <param name="defaultValue">See <see cref="DefaultValue"/>.</param>
/// <param name="flag">See <see cref="Flags"/>.</param>
/// <param name="desc">See <see cref="Desc"/>.</param>
/// <typeparam name="T">The type of the CVar, which can be any of: bool, int, long, float, string, any enum, and ushort.</typeparam>
public static CVarDef<T> Create<T>(
string name,
T defaultValue,
@@ -28,6 +54,12 @@ namespace Robust.Shared.Configuration
}
}
/// <summary>
/// Contains information defining a CVar for <see cref="IConfigurationManager"/>
/// </summary>
/// <typeparam name="T">The type of the CVar, which can be any of: bool, int, long, float, string, any enum, and ushort.</typeparam>
/// <seealso cref="CVarDefsAttribute"/>
/// <seealso cref="M:Robust.Shared.Configuration.IConfigurationManager.RegisterCVar``1(System.String,``0,Robust.Shared.Configuration.CVar,System.Action{``0})"/>
public sealed class CVarDef<T> : CVarDef where T : notnull
{
public new T DefaultValue { get; }
@@ -39,6 +71,28 @@ namespace Robust.Shared.Configuration
}
}
/// <summary>
/// Marks a static class as containing CVar definitions.
/// </summary>
/// <remarks>
/// <para>
/// There is no limit on the number of CVarDefs classes you can have, and all CVars will ultimately share the
/// same namespace regardless of which class they're in.
/// </para>
/// <para>
/// CVar definitions can be in any assembly, but should never be marked <see cref="CVar.REPLICATED"/> or
/// <see cref="CVar.NOTIFY"/> if not in a shared assembly.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// public static class MyCVars
/// {
/// public static readonly CVarDef&lt;bool&gt; MyEnabled =
/// CVarDef.Create("mycvars.enabled", true, CVar.SERVER, "Enables the thing.");
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Class)]
[MeansImplicitUse]
public sealed class CVarDefsAttribute : Attribute

View File

@@ -1,13 +1,17 @@
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Robust.Shared.Toolshed;
namespace Robust.Shared.Console
{
/// <summary>
/// Basic interface to handle console commands. Any class implementing this will be
/// registered with the console system through reflection.
/// Basic interface to handle console commands. Any class implementing this will be
/// registered with the console system through reflection.
/// </summary>
/// <remarks>
/// For server commands, it is much preferred to use <see cref="ToolshedCommand"/>.
/// </remarks>
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
public interface IConsoleCommand
{

View File

@@ -3,9 +3,16 @@ using System.Threading.Tasks;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Toolshed;
namespace Robust.Shared.Console;
/// <summary>
/// A variant on <see cref="IConsoleCommand"/> that has some built-in default localization strings.
/// </summary>
/// <remarks>
/// For server commands, it is much preferred to use <see cref="ToolshedCommand"/>.
/// </remarks>
public abstract class LocalizedCommands : IConsoleCommand
{
[Dependency] protected readonly ILocalizationManager LocalizationManager = default!;

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -7,7 +8,24 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
/// <inheritdoc cref="IComponent"/>
/// <summary>
/// The base class for all ECS components. A component is a piece of data, ideally without methods, that is
/// attached to or will be attached to some <see cref="EntityUid"/>. Entities do not have any data without
/// components to specify it.
/// </summary>
/// <remarks>
/// <para>
/// Components must be registered, usually with <see cref="RegisterComponentAttribute"/>, for the ECS to
/// recognize them.
/// </para>
/// <para>
/// Components implicitly have <see cref="DataDefinitionAttribute"/>, and are always valid for YAML ser/de.
/// </para>
/// </remarks>
/// <seealso cref="NetworkedComponentAttribute"/>
/// <seealso cref="DataFieldAttribute"/>
/// <seealso cref="ComponentState"/>
/// <seealso cref="UnsavedComponentAttribute"/>
[Reflect(false)]
[ImplicitDataDefinitionForInheritors]
public abstract partial class Component : IComponent

View File

@@ -6,14 +6,16 @@ namespace Robust.Shared.GameObjects;
/// <summary>
/// Marks a component as being automatically registered by <see cref="IComponentFactory.DoAutoRegistrations" />
/// </summary>
/// <seealso cref="Component"/>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(IComponent))]
[MeansImplicitUse]
public sealed class RegisterComponentAttribute : Attribute;
/// <summary>
/// Defines Name that this component is represented with in prototypes.
/// Defines Name that this component is represented with in prototypes.
/// </summary>
/// <seealso cref="Component"/>
[AttributeUsage(AttributeTargets.Class)]
public sealed class ComponentProtoNameAttribute(string prototypeName) : Attribute
{
@@ -21,8 +23,9 @@ public sealed class ComponentProtoNameAttribute(string prototypeName) : Attribut
}
/// <summary>
/// Marks a component as not being saved when saving maps/grids.
/// Marks a component as not being saved when saving maps/grids. This is useful for purely runtime information.
/// </summary>
/// <seealso cref="ComponentRegistration.Unsaved"/>
/// <seealso cref="Component"/>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class UnsavedComponentAttribute : Attribute;

View File

@@ -582,6 +582,10 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Exception fired whenever a component not recognized by the engine is encountered.
/// This is usually caused by forgetting <see cref="RegisterComponentAttribute"/>.
/// </summary>
[Serializable]
public sealed class UnknownComponentException : Exception
{
@@ -596,10 +600,17 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Exception fired if you try to register a new component after all registrations have been locked.
/// This is, typically, after network IDs have been assigned.
/// </summary>
public sealed class ComponentRegistrationLockException : Exception
{
}
/// <summary>
/// Exception fired when a component's name is entirely invalid. All component type names must end with Component.
/// </summary>
public sealed class InvalidComponentNameException : Exception
{
public InvalidComponentNameException(string message) : base(message)

View File

@@ -1,18 +1,37 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects;
/// <summary>
/// An abstract base class for a component's network state. For simple cases, you can automatically generate this using
/// <see cref="AutoGenerateComponentStateAttribute"/> and <see cref="AutoNetworkedFieldAttribute"/>.
/// </summary>
/// <remarks>
/// <para>
/// If your component's state is particularly complex, or you otherwise want manual control, you can implement this
/// directly and register necessary event handlers for <see cref="ComponentHandleState"/> and
/// <see cref="ComponentGetState"/>.
/// </para>
/// <para>
/// How state is actually applied for a component, and what it looks like, is user defined. For an example, look at
/// <see cref="OccluderComponent.OccluderComponentState"/>.
/// </para>
/// </remarks>
[RequiresSerializable]
[Serializable, NetSerializable]
[Virtual]
public abstract class ComponentState : IComponentState;
/// <summary>
/// Represents the state of a component for networking purposes.
/// Represents the state of a component for networking purposes.
/// </summary>
public interface IComponentState;
/// <summary>
/// Internal for RT, you probably want <see cref="IComponentDeltaState{TState}"/>.
/// </summary>
public interface IComponentDeltaState : IComponentState
{
public void ApplyToFullState(IComponentState fullState);
@@ -21,20 +40,20 @@ public interface IComponentDeltaState : IComponentState
}
/// <summary>
/// Interface for component states that only contain partial state data. The actual delta state class should be a
/// separate class from the full component states.
/// Interface for component states that only contain partial state data. The actual delta state class should be a
/// separate class from the full component states.
/// </summary>
/// <typeparam name="TState">The full-state class associated with this partial state</typeparam>
public interface IComponentDeltaState<TState> : IComponentDeltaState where TState: IComponentState
{
/// <summary>
/// This function will apply the current delta state to the provided full state, modifying it in the process.
/// This function will apply the current delta state to the provided full state, modifying it in the process.
/// </summary>
public void ApplyToFullState(TState fullState);
/// <summary>
/// This function should take in a full state and return a new full state with the current delta applied, WITHOUT
/// modifying the original input state.
/// This function should take in a full state and return a new full state with the current delta applied,
/// WITHOUT modifying the original input state.
/// </summary>
public TState CreateNewFullState(TState fullState);

View File

@@ -17,8 +17,11 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Stores the position and orientation of the entity.
/// Stores the relative and global position and orientation of the entity.<br/>
/// This also tracks the overall transform hierarchy, which allows entities to be children of other entities
/// and move when their parent moves cheaply.
/// </summary>
/// <seealso cref="SharedTransformSystem"/>
[RegisterComponent, NetworkedComponent]
public sealed partial class TransformComponent : Component, IComponentDebug
{

View File

@@ -0,0 +1,14 @@
<entries>
<entry name="EntityQueryResolve">
<summary>
Attempts to look up <typeparamref name="TComp1"/> on the given entity, writing it into the given space
if it finds it and the space was not already empty.
</summary>
<remarks>
This is preferable to
<see cref="M:Robust.Shared.GameObjects.EntityQuery`1.TryComp(Robust.Shared.GameObjects.EntityUid,`0@)"/> if it
is erroneous for the component to not be present, but you don't want to crash the game.<br/>
This is also preferable if you may have already looked up the component, saving on lookup time.
</remarks>
</entry>
</entries>

View File

@@ -5,6 +5,16 @@ using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
/// <summary>
/// An <see cref="EntityUid"/> with an associated component (or components) looked up in advance.
/// This is used by APIs to strongly type them over a required component, and can easily be obtained for an
/// EntityUid using <see cref="M:Robust.Shared.GameObjects.EntityQuery`1.Get(Robust.Shared.GameObjects.EntityUid)"/>,
/// <see cref="M:Robust.Shared.GameObjects.EntityQuery`1.TryComp(Robust.Shared.GameObjects.EntityUid,`0@)"/>,
/// and other <see cref="EntityQuery{TComp1}"/> methods.
/// </summary>
/// <remarks>
/// This type exists for up to eight (i.e. <c>Entity&lt;T1, T2, T3, T4, T5, T6, T7, T8&gt;</c>) parameters.
/// </remarks>
[NotYamlSerializable]
public record struct Entity<T> : IFluentEntityUid, IAsType<EntityUid>
where T : IComponent?
@@ -53,6 +63,7 @@ public record struct Entity<T> : IFluentEntityUid, IAsType<EntityUid>
public readonly EntityUid AsType() => Owner;
}
/// <inheritdoc cref="Entity{T}"/>
[NotYamlSerializable]
public record struct Entity<T1, T2> : IFluentEntityUid, IAsType<EntityUid>
where T1 : IComponent? where T2 : IComponent?
@@ -124,6 +135,7 @@ public record struct Entity<T1, T2> : IFluentEntityUid, IAsType<EntityUid>
public readonly EntityUid AsType() => Owner;
}
/// <inheritdoc cref="Entity{T}"/>
[NotYamlSerializable]
public record struct Entity<T1, T2, T3> : IFluentEntityUid, IAsType<EntityUid>
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent?
@@ -231,6 +243,7 @@ public record struct Entity<T1, T2, T3> : IFluentEntityUid, IAsType<EntityUid>
public readonly EntityUid AsType() => Owner;
}
/// <inheritdoc cref="Entity{T}"/>
[NotYamlSerializable]
public record struct Entity<T1, T2, T3, T4> : IFluentEntityUid, IAsType<EntityUid>
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent?
@@ -362,6 +375,7 @@ public record struct Entity<T1, T2, T3, T4> : IFluentEntityUid, IAsType<EntityUi
public readonly EntityUid AsType() => Owner;
}
/// <inheritdoc cref="Entity{T}"/>
[NotYamlSerializable]
public record struct Entity<T1, T2, T3, T4, T5> : IFluentEntityUid, IAsType<EntityUid>
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent?
@@ -517,6 +531,7 @@ public record struct Entity<T1, T2, T3, T4, T5> : IFluentEntityUid, IAsType<Enti
public readonly EntityUid AsType() => Owner;
}
/// <inheritdoc cref="Entity{T}"/>
[NotYamlSerializable]
public record struct Entity<T1, T2, T3, T4, T5, T6> : IFluentEntityUid, IAsType<EntityUid>
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent? where T6 : IComponent?
@@ -696,6 +711,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6> : IFluentEntityUid, IAsType<
public readonly EntityUid AsType() => Owner;
}
/// <inheritdoc cref="Entity{T}"/>
[NotYamlSerializable]
public record struct Entity<T1, T2, T3, T4, T5, T6, T7> : IFluentEntityUid, IAsType<EntityUid>
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent? where T6 : IComponent? where T7 : IComponent?
@@ -899,6 +915,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7> : IFluentEntityUid, IAsT
public readonly EntityUid AsType() => Owner;
}
/// <inheritdoc cref="Entity{T}"/>
[NotYamlSerializable]
public record struct Entity<T1, T2, T3, T4, T5, T6, T7, T8> : IFluentEntityUid, IAsType<EntityUid>
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent? where T6 : IComponent? where T7 : IComponent? where T8 : IComponent?

View File

@@ -67,7 +67,8 @@ namespace Robust.Shared.GameObjects
/// <remarks>
/// This has a very specific purpose, and has massive potential to be abused.
/// DO NOT USE THIS IN CONTENT UNLESS YOU KNOW WHAT YOU'RE DOING, the only reason it's not internal
/// is because of the component network source generator.
/// is because of the component network source generator.<br/>
/// This may be removed, modified, or pulled back internal at ANY TIME.
/// </remarks>
public void RaiseComponentEvent<TEvent, TComponent>(EntityUid uid, TComponent component, TEvent args)
where TEvent : notnull

View File

@@ -21,7 +21,6 @@ using Robust.Shared.Exceptions;
namespace Robust.Shared.GameObjects
{
/// <inheritdoc />
public partial class EntityManager
{
[IoC.Dependency] private readonly IComponentFactory _componentFactory = default!;
@@ -1756,6 +1755,38 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// An index of all entities with a given component, avoiding looking up the component's storage every time.
/// Using these saves on dictionary lookups, making your code slightly more efficient, and ties in nicely with
/// <see cref="Entity{T}"/>.
/// </summary>
/// <typeparam name="TComp1">Any component type.</typeparam>
/// <example>
/// <code>
/// public sealed class MySystem : EntitySystem
/// {
/// private EntityQuery&lt;TransformComponent&gt; _transforms = default!;
/// <br/>
/// public override void Initialize()
/// {
/// _transforms = GetEntityQuery&lt;TransformComponent&gt;();
/// }
/// <br/>
/// public void DoThings(EntityUid myEnt)
/// {
/// var ent = _transforms.Get(myEnt);
/// // ...
/// }
/// }
/// </code>
/// </example>
/// <remarks>
/// Queries hold references to <see cref="IEntityManager"/> internals, and are always up to date with the world.
/// They can not however perform mutation, if you need to add or remove components you must use
/// <see cref="EntitySystem"/> or <see cref="IEntityManager"/> methods.
/// </remarks>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.GetEntityQuery``1">EntitySystem.GetEntityQuery()</seealso>
/// <seealso cref="M:Robust.Shared.GameObjects.EntityManager.GetEntityQuery``1">EntityManager.GetEntityQuery()</seealso>
public readonly struct EntityQuery<TComp1> where TComp1 : IComponent
{
private readonly Dictionary<EntityUid, IComponent> _traitDict;
@@ -1767,6 +1798,18 @@ namespace Robust.Shared.GameObjects
_sawmill = sawmill;
}
/// <summary>
/// Gets <typeparamref name="TComp1"/> for an entity, throwing if it can't find it.
/// </summary>
/// <param name="uid">The entity to do a lookup for.</param>
/// <returns>The located component.</returns>
/// <exception cref="KeyNotFoundException">Thrown if the entity does not have a component of type <typeparamref name="TComp1"/>.</exception>
/// <seealso cref="M:Robust.Shared.GameObjects.IEntityManager.GetComponent``1(Robust.Shared.GameObjects.EntityUid)">
/// IEntityManager.GetComponent&lt;T&gt;(EntityUid)
/// </seealso>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.Comp``1(Robust.Shared.GameObjects.EntityUid)">
/// EntitySystem.Comp&lt;T&gt;(EntityUid)
/// </seealso>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1 GetComponent(EntityUid uid)
@@ -1777,6 +1820,7 @@ namespace Robust.Shared.GameObjects
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}");
}
/// <inheritdoc cref="GetComponent"/>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public Entity<TComp1> Get(EntityUid uid)
{
@@ -1786,6 +1830,22 @@ namespace Robust.Shared.GameObjects
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}");
}
/// <summary>
/// Gets <typeparamref name="TComp1"/> for an entity, if it's present.
/// </summary>
/// <remarks>
/// If it is strictly errorenous for a component to not be present, you may want to use
/// <see cref="Resolve(Robust.Shared.GameObjects.EntityUid,ref TComp1?,bool)"/> instead.
/// </remarks>
/// <param name="uid">The entity to do a lookup for.</param>
/// <param name="component">The located component, if any.</param>
/// <returns>Whether the component was found.</returns>
/// <seealso cref="M:Robust.Shared.GameObjects.IEntityManager.TryGetComponent``1(Robust.Shared.GameObjects.EntityUid,``0@)">
/// IEntityManager.TryGetComponent&lt;T&gt;(EntityUid, out T?)
/// </seealso>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.TryComp``1(Robust.Shared.GameObjects.EntityUid,``0@)">
/// EntitySystem.TryComp&lt;T&gt;(EntityUid, out T?)
/// </seealso>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
@@ -1799,6 +1859,7 @@ namespace Robust.Shared.GameObjects
return TryGetComponent(uid.Value, out component);
}
/// <inheritdoc cref="TryGetComponent(Robust.Shared.GameObjects.EntityUid?,out TComp1?)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryGetComponent(EntityUid uid, [NotNullWhen(true)] out TComp1? component)
@@ -1813,24 +1874,40 @@ namespace Robust.Shared.GameObjects
return false;
}
/// <inheritdoc cref="TryGetComponent(Robust.Shared.GameObjects.EntityUid?,out TComp1?)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryComp(EntityUid uid, [NotNullWhen(true)] out TComp1? component)
=> TryGetComponent(uid, out component);
/// <inheritdoc cref="TryGetComponent(Robust.Shared.GameObjects.EntityUid?,out TComp1?)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
=> TryGetComponent(uid, out component);
/// <summary>
/// Tests if the given entity has <typeparamref name="TComp1"/>.
/// </summary>
/// <param name="uid">The entity to do a lookup for.</param>
/// <returns>Whether the component exists for that entity.</returns>
/// <remarks>If you immediately need to then look up that component, it's more efficient to use <see cref="TryComp(Robust.Shared.GameObjects.EntityUid,out TComp1?)"/>.</remarks>
/// <seealso cref="M:Robust.Shared.GameObjects.IEntityManager.HasComponent``1(Robust.Shared.GameObjects.EntityUid)">
/// IEntityManager.HasComponent&lt;T&gt;(EntityUid)
/// </seealso>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.HasComp``1(Robust.Shared.GameObjects.EntityUid)">
/// EntitySystem.HasComp&lt;T&gt;(EntityUid)
/// </seealso>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComp(EntityUid uid) => HasComponent(uid);
/// <inheritdoc cref="HasComp(Robust.Shared.GameObjects.EntityUid)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComp([NotNullWhen(true)] EntityUid? uid) => HasComponent(uid);
/// <inheritdoc cref="HasComp(Robust.Shared.GameObjects.EntityUid)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComponent(EntityUid uid)
@@ -1838,6 +1915,7 @@ namespace Robust.Shared.GameObjects
return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted;
}
/// <inheritdoc cref="HasComp(Robust.Shared.GameObjects.EntityUid)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComponent([NotNullWhen(true)] EntityUid? uid)
@@ -1845,6 +1923,14 @@ namespace Robust.Shared.GameObjects
return uid != null && HasComponent(uid.Value);
}
/// <include file='Docs.xml' path='entries/entry[@name="EntityQueryResolve"]/*'/>
/// <param name="uid">The entity to do a lookup for.</param>
/// <param name="component">The space to write the component into if found.</param>
/// <param name="logMissing">Whether to log if the component is missing, for diagnostics.</param>
/// <returns>Whether the component was found.</returns>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.Resolve``1(Robust.Shared.GameObjects.EntityUid,``0@,System.Boolean)">
/// EntitySystem.Resolve&lt;T&gt;(EntityUid, out T?)
/// </seealso>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
{
@@ -1868,12 +1954,24 @@ namespace Robust.Shared.GameObjects
return false;
}
/// <include file='Docs.xml' path='entries/entry[@name="EntityQueryResolve"]/*'/>
/// <param name="entity">The space to write the component into if found.</param>
/// <param name="logMissing">Whether to log if the component is missing, for diagnostics.</param>
/// <returns>Whether the component was found.</returns>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.Resolve``1(Robust.Shared.GameObjects.EntityUid,``0@,System.Boolean)">
/// EntitySystem.Resolve&lt;T&gt;(EntityUid, out T?)
/// </seealso>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Resolve(ref Entity<TComp1?> entity, bool logMissing = true)
{
return Resolve(entity.Owner, ref entity.Comp, logMissing);
}
/// <summary>
/// Gets <typeparamref name="TComp1"/> for an entity if it's present, or null if it's not.
/// </summary>
/// <param name="uid">The entity to do the lookup on.</param>
/// <returns>The component, if it exists.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1? CompOrNull(EntityUid uid)
@@ -1884,6 +1982,7 @@ namespace Robust.Shared.GameObjects
return default;
}
/// <inheritdoc cref="GetComponent"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1 Comp(EntityUid uid)
@@ -2113,8 +2212,35 @@ namespace Robust.Shared.GameObjects
#region Query
/// <summary>
/// Returns all matching unpaused components.
/// Iterates all entities that have the given components, including the components themselves, but only if
/// the entity they're on is not <see cref="EntitySystem.Paused">Paused</see>.
/// </summary>
/// <remarks>
/// <para>
/// Internally, checks for what components exist are done in the order of the generics, so the least frequently
/// occurring component should always be the first argument, and so on.
/// </para>
/// <para>
/// This type exists for <b>up to</b> four components, TComp1 through TComp4, as generic arguments.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// // within a system.
/// var enumerator = EntityQueryEnumerator&lt;TransformComponent&gt;();
/// <br/>
/// while (enumerator.MoveNext(out var ent, out var xform))
/// {
/// // ... do work with the entity we just found.
/// }
/// </code>
/// </example>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.EntityQueryEnumerator``1">
/// EntitySystem.EntityQueryEnumerator&lt;TComp1, ...&gt;()
/// </seealso>
/// <seealso cref="M:Robust.Shared.GameObjects.IEntityManager.EntityQueryEnumerator``1">
/// IEntityManager.EntityQueryEnumerator&lt;TComp1, ...&gt;()
/// </seealso>
public struct EntityQueryEnumerator<TComp1> : IDisposable
where TComp1 : IComponent
{
@@ -2129,6 +2255,12 @@ namespace Robust.Shared.GameObjects
_metaQuery = metaQuery;
}
/// <summary>
/// Provides the next entity and component in the enumerator, if there are still more to iterate through.
/// </summary>
/// <param name="uid">The found entity, if any.</param>
/// <param name="comp1">A component on the found entity.</param>
/// <returns>Whether the enumerator was empty (and as such no entity nor component were returned)</returns>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1)
{
while (true)
@@ -2170,9 +2302,7 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Returns all matching unpaused components.
/// </summary>
/// <inheritdoc cref="EntityQueryEnumerator{TComp1}"/>
public struct EntityQueryEnumerator<TComp1, TComp2> : IDisposable
where TComp1 : IComponent
where TComp2 : IComponent
@@ -2191,6 +2321,8 @@ namespace Robust.Shared.GameObjects
_metaQuery = metaQuery;
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityQueryEnumerator`1.MoveNext(Robust.Shared.GameObjects.EntityUid@,`0@)"/>
/// <param name="comp2">A component on the found entity.</param>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2)
{
while (true)
@@ -2239,9 +2371,7 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Returns all matching unpaused components.
/// </summary>
/// <inheritdoc cref="EntityQueryEnumerator{TComp1}"/>
public struct EntityQueryEnumerator<TComp1, TComp2, TComp3> : IDisposable
where TComp1 : IComponent
where TComp2 : IComponent
@@ -2264,6 +2394,9 @@ namespace Robust.Shared.GameObjects
_metaQuery = metaQuery;
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityQueryEnumerator`1.MoveNext(Robust.Shared.GameObjects.EntityUid@,`0@)"/>
/// <param name="comp2">A component on the found entity.</param>
/// <param name="comp3">A component on the found entity.</param>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3)
{
while (true)
@@ -2322,9 +2455,7 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Returns all matching unpaused components.
/// </summary>
/// <inheritdoc cref="EntityQueryEnumerator{TComp1}"/>
public struct EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4> : IDisposable
where TComp1 : IComponent
where TComp2 : IComponent
@@ -2351,6 +2482,10 @@ namespace Robust.Shared.GameObjects
_metaQuery = metaQuery;
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityQueryEnumerator`1.MoveNext(Robust.Shared.GameObjects.EntityUid@,`0@)"/>
/// <param name="comp2">A component on the found entity.</param>
/// <param name="comp3">A component on the found entity.</param>
/// <param name="comp4">A component on the found entity.</param>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3, [NotNullWhen(true)] out TComp4? comp4)
{
while (true)
@@ -2422,8 +2557,35 @@ namespace Robust.Shared.GameObjects
#region All query
/// <summary>
/// Returns all matching components, paused or not.
/// Iterates all entities that have the given components, including the components themselves, regardless
/// of if the entity is <see cref="EntitySystem.Paused">Paused</see>.
/// </summary>
/// <remarks>
/// <para>
/// Internally, checks for what components exist are done in the order of the generics, so the least frequently
/// occurring component should always be the first argument, and so on.
/// </para>
/// <para>
/// This type exists for <b>up to</b> four components, TComp1 through TComp4, as generic arguments.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// // within a system.
/// var enumerator = AllEntityQueryEnumerator&lt;TransformComponent&gt;();
/// <br/>
/// while (enumerator.MoveNext(out var ent, out var xform))
/// {
/// // ... do work with the entity we just found.
/// }
/// </code>
/// </example>
/// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.AllEntityQuery``1">
/// EntitySystem.AllEntityQuery&lt;TComp1, ...&gt;()
/// </seealso>
/// <seealso cref="M:Robust.Shared.GameObjects.IEntityManager.AllEntityQueryEnumerator``1">
/// IEntityManager.AllEntityQueryEnumerator&lt;TComp1, ...&gt;()
/// </seealso>
public struct AllEntityQueryEnumerator<TComp1> : IDisposable
where TComp1 : IComponent
{
@@ -2435,6 +2597,7 @@ namespace Robust.Shared.GameObjects
_traitDict = traitDict.GetEnumerator();
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityQueryEnumerator`1.MoveNext(Robust.Shared.GameObjects.EntityUid@,`0@)"/>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1)
{
while (true)
@@ -2471,9 +2634,7 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Returns all matching components, paused or not.
/// </summary>
/// <inheritdoc cref="AllEntityQueryEnumerator{TComp1}"/>
public struct AllEntityQueryEnumerator<TComp1, TComp2> : IDisposable
where TComp1 : IComponent
where TComp2 : IComponent
@@ -2489,6 +2650,7 @@ namespace Robust.Shared.GameObjects
_traitDict2 = traitDict2;
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityQueryEnumerator`2.MoveNext(Robust.Shared.GameObjects.EntityUid@,`0@,`1@)"/>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2)
{
while (true)
@@ -2532,9 +2694,7 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Returns all matching components, paused or not.
/// </summary>
/// <inheritdoc cref="AllEntityQueryEnumerator{TComp1}"/>
public struct AllEntityQueryEnumerator<TComp1, TComp2, TComp3> : IDisposable
where TComp1 : IComponent
where TComp2 : IComponent
@@ -2554,6 +2714,7 @@ namespace Robust.Shared.GameObjects
_traitDict3 = traitDict3;
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityQueryEnumerator`3.MoveNext(Robust.Shared.GameObjects.EntityUid@,`0@,`1@,`2@)"/>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3)
{
while (true)
@@ -2607,9 +2768,7 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Returns all matching components, paused or not.
/// </summary>
/// <inheritdoc cref="AllEntityQueryEnumerator{TComp1}"/>
public struct AllEntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4> : IDisposable
where TComp1 : IComponent
where TComp2 : IComponent
@@ -2633,6 +2792,7 @@ namespace Robust.Shared.GameObjects
_traitDict4 = traitDict4;
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityQueryEnumerator`4.MoveNext(Robust.Shared.GameObjects.EntityUid@,`0@,`1@,`2@,`3@)"/>
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3, [NotNullWhen(true)] out TComp4? comp4)
{
while (true)

View File

@@ -31,8 +31,14 @@ namespace Robust.Shared.GameObjects
protected IComponentFactory Factory => EntityManager.ComponentFactory;
/// <summary>
/// A logger sawmill for logging debug/informational messages into.
/// </summary>
public ISawmill Log { get; private set; } = default!;
/// <summary>
/// The name for the preprovided log sawmill <see cref="Log"/>.
/// </summary>
protected virtual string SawmillName
{
get
@@ -59,9 +65,19 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// A list of systems this system MUST update after. This determines the overall update order for systems.
/// </summary>
protected internal List<Type> UpdatesAfter { get; } = new();
/// <summary>
/// A list of systems this system MUST update before. This determines the overall update order for systems.
/// </summary>
protected internal List<Type> UpdatesBefore { get; } = new();
/// <summary>
/// Whether this system will also tick outside of predicted ticks on the client.
/// </summary>
/// <seealso cref="Update"/>
public bool UpdatesOutsidePrediction { get; protected internal set; }
IEnumerable<Type> IEntitySystem.UpdatesAfter => UpdatesAfter;
@@ -97,36 +113,69 @@ namespace Robust.Shared.GameObjects
#region Event Proxy
/// <summary>
/// Raise an event on the event bus, broadcasted locally to all listeners by value.
/// </summary>
/// <param name="message">The message to send, consuming it.</param>
/// <typeparam name="T">The type of the message to send.</typeparam>
protected void RaiseLocalEvent<T>(T message) where T : notnull
{
EntityManager.EventBusInternal.RaiseEvent(EventSource.Local, message);
}
/// <summary>
/// Raise an event on the event bus, broadcasted locally to all listeners by ref.
/// </summary>
/// <param name="message">The location of a message, to be sent by reference and modified in place.</param>
/// <typeparam name="T">The type of the message to send.</typeparam>
protected void RaiseLocalEvent<T>(ref T message) where T : notnull
{
EntityManager.EventBusInternal.RaiseEvent(EventSource.Local, ref message);
}
/// <summary>
/// Raise an event of unknown type on the event bus, broadcasted locally to all listeners of its underlying
/// type.
/// </summary>
/// <param name="message">The message to send.</param>
protected void RaiseLocalEvent(object message)
{
EntityManager.EventBusInternal.RaiseEvent(EventSource.Local, message);
}
/// <summary>
/// Queue an event to broadcast locally at the end of the tick.
/// </summary>
/// <param name="message">The entity event to raise.</param>
protected void QueueLocalEvent(EntityEventArgs message)
{
EntityManager.EventBusInternal.QueueEvent(EventSource.Local, message);
}
/// <summary>
/// Queue a networked event to be broadcast to all clients at the end of the tick.
/// </summary>
/// <param name="message">The entity event to raise.</param>
protected void RaiseNetworkEvent(EntityEventArgs message)
{
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message);
}
/// <summary>
/// Queue a networked event to be sent to a specific connection at the end of the tick.
/// </summary>
/// <param name="message">The entity event to raise.</param>
/// <param name="channel">The session to send the event to.</param>
protected void RaiseNetworkEvent(EntityEventArgs message, INetChannel channel)
{
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, channel);
}
/// <summary>
/// Queue a networked event to be sent to a specific session at the end of the tick.
/// </summary>
/// <param name="message">The entity event to raise.</param>
/// <param name="session">The session to send the event to.</param>
protected void RaiseNetworkEvent(EntityEventArgs message, ICommonSession session)
{
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, session.Channel);
@@ -135,7 +184,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Raises a networked event with some filter.
/// </summary>
/// <param name="message">The event to send</param>
/// <param name="message">The entity event to raise.</param>
/// <param name="filter">The filter that specifies recipients</param>
/// <param name="recordReplay">Optional bool specifying whether or not to save this event to replays.</param>
protected void RaiseNetworkEvent(EntityEventArgs message, Filter filter, bool recordReplay = true)
@@ -149,34 +198,68 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Raise a networked event for a given recipient entity, if there's a client attached.
/// </summary>
/// <param name="message">The entity event to raise.</param>
/// <param name="recipient">The entity to look up a session to send to on.</param>
protected void RaiseNetworkEvent(EntityEventArgs message, EntityUid recipient)
{
if (_playerMan.TryGetSessionByEntity(recipient, out var session))
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, session.Channel);
}
/// <summary>
/// Raise a local event, optionally broadcasted, on a specific entity by value.
/// </summary>
/// <param name="uid">The entity to raise the event on.</param>
/// <param name="args">The event to raise, sent by value.</param>
/// <param name="broadcast">Whether to broadcast the event alongside raising it directed.</param>
/// <typeparam name="TEvent">The type of the event to raise.</typeparam>
protected void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = false)
where TEvent : notnull
{
EntityManager.EventBusInternal.RaiseLocalEvent(uid, args, broadcast);
}
/// <summary>
/// Raise a local event, optionally broadcasted, on a specific entity by value.
/// This raise the event for <paramref name="args"/>' underlying concrete type.
/// </summary>
/// <param name="uid">The entity to raise the event on.</param>
/// <param name="args">The event to raise, sent by value.</param>
/// <param name="broadcast">Whether to broadcast the event alongside raising it directed.</param>
protected void RaiseLocalEvent(EntityUid uid, object args, bool broadcast = false)
{
EntityManager.EventBusInternal.RaiseLocalEvent(uid, args, broadcast);
}
/// <summary>
/// Raise a local event, optionally broadcasted, on a specific entity by ref.
/// </summary>
/// <param name="uid">The entity to raise the event on.</param>
/// <param name="args">The event to raise, sent by ref.</param>
/// <param name="broadcast">Whether to broadcast the event alongside raising it directed.</param>
/// <typeparam name="TEvent">The type of the event to raise.</typeparam>
protected void RaiseLocalEvent<TEvent>(EntityUid uid, ref TEvent args, bool broadcast = false)
where TEvent : notnull
{
EntityManager.EventBusInternal.RaiseLocalEvent(uid, ref args, broadcast);
}
/// <summary>
/// Raise a local event, optionally broadcasted, on a specific entity by ref.
/// This raise the event for <paramref name="args"/>' underlying concrete type.
/// </summary>
/// <param name="uid">The entity to raise the event on.</param>
/// <param name="args">The event to raise, sent by ref.</param>
/// <param name="broadcast">Whether to broadcast the event alongside raising it directed.</param>
protected void RaiseLocalEvent(EntityUid uid, ref object args, bool broadcast = false)
{
EntityManager.EventBusInternal.RaiseLocalEvent(uid, ref args, broadcast);
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityEventBus.RaiseComponentEvent``2(Robust.Shared.GameObjects.EntityUid,``1,``0@)"/>
protected void RaiseComponentEvent<TEvent, TComp>(EntityUid uid, TComp comp, ref TEvent args)
where TEvent : notnull
where TComp : IComponent
@@ -184,6 +267,7 @@ namespace Robust.Shared.GameObjects
EntityManager.EventBusInternal.RaiseComponentEvent(uid, comp, ref args);
}
/// <inheritdoc cref="M:Robust.Shared.GameObjects.EntityEventBus.RaiseComponentEvent``2(Robust.Shared.GameObjects.EntityUid,``1,``0@)"/>
public void RaiseComponentEvent<TEvent>(EntityUid uid, IComponent component, ref TEvent args)
where TEvent : notnull
{

View File

@@ -9,9 +9,27 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// This type contains a network identification number of an entity.
/// This can be used by the EntityManager to access an entity
/// This type contains the unique identifier for a given entity.
/// This can be used with and obtained from <see cref="IEntityManager"/> to manipulate, query, and otherwise
/// work with entities and their components.
/// </summary>
/// <remarks>
/// <para>
/// An entity is a unique identifier (this type) and a collection of assorted <see cref="Component"/>s that are
/// attached to it. Components provide data to describe the entity, and entities+components are operated on by
/// <see cref="EntitySystem"/>s.
/// </para>
/// <para>
/// EntityUids are not guaranteed to be unique across individual instances of the game, or individual instances
/// of <see cref="IEntityManager"/>. For network identification, see <see cref="NetEntity"/>, and for global
/// uniqueness across time you'll need to make something yourself.
/// </para>
/// <para>
/// Sharing EntityUids between <see cref="IEntityManager"/>s, or otherwise summoning IDs from thin air, is
/// effectively undefined behavior and most likely will refer to some random other entity that may or may not
/// still exist.
/// </para>
/// </remarks>
[CopyByRef]
public readonly struct EntityUid : IEquatable<EntityUid>, IComparable<EntityUid>, ISpanFormattable
{
@@ -28,7 +46,7 @@ namespace Robust.Shared.GameObjects
public static readonly EntityUid FirstUid = new(1);
/// <summary>
/// Creates an instance of this structure, with the given network ID.
/// Creates an instance of this structure, with the given unique id.
/// </summary>
public EntityUid(int id)
{
@@ -58,8 +76,7 @@ namespace Robust.Shared.GameObjects
}
/// <summary>
/// Checks if the ID value is valid. Does not check if it identifies
/// a valid Entity.
/// Checks if the ID value is valid at all, but does not check if it identifies a currently living entity.
/// </summary>
[Pure]
public bool IsValid()

View File

@@ -5,10 +5,10 @@ using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
{
/// <remarks>
/// Base component for the ECS system.
/// Instances are dynamically instantiated by a <c>ComponentFactory</c>, and will have their IoC Dependencies resolved.
/// </remarks>
/// <summary>
/// The base interface for an ECS component. You probably want <see cref="Component"/>.<br/>
/// </summary>
/// <include file='../Serialization/Manager/Attributes/Docs.xml' path='entries/entry[@name="ImpliesDataDefinition"]/*'/>
[ImplicitDataDefinitionForInheritors]
public partial interface IComponent
{
@@ -50,18 +50,21 @@ namespace Robust.Shared.GameObjects
EntityUid Owner { get; set; }
/// <summary>
/// Component has been (or is currently being) initialized.
/// Whether the component has been or is being initialized.
/// </summary>
/// <seealso cref="ComponentInit"/>
bool Initialized { get; }
/// <summary>
/// This is true when the component is active.
/// </summary>
/// <seealso cref="ComponentStartup"/>
bool Running { get; }
/// <summary>
/// True if the component has been removed from its owner, AKA deleted.
/// </summary>
/// <seealso cref="ComponentRemove"/>
bool Deleted { get; }
/// <summary>

View File

@@ -1,7 +1,14 @@
namespace Robust.Shared.GameObjects
{
/// <summary>
/// An ages old interface to add extra debug data to the "entfo" server-side command.
/// </summary>
public partial interface IComponentDebug : IComponent
{
/// <summary>
/// Returns a debug string to print to the console or otherwise display from debug tools.
/// This can contain newlines.
/// </summary>
string GetDebugString();
}
}

View File

@@ -1,10 +0,0 @@
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Raised directed on an entity when the map is initialized.
/// </summary>
[ComponentEvent(Exclusive = false)]
public sealed class MapInitEvent : EntityEventArgs
{
}
}

View File

@@ -0,0 +1,9 @@
namespace Robust.Shared.GameObjects;
/// <summary>
/// Raised directed on an entity when the map is initialized.
/// </summary>
[ComponentEvent(Exclusive = false)]
public sealed class MapInitEvent : EntityEventArgs
{
}

View File

@@ -14,6 +14,9 @@ using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Manages all the grids and maps in the ECS, providing methods to create and modify them.
/// </summary>
public abstract partial class SharedMapSystem : EntitySystem
{
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;

View File

@@ -16,7 +16,7 @@ namespace Robust.Shared.Map
public delegate bool GridCallback<TState>(EntityUid uid, MapGridComponent grid, ref TState state);
/// <summary>
/// This manages all of the grids in the world.
/// This manages all the grids and maps in the world. Largely superseded by <see cref="SharedMapSystem"/>.
/// </summary>
public interface IMapManager
{

View File

@@ -13,6 +13,9 @@ namespace Robust.Shared.Map
/// <summary>
/// The numeric tile ID used to refer to this tile inside the map datastructure.
/// </summary>
/// <remarks>
/// The engine does not automatically generate these IDs, games are responsible for assigning them.
/// </remarks>
ushort TileId { get; }
/// <summary>

View File

@@ -1,11 +1,26 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Robust.Shared.Map
{
/// <summary>
/// Uniquely identifies a map.
/// </summary>
/// <remarks>
/// All maps, aside from <see cref="Nullspace"/>, are also entities. When writing generic code it's usually
/// preferable to use <see cref="EntityUid"/> or <see cref="Entity{T}"/> instead.
/// </remarks>
/// <seealso cref="IMapManager"/>
/// <seealso cref="SharedMapSystem"/>
[Serializable, NetSerializable]
public readonly struct MapId : IEquatable<MapId>
{
/// <summary>
/// The equivalent of <c>null</c> for maps. There is no map entity assigned to this and anything here is
/// a root (has no parent) for the <see cref="TransformComponent">transform hierarchy</see>.<br/>
/// All map entities live in nullspace and function as roots, for example.
/// </summary>
public static readonly MapId Nullspace = new(0);
internal readonly int Value;

View File

@@ -23,12 +23,12 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
public readonly byte Flags;
/// <summary>
/// Variant of this tile to render.
/// Variant of this tile to render.
/// </summary>
public readonly byte Variant;
/// <summary>
/// Rotation and mirroring of this tile to render. 0-3 is normal, 4-7 is mirrored.
/// Rotation and mirroring of this tile to render. 0-3 is normal, 4-7 is mirrored.
/// </summary>
public readonly byte RotationMirroring;

View File

@@ -1,5 +1,8 @@
namespace Robust.Shared.Network
{
/// <summary>
/// The possible kinds of login a user can engage in.
/// </summary>
public enum LoginType : byte
{
/// <summary>

View File

@@ -3,6 +3,23 @@ using Robust.Shared.Serialization;
namespace Robust.Shared.Network
{
/// <summary>
/// A unique identifier for a given user's account.
/// </summary>
/// <remarks>
/// <para>
/// If connected to auth and auth is mandatory, this is guaranteed to be
/// <b>globally unique</b> within that auth service, duplicate players will not exist.
/// </para>
/// <para>
/// Similarly, the engine assumes that if the user is <see cref="LoginType.GuestAssigned">a known guest with an
/// assigned ID</see>, their ID is also globally unique.
/// </para>
/// <para>
/// This is independent of username, and in the current auth implementation users can freely change username.
/// Think of this as a way to refer to a given account regardless of what it's named.
/// </para>
/// </remarks>
[Serializable, NetSerializable]
public struct NetUserId : IEquatable<NetUserId>, ISelfSerialize
{

View File

@@ -3,9 +3,16 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.Player;
/// <summary>
/// This component is added to entities that are currently controlled by a player and is removed when the player is detached.
/// </summary>
/// <seealso cref="M:Robust.Shared.Player.ISharedPlayerManager.SetAttachedEntity(Robust.Shared.Player.ICommonSession,System.Nullable{Robust.Shared.GameObjects.EntityUid},Robust.Shared.Player.ICommonSession@,System.Boolean)"/>
[RegisterComponent, UnsavedComponent]
public sealed partial class ActorComponent : Component
{
/// <summary>
/// The player session currently attached to the entity.
/// </summary>
[ViewVariables]
public ICommonSession PlayerSession { get; internal set; } = default!;
}

View File

@@ -22,6 +22,9 @@ public sealed class ActorSystem : EntitySystem
_playerManager.SetAttachedEntity(component.PlayerSession, null);
}
/// <summary>
/// Retrieves the session on a given entity, if one exists.
/// </summary>
[PublicAPI]
public bool TryGetSession(EntityUid? uid, out ICommonSession? session)
{
@@ -35,6 +38,9 @@ public sealed class ActorSystem : EntitySystem
return false;
}
/// <summary>
/// Retrieves the session on a given entity, if one exists.
/// </summary>
[PublicAPI]
[Pure]
public ICommonSession? GetSession(EntityUid? uid)

View File

@@ -8,69 +8,85 @@ using Robust.Shared.Network;
namespace Robust.Shared.Player;
/// <summary>
/// Common info between client and server sessions.
/// Common info between client and server sessions.
/// </summary>
/// <seealso cref="ISharedPlayerManager"/>
public interface ICommonSession
{
/// <summary>
/// Status of the session.
/// Status of the session, dictating the connection state.
/// </summary>
SessionStatus Status { get; }
/// <summary>
/// Entity UID that this session is represented by in the world, if any.
/// Entity UID that this session is represented by in the world, if any. Any attached entity will have
/// <see cref="ActorComponent"/>.
/// </summary>
/// <seealso cref="ActorSystem"/>
EntityUid? AttachedEntity { get; }
/// <summary>
/// The UID of this session.
/// The unique user ID of this session.
/// </summary>
/// <remarks>
/// If this user's <see cref="AuthType"/> is <see cref="LoginType.LoggedIn"/> or
/// <see cref="LoginType.GuestAssigned"/> (<see cref="LoginTypeExt.HasStaticUserId"/>),
/// their user id is globally unique as described on
/// <see cref="NetUserId"/>.
/// </remarks>
NetUserId UserId { get; }
/// <summary>
/// Current name of this player.
/// Current name of this player.
/// </summary>
string Name { get; }
/// <summary>
/// Current connection latency of this session. If <see cref="Channel"/> is not null this simply returns
/// <see cref="INetChannel.Ping"/>. This is not currently usable by client-side code that wants to try access ping
/// information of other players.
/// Current connection latency of this session. If <see cref="Channel"/> is not null this simply returns
/// <see cref="INetChannel.Ping"/>. This is not currently usable by client-side code that wants to try access
/// ping information of other players.
/// </summary>
short Ping { get; }
// TODO PlayerManager ping networking.
/// <summary>
/// The current network channel for this session.
/// The current network channel for this session, if one exists.
/// </summary>
/// <remarks>
/// On the Server every player has a network channel,
/// on the Client only the LocalPlayer has a network channel, and that channel points to the server.
/// On the server every player has a network channel,
/// and on the client only the LocalPlayer has a network channel, and that channel points to the server.
/// Sessions without channels will have null here.
/// </remarks>
INetChannel Channel { get; set; }
/// <summary>
/// How this session logged in, which dictates the uniqueness of <see cref="UserId"/>.
/// </summary>
LoginType AuthType { get; }
/// <summary>
/// List of "eyes" to use for PVS range checks.
/// List of "eyes" to use for PVS range checks.
/// </summary>
HashSet<EntityUid> ViewSubscriptions { get; }
/// <summary>
/// The last time this session connected.
/// </summary>
DateTime ConnectedTime { get; set; }
/// <summary>
/// Session state, for sending player lists to clients.
/// Session state, for sending player lists to clients.
/// </summary>
SessionState State { get; }
/// <summary>
/// Class for storing arbitrary session-specific data that is not lost upon reconnect.
/// Class for storing arbitrary session-specific data that is not lost upon reconnect.
/// </summary>
SessionData Data { get; }
/// <summary>
/// If true, this indicates that this is a client-side session, and should be ignored when applying a server's
/// game state.
/// If true, this indicates that this is a client-side session, and should be ignored when applying a server's
/// game state.
/// </summary>
bool ClientSide { get; set; }
}

View File

@@ -13,18 +13,24 @@ namespace Robust.Shared.Player;
public interface ISharedPlayerManager
{
/// <summary>
/// list of connected sessions.
/// List of all connected sessions.
/// </summary>
/// <remarks>
/// You should not modify the contents of this list.
/// </remarks>
ICommonSession[] Sessions { get; }
/// <summary>
/// Sessions with a remote endpoint. On the server, this is equivalent to <see cref="Sessions"/>. On the client,
/// this will only ever contain <see cref="LocalSession"/>
/// Sessions with a remote endpoint. On the server, this is equivalent to <see cref="Sessions"/>. On the client,
/// this will only ever contain <see cref="LocalSession"/>
/// </summary>
/// <remarks>
/// You should not modify the contents of this list.
/// </remarks>
ICommonSession[] NetworkedSessions { get; }
/// <summary>
/// Dictionary mapping connected users to their sessions.
/// Dictionary mapping connected users to their sessions.
/// </summary>
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
@@ -39,7 +45,7 @@ public interface ISharedPlayerManager
int MaxPlayers { get; }
/// <summary>
/// Initializes the manager.
/// Initializes the manager.
/// </summary>
/// <param name="maxPlayers">Maximum number of players that can connect to this server at one time. Does nothing
/// on the client.</param>
@@ -49,79 +55,123 @@ public interface ISharedPlayerManager
void Shutdown();
/// <summary>
/// Indicates that some session's networked data has changed. This will cause an updated player list to be sent to
/// all players.
/// Indicates that some session's networked data has changed. This will cause an updated player list to be sent
/// to all players.
/// </summary>
void Dirty();
/// <summary>
/// The session of the local player. This will be null on the server.
/// The session of the local player. This will be null on the server.
/// </summary>
[ViewVariables] ICommonSession? LocalSession { get; }
/// <summary>
/// The user Id of the local player. This will be null on the server.
/// The user id of the local player. This will be null on the server.
/// </summary>
[ViewVariables] NetUserId? LocalUser { get; }
/// <summary>
/// The entity currently controlled by the local player. This will be null on the server.
/// The entity currently controlled by the local player. This will be null on the server.
/// </summary>
[ViewVariables] EntityUid? LocalEntity { get; }
/// <summary>
/// This gets invoked when a session's <see cref="ICommonSession.Status"/> changes.
/// This gets invoked when a session's <see cref="ICommonSession.Status"/> changes.
/// </summary>
event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
/// <summary>
/// Attempts to resolve a username into a <see cref="NetUserId"/>.
/// Attempts to resolve a username into a <see cref="NetUserId"/>.
/// </summary>
bool TryGetUserId(string userName, out NetUserId userId);
/// <summary>
/// Attempts to get the session that is currently attached to a given entity.
/// Attempts to get the session that is currently attached to a given entity.
/// </summary>
bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Attempts to get the session with the given <see cref="NetUserId"/>.
/// Attempts to get the session with the given <see cref="NetUserId"/>.
/// </summary>
bool TryGetSessionById([NotNullWhen(true)] NetUserId? user, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Attempts to get the session with the given <see cref="ICommonSession.Name"/>.
/// Attempts to get the session with the given <see cref="ICommonSession.Name"/>.
/// </summary>
bool TryGetSessionByUsername(string username, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Attempts to get the session that corresponds to the given channel.
/// Attempts to get the session that corresponds to the given channel.
/// </summary>
bool TryGetSessionByChannel(INetChannel channel, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Gets the session that corresponds to the given channel, throwing if it doesn't exist.
/// </summary>
/// <exception cref="KeyNotFoundException">Thrown if no such session exists.</exception>
ICommonSession GetSessionByChannel(INetChannel channel) => GetSessionById(channel.UserId);
/// <summary>
/// Gets the session that corresponds to the given user id, throwing if it doesn't exist.
/// </summary>
/// <exception cref="KeyNotFoundException">Thrown if no such session exists.</exception>
ICommonSession GetSessionById(NetUserId user);
/// <summary>
/// Check if the given user id has an active session.
/// Check if the given user id has an active session.
/// </summary>
bool ValidSessionId(NetUserId user) => TryGetSessionById(user, out _);
/// <summary>
/// Alternate method to get <see cref="ICommonSession.Data"/>
/// </summary>
SessionData GetPlayerData(NetUserId userId);
/// <summary>
/// Grabs a session's <see cref="ICommonSession.Data"/> if it can be found.
/// </summary>
/// <param name="userId">The user ID to get data for.</param>
/// <param name="data">The session data if found.</param>
/// <returns>Success or failure.</returns>
bool TryGetPlayerData(NetUserId userId, [NotNullWhen(true)] out SessionData? data);
/// <summary>
/// Grabs a session's <see cref="ICommonSession.Data"/> if it can be found.
/// </summary>
/// <param name="userName">The username to get data for.</param>
/// <param name="data">The session data if found.</param>
/// <returns>Success or failure.</returns>
bool TryGetPlayerDataByUsername(string userName, [NotNullWhen(true)] out SessionData? data);
/// <summary>
/// Checks if a given user has any <see cref="ICommonSession.Data"/>.
/// </summary>
bool HasPlayerData(NetUserId userId);
/// <summary>
/// Returns all <see cref="ICommonSession.Data">session data</see>.
/// </summary>
/// <returns></returns>
IEnumerable<SessionData> GetAllPlayerData();
void GetPlayerStates(GameTick fromTick, List<SessionState> states);
void UpdateState(ICommonSession commonSession);
/// <inheritdoc cref="RemoveSession(Robust.Shared.Player.ICommonSession,bool)"/>
void RemoveSession(ICommonSession session, bool removeData = false);
/// <summary>
/// Completely destroys a session, optionally also removing its data.
/// </summary>
void RemoveSession(NetUserId user, bool removeData = false);
/// <summary>
/// Creates a session from a network channel.
/// </summary>
ICommonSession CreateAndAddSession(INetChannel channel);
/// <summary>
/// Creates a new session, without a network channel attached.
/// </summary>
/// <remarks>
/// This should be used carefully, games tend to expect a network channel to be present unless they're client
/// side. This is for example used to create a session for singleplayer clients.
/// </remarks>
ICommonSession CreateAndAddSession(NetUserId user, string name);
/// <summary>

View File

@@ -7,9 +7,14 @@ using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Prototypes;
/// <summary>
/// Quick attribute to give the prototype its type string.
/// To prevent needing to instantiate it because interfaces can't declare statics.
/// Defines the unique type id ("kind") and load priority for a prototype, and registers it so the game knows about
/// the type in question during init and can deserialize it.
/// <br/>
/// </summary>
/// <include file='../Serialization/Manager/Attributes/Docs.xml' path='entries/entry[@name="ImpliesDataDefinition"]/*'/>
/// <seealso cref="PrototypeRecordAttribute"/>
/// <seealso cref="IdDataFieldAttribute"/>
/// <seealso cref=""/>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
#if !ROBUST_ANALYZERS_TEST
[BaseTypeRequired(typeof(IPrototype))]
@@ -20,11 +25,18 @@ namespace Robust.Shared.Prototypes;
public class PrototypeAttribute : Attribute
{
/// <summary>
/// Override for the name of this kind of prototype. If not specified, this is automatically inferred via <see cref="PrototypeManager.CalculatePrototypeName"/>
/// Override for the name of this kind of prototype.
/// If not specified, this is automatically inferred via <see cref="PrototypeManager.CalculatePrototypeName"/>
/// </summary>
public string? Type { get; internal set; }
public readonly int LoadPriority = 1;
/// <summary>
/// Defines the load order for prototype kinds. Higher priorities are loaded earlier.
/// </summary>
public readonly int LoadPriority;
/// <param name="type">See <see cref="Type"/>.</param>
/// <param name="loadPriority">See <see cref="LoadPriority"/>.</param>
public PrototypeAttribute(string? type = null, int loadPriority = 1)
{
Type = type;
@@ -38,6 +50,13 @@ public class PrototypeAttribute : Attribute
}
}
/// <summary>
/// Defines the unique type id ("kind") and load priority for a prototype, and registers it so the game knows about
/// the type in question during init and can deserialize it.
/// <br/>
/// </summary>
/// <include file='../Serialization/Manager/Attributes/Docs.xml' path='entries/entry[@name="ImpliesDataRecord"]/*'/>
/// <seealso cref="PrototypeAttribute"/>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
#if !ROBUST_ANALYZERS_TEST
[BaseTypeRequired(typeof(IPrototype))]
@@ -47,6 +66,8 @@ public class PrototypeAttribute : Attribute
#endif
public sealed class PrototypeRecordAttribute : PrototypeAttribute
{
/// <param name="type">See <see cref="PrototypeAttribute.Type"/>.</param>
/// <param name="loadPriority">See <see cref="PrototypeAttribute.LoadPriority"/>.</param>
public PrototypeRecordAttribute(string type, int loadPriority = 1) : base(type, loadPriority)
{
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<entries>
<entry name="IPrototype">
<remarks>
<para>
It is strongly discouraged to ever construct inheritors of this interface manually, if you need a prototype that
is also runtime-constructable data, consider moving all the data into its own class and using
<see cref="T:Robust.Shared.Serialization.Manager.Attributes.IncludeDataFieldAttribute"/> to flatten it into
the prototype class.
</para>
<para>
Prototypes should <b>never</b> be mutated at runtime, and should be treated as a read only source of truth. Keep
in mind that for example reading a container like a list from the prototype does not make a copy, and you must do
so yourself if you intend to mutate.
</para>
</remarks>
</entry>
</entries>

View File

@@ -7,17 +7,20 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.Prototypes
{
/// <summary>
/// An IPrototype is a prototype that can be loaded from the global YAML prototypes.
/// IPrototype, when combined with <see cref="PrototypeAttribute"/>, defines a type that the game can load from
/// the global YAML prototypes folder during init or runtime. It's a way of defining data for the game to read
/// and act on.
/// </summary>
/// <remarks>
/// To use this, the prototype must be accessible through IoC with <see cref="IoCTargetAttribute"/>
/// and it must have a <see cref="PrototypeAttribute"/> to give it a type string.
/// </remarks>
/// <include file='Docs.xml' path='entries/entry[@name="IPrototype"]/*'/>
/// <seealso cref="IPrototypeManager"/>
/// <seealso cref="PrototypeAttribute"/>
/// <seealso cref="IInheritingPrototype"/>
public interface IPrototype
{
/// <summary>
/// An ID for this prototype instance.
/// If this is a duplicate, an error will be thrown.
/// A unique ID for this prototype instance.
/// This will never be a duplicate, and the game will error during loading if there are multiple prototypes
/// with the same unique ID.
/// </summary>
#if !ROBUST_ANALYZERS_TEST
[ViewVariables(VVAccess.ReadOnly)]
@@ -25,13 +28,40 @@ namespace Robust.Shared.Prototypes
string ID { get; }
}
/// <summary>
/// An extension of <see cref="IPrototype"/> that allows for a prototype to have parents that it inherits data
/// from. This, alongside <see cref="AlwaysPushInheritanceAttribute"/> and
/// <see cref="NeverPushInheritanceAttribute"/>, allow data-based multiple inheritance.
/// </summary>
/// <example>
/// An example of this in practice is <see cref="EntityPrototype"/>.
/// </example>
/// <include file='Docs.xml' path='entries/entry[@name="IPrototype"]/*'/>
/// <seealso cref="IPrototypeManager"/>
/// <seealso cref="PrototypeAttribute"/>
/// <seealso cref="IPrototype"/>
public interface IInheritingPrototype
{
/// <summary>
/// The collection of parents for this prototype. Parents' data is applied to the child in order of
/// specification in the array.
/// </summary>
string[]? Parents { get; }
/// <summary>
/// Whether this prototype is "abstract". This behaves ike an abstract class, abstract prototypes are never
/// indexable and do not show up when enumerating prototypes, as they're just a source of data to inherit
/// from.
/// </summary>
bool Abstract { get; }
}
/// <summary>
/// Marks a field as a prototype's unique identifier. This field must always be a <c>string?</c>.
/// <br/>
/// This field is always required.
/// </summary>
/// <seealso cref="IPrototype"/>
public sealed class IdDataFieldAttribute : DataFieldAttribute
{
public const string Name = "id";
@@ -41,6 +71,13 @@ namespace Robust.Shared.Prototypes
}
}
/// <summary>
/// Marks a field as the parent/parents field for this prototype, as required by
/// <see cref="IInheritingPrototype"/>. This must either be a <c>string?</c>, or <c>string[]?</c>.
/// <br/>
/// This field is never required.
/// </summary>
/// <seealso cref="IInheritingPrototype"/>
public sealed class ParentDataFieldAttribute : DataFieldAttribute
{
public const string Name = "parent";
@@ -50,6 +87,13 @@ namespace Robust.Shared.Prototypes
}
}
/// <summary>
/// Marks a field as the abstract field for this prototype, as required by
/// <see cref="IInheritingPrototype"/>. This must be a <c>bool</c>.
/// <br/>
/// This field is never required.
/// </summary>
/// <seealso cref="IInheritingPrototype"/>
public sealed class AbstractDataFieldAttribute : DataFieldAttribute
{
public const string Name = "abstract";

View File

@@ -16,13 +16,18 @@ using Robust.Shared.Utility;
namespace Robust.Shared.Prototypes;
/// <summary>
/// Handle storage and loading of YAML prototypes.
/// Handle storage and loading of YAML prototypes. These are defined in code using <see cref="PrototypeAttribute"/>
/// and <see cref="PrototypeRecordAttribute"/> on classes that implement <see cref="IPrototype"/> or
/// <see cref="IInheritingPrototype"/>.
/// </summary>
/// <remarks>
/// Terminology:
/// "Kinds" are the types of prototypes there are, like <see cref="EntityPrototype"/>.
/// "Prototypes" are simply filled-in prototypes from YAML.
/// Terminology:<br/>
/// "Kinds" are the types of prototypes there are, like <see cref="EntityPrototype"/>.<br/>
/// "Prototypes" are simply filled-in prototypes from YAML.<br/>
/// </remarks>
/// <seealso cref="IPrototype"/>
/// <seealso cref="IInheritingPrototype"/>
/// <seealso cref="PrototypeAttribute"/>
public interface IPrototypeManager
{
void Initialize();

View File

@@ -10,9 +10,9 @@ namespace Robust.Shared.Sandboxing
/// Effectively equivalent to <see cref="Activator.CreateInstance(Type)"/> but safe for content use.
/// </summary>
/// <exception cref="SandboxArgumentException">
/// Thrown if <paramref name="type"/> is not defined in content.
/// Thrown if <paramref name="type"/> is not defined in content.
/// </exception>
/// <seealso cref="IDynamicTypeFactory.CreateInstance(System.Type)"/>
/// <seealso cref="IDynamicTypeFactory.CreateInstance(System.Type, bool, bool)"/>
object CreateInstance(Type type);
}

View File

@@ -1,9 +1,27 @@
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization
{
/// <summary>
/// Allows a type with an argument-free constructor (i.e. <c>new()</c>) to provide its own serializer and
/// deserializer in place from a string, without deferring to an <see cref="ITypeSerializer{TType,TNode}"/>.
/// </summary>
/// <remarks>
/// This is much more limited than a full serializer, and only allows working with a scalar, but may be
/// convenient.
/// </remarks>
public interface ISelfSerialize
{
/// <summary>
/// Deserialize the type from a given string value, after having already constructed one with <c>new()</c>.
/// </summary>
/// <param name="value">The scalar to deserialize from.</param>
void Deserialize(string value);
/// <summary>
/// Serialize the type to a yaml scalar (i.e. string).
/// </summary>
/// <returns>The serialized representation of the data.</returns>
string Serialize();
}
}

View File

@@ -1,13 +1,13 @@
namespace Robust.Shared.Serialization;
/// <summary>
/// Provides a method that gets executed after deserialization is complete and a method that gets executed before serialization
/// Provides a method that gets executed after deserialization is complete.
/// </summary>
[RequiresExplicitImplementation]
public interface ISerializationHooks
{
/// <summary>
/// Gets executed after deserialization is complete
/// Gets executed after deserialization is complete
/// </summary>
void AfterDeserialization() {}
}

View File

@@ -2,10 +2,33 @@ using System;
namespace Robust.Shared.Serialization.Manager.Attributes
{
// TODO Serialization: find a way to constrain this to DataFields only & make exclusive w/ NeverPush
/// <summary>
/// Adds the parent DataDefinition field to this field.
/// When inheriting a field from a parent, always <b>merge</b> the two fields when such behavior exists.
/// This is unlike the normal behavior where the child's field will always <b>overwrite</b> the parent's.
/// <br/>
/// Merging is done at a YAML level by merging mappings and sequences recursively.
/// </summary>
/// <example>
/// <code>
/// - id: Parent
/// myField: [Foo, Bar]
/// <br/>
/// - id: Child
/// parents: [Parent]
/// myField: [Baz, Qux]
/// </code>
/// Which, when deserialized and assuming myField is marked with AlwaysPushInheritance, will result in data that
/// looks like this:
/// <code>
/// - id: Child
/// myField: [Foo, Bar, Baz, Qux]
/// </code>
/// compared to the default behavior:
/// <code>
/// - id: Child
/// myField: [Baz, Qux]
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AlwaysPushInheritanceAttribute : Attribute
{

View File

@@ -7,49 +7,49 @@ namespace Robust.Shared.Serialization.Manager.Attributes;
/// <see cref="ISerializationManager.CopyTo"/> and <see cref="ISerializationManager.CreateCopy"/>.
/// This means that the source instance is returned directly.
/// This attribute is not inherited.
/// <remarks>
/// Note that when calling any of the generic <see cref="ISerializationManager.CopyTo{T}"/> and
/// <see cref="ISerializationManager.CreateCopy{T}"/> methods, this attribute will only be respected
/// if the generic parameter passed to the copying methods has this attribute.
/// For example, if a copy method is called with a generic parameter T that is not annotated with this attribute,
/// but the actual type of the source parameter is annotated with this attribute, it will not be copied by ref.
/// Conversely, if the generic parameter T is annotated with this attribute, but the actual type of the source
/// is an inheritor which is not annotated with this attribute, it will still be copied by ref.
/// If the generic parameter T is a type derived from another that is annotated with the attribute,
/// but it itself is not annotated with this attribute, source will not be copied by ref as this attribute
/// is not inherited.
/// <code>
/// public class A {}
///
/// [CopyByRef]
/// public class B : A {}
///
/// public class C : B {}
///
/// public class Copier(ISerializationManager manager)
/// {
/// var a = new A();
/// var b = new B();
/// var c = new C();
///
/// // false, not copied by ref
/// manager.CreateCopy(a) == a
///
/// // false, not copied by ref
/// manager.CreateCopy&lt;A&gt;(b) == b
///
/// // true, copied by ref
/// manager.CreateCopy(b) == b
///
/// // false, not copied by ref
/// manager.CreateCopy(c) == c
///
/// // true, copied by ref
/// manager.CreateCopy&lt;B&gt;(c) == c
/// }
/// </code>
/// </remarks>
/// </summary>
/// <remarks>
/// Note that when calling any of the generic <see cref="ISerializationManager.CopyTo{T}"/> and
/// <see cref="ISerializationManager.CreateCopy{T}"/> methods, this attribute will only be respected
/// if the generic parameter passed to the copying methods has this attribute.
/// For example, if a copy method is called with a generic parameter T that is not annotated with this attribute,
/// but the actual type of the source parameter is annotated with this attribute, it will not be copied by ref.
/// Conversely, if the generic parameter T is annotated with this attribute, but the actual type of the source
/// is an inheritor which is not annotated with this attribute, it will still be copied by ref.
/// If the generic parameter T is a type derived from another that is annotated with the attribute,
/// but it itself is not annotated with this attribute, source will not be copied by ref as this attribute
/// is not inherited.
/// <code>
/// public class A {}
/// <br/>
/// [CopyByRef]
/// public class B : A {}
/// <br/>
/// public class C : B {}
/// <br/>
/// public class Copier(ISerializationManager manager)
/// {
/// var a = new A();
/// var b = new B();
/// var c = new C();
/// <br/>
/// // false, not copied by ref
/// manager.CreateCopy(a) == a
/// <br/>
/// // false, not copied by ref
/// manager.CreateCopy&lt;A&gt;(b) == b
/// <br/>
/// // true, copied by ref
/// manager.CreateCopy(b) == b
/// <br/>
/// // false, not copied by ref
/// manager.CreateCopy(c) == c
/// <br/>
/// // true, copied by ref
/// manager.CreateCopy&lt;B&gt;(c) == c
/// }
/// </code>
/// </remarks>
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |

View File

@@ -3,6 +3,13 @@ using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// Marks this type as being data-serializable.
/// </summary>
/// <include file='Docs.xml' path='entries/entry[@name="MeansDataDefinitionHaver"]/*'/>
/// <include file='Docs.xml' path='entries/entry[@name="DataDefinitionExample"]/*'/>
/// <seealso cref="DataFieldAttribute"/>
/// <seealso cref="DataRecordAttribute"/>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
[MeansDataDefinition]
[MeansImplicitUse]

View File

@@ -1,10 +1,20 @@
using System;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.ViewVariables;
#if !ROBUST_ANALYZERS_TEST
using JetBrains.Annotations;
#endif
namespace Robust.Shared.Serialization.Manager.Attributes
{
/// <summary>
/// Marks a field or property as being serializable/deserializable, also implying
/// <see cref="ViewVariablesAttribute"/> with ReadWrite permissions.
/// </summary>
/// <include file='Docs.xml' path='entries/entry[@name="DataDefinitionExample"]/*'/>
/// <seealso cref="DataDefinitionAttribute"/>
/// <seealso cref="DataRecordAttribute"/>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
#if !ROBUST_ANALYZERS_TEST
[MeansImplicitAssignment]
@@ -20,12 +30,18 @@ namespace Robust.Shared.Serialization.Manager.Attributes
public string? Tag { get; internal set; }
/// <summary>
/// Whether or not this field being mapped is required for the component to function.
/// Whether this field being mapped is required for the object to successfully deserialize.
/// This will not guarantee that the field is mapped when the program is run,
/// it is meant to be used as metadata information.
/// </summary>
public readonly bool Required;
/// <param name="tag">See <see cref="Tag"/>.</param>
/// <param name="readOnly">See <see cref="DataFieldBaseAttribute.ReadOnly"/>.</param>
/// <param name="priority">See <see cref="DataFieldBaseAttribute.Priority"/>.</param>
/// <param name="required">See <see cref="Required"/>.</param>
/// <param name="serverOnly">See <see cref="DataFieldBaseAttribute.ServerOnly"/>.</param>
/// <param name="customTypeSerializer">See <see cref="DataFieldBaseAttribute.CustomTypeSerializer"/>.</param>
public DataFieldAttribute(string? tag = null, bool readOnly = false, int priority = 1, bool required = false, bool serverOnly = false, Type? customTypeSerializer = null) : base(readOnly, priority, serverOnly, customTypeSerializer)
{
Tag = tag;
@@ -40,11 +56,60 @@ namespace Robust.Shared.Serialization.Manager.Attributes
public abstract class DataFieldBaseAttribute : Attribute
{
/// <summary>
/// Defines an order datafields should be deserialized in. You rarely if ever need this functionality, and
/// it has no effect on serialization.
/// </summary>
/// <example>
/// <code>
/// [DataDefinition]
/// public class TestClass
/// {
/// // This field is decoded first, as it has the highest priority.
/// [DataField(priority: 3)]
/// public FooData First = new();
/// <br/>
/// // This field has the default priority of 0, and is in the middle.
/// [DataField]
/// public bool Middle = false;
/// <br/>
/// // This field has the lowest priority, so it's dead last.
/// [DataField(priority: -999)]
/// public int DeadLast = 0;
/// }
/// </code>
/// </example>
public readonly int Priority;
/// <summary>
/// A specific <see cref="ITypeSerializer{TType,TNode}"/> implementation to use for parsing this type.
/// This allows you to provide custom yaml parsing logic for a field, an example of this in regular use is
/// <see cref="FlagSerializer{TTag}"/>.
/// </summary>
public readonly Type? CustomTypeSerializer;
/// <summary>
/// Marks the datafield as only ever being <b>read/deserialized</b> from YAML, it will never be
/// written/saved.
///
/// This is useful for data that is only ever used during, say, entity setup, and shouldn't be kept in live
/// entities nor saved for them.
/// </summary>
public readonly bool ReadOnly;
/// <summary>
/// Marks the datafield as server only, indicating to client code that it should not attempt to read or
/// write this field because it may not understand the contained data.
///
/// This is useful for working with types that only exist on the server in otherwise shared data like a
/// shared prototype.
/// </summary>
public readonly bool ServerOnly;
/// <param name="readOnly">See <see cref="ReadOnly"/>.</param>
/// <param name="priority">See <see cref="Priority"/>.</param>
/// <param name="serverOnly">See <see cref="ServerOnly"/>.</param>
/// <param name="customTypeSerializer">See <see cref="CustomTypeSerializer"/>.</param>
protected DataFieldBaseAttribute(bool readOnly = false, int priority = 1, bool serverOnly = false, Type? customTypeSerializer = null)
{
ReadOnly = readOnly;

View File

@@ -4,9 +4,22 @@ using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// Makes all properties in a record data fields with camel case naming.
/// <seealso cref="DataFieldAttribute"/>
/// Marks this type as being data-serializable and automatically marks all properties as data fields.
/// </summary>
/// <include file='Docs.xml' path='entries/entry[@name="MeansDataDefinitionHaver"]/*'/>
/// <example>
/// <code>
/// [DataRecord]
/// public sealed record MyRecord(int Foo, bool Bar);
/// </code>
/// which has the serialized yaml equivalent of:
/// <code>
/// foo: 0
/// bar: false
/// </code>
/// </example>
/// <seealso cref="DataDefinitionAttribute"/>
/// <seealso cref="DataFieldAttribute"/>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
[MeansDataDefinition]
[MeansDataRecord]

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8" ?>
<entries>
<entry name="MeansDataDefinitionHaver"> <!-- Commentary on all MeansDataDefinition. -->
<remarks>
<para>
Data-serializable types <b>must</b> be <see langword="partial"/>. They can be serialized/deserialized with <see cref="T:Robust.Shared.Serialization.Manager.ISerializationManager" />.
Properties to be serialized should be annotated with <see cref="T:Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute"/>, though this is automatic for <see cref="T:Robust.Shared.Serialization.Manager.Attributes.DataRecordAttribute" />.
</para>
<para>
This also allows this type to be used from <c>!type:name</c> annotations in YAML if its name is unique.
Fields not marked with <see cref="T:Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute"/> or its
relatives are never serialized.
</para>
<para>
Has no relation to <see cref="T:System.SerializableAttribute"/>, which is only used with
<see cref="T:Robust.Shared.Serialization.NetSerializableAttribute"/> in RobustToolbox games.
</para>
</remarks>
</entry>
<entry name="ImpliesDataDefinition">
<summary>
Also implies <see cref="T:Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute"/>, you don't
need to specify it yourself.
</summary>
</entry>
<entry name="ImpliesDataRecord">
<summary>
Also implies <see cref="T:Robust.Shared.Serialization.Manager.Attributes.DataRecordAttribute"/>, you don't
need to specify it yourself.
</summary>
</entry>
<entry name="DataDefinitionExample">
<example>
Starting with a definition in C#:
<code>
[DataDefinition] // Mark our type as being serializable by ISerializationManager.
public partial class MyData
{
// Mark this field as being a data field, it'll be named "enabled" implicitly as we
// didn't specify any names.
// If a field is not required, it is best practice to specify a default value.
[DataField]
public bool Enabled = true;
<br/> <!-- Yea so tip to the next person: Rider will eat your whitespace in code blocks. use breaks. -->
[DataField(required: true)]
public int Counter;
}
</code>
This definition describes a YAML schema, which when serialized could look like this:
<code>
enabled: false
counter: 3
</code>
</example>
</entry>
</entries>

View File

@@ -2,6 +2,30 @@ using System;
namespace Robust.Shared.Serialization.Manager.Attributes
{
/// <summary>
/// Marks all classes or interfaces that inherit from the one with this attribute with
/// <see cref="DataDefinitionAttribute"/>, without requiring this be done manually.
/// Cannot be reversed by inheritors!
/// </summary>
/// <example>
/// <code>
/// [ImplicitDataDefinitionForInheritors]
/// public abstract class BaseClass
/// {
/// [DataField]
/// public bool Enabled;
/// }
/// <br/>
/// // Not only do we not need to mark this as a data definition,
/// // we inherit our fields from our parent class as normal and can add our own fields.
/// public sealed class MyClass : BaseClass
/// {
/// [DataField]
/// public int Counter;
/// }
/// </code>
/// </example>
/// <seealso cref="ImplicitDataRecordAttribute"/>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public sealed class ImplicitDataDefinitionForInheritorsAttribute : Attribute
{

View File

@@ -3,9 +3,11 @@
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// Makes any inheritors data records.
/// <seealso cref="DataRecordAttribute"/>
/// Marks all classes or interfaces that inherit from the one with this attribute with
/// <see cref="DataRecordAttribute"/>, without requiring this be done manually.
/// Cannot be reversed by inheritors!
/// </summary>
/// <seealso cref="ImplicitDataDefinitionForInheritorsAttribute"/>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public sealed class ImplicitDataRecordAttribute : Attribute
{

View File

@@ -4,23 +4,42 @@ using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// Inlines the datafield instead of putting it into its own node.
/// Marks a field or property as being serializable/deserializable, including all of the fields from its type into
/// the current data definition. Does not create a named field of its own, and should be used sparingly for
/// conciseness and readability only.
/// </summary>
/// <remarks>
/// mapping:
/// data1: 0
/// data2: 0
/// Becomes
/// data1: 0
/// data2: 0
/// This should never be used in a way where the included/collapsed fields conflict in name with other fields!
/// </remarks>
/// <example>
/// <code>
/// otherField: 42
/// myField:
/// subfield1: foo
/// subfield2: bar
/// </code>
/// becomes
/// <code>
/// otherField: 42
/// subfield1: foo
/// subfield2: bar
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[MeansImplicitAssignment]
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]
public sealed class IncludeDataFieldAttribute : DataFieldBaseAttribute
{
public IncludeDataFieldAttribute(bool readOnly = false, int priority = 1, bool serverOnly = false,
Type? customTypeSerializer = null) : base(readOnly, priority, serverOnly, customTypeSerializer)
/// <param name="readOnly">See <see cref="DataFieldBaseAttribute.ReadOnly"/>.</param>
/// <param name="priority">See <see cref="DataFieldBaseAttribute.Priority"/>.</param>
/// <param name="serverOnly">See <see cref="DataFieldBaseAttribute.ServerOnly"/>.</param>
/// <param name="customTypeSerializer">See <see cref="DataFieldBaseAttribute.CustomTypeSerializer"/>.</param>
public IncludeDataFieldAttribute(
bool readOnly = false,
int priority = 1,
bool serverOnly = false,
Type? customTypeSerializer = null
) : base(readOnly, priority, serverOnly, customTypeSerializer)
{
}

View File

@@ -3,6 +3,11 @@ using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes
{
/// <summary>
/// Marks an attribute class as implying <see cref="DataDefinitionAttribute"/>.
/// </summary>
/// <seealso cref="DataFieldAttribute"/>
/// <seealso cref="DataRecordAttribute"/>
[BaseTypeRequired(typeof(Attribute))]
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class MeansDataDefinitionAttribute : Attribute

View File

@@ -4,8 +4,9 @@ using JetBrains.Annotations;
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// <seealso cref="DataRecordAttribute"/>
/// Marks an attribute class as implying <see cref="DataRecordAttribute"/>.
/// </summary>
/// <seealso cref="DataRecordAttribute"/>
[BaseTypeRequired(typeof(Attribute))]
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class MeansDataRecordAttribute : Attribute

View File

@@ -1,8 +1,15 @@
using System;
using Robust.Shared.Prototypes;
namespace Robust.Shared.Serialization.Manager.Attributes
{
// TODO Serialization: find a way to constrain this to DataField only & make exclusive w/ AlwaysPush
/// <summary>
/// When added to a <see cref="DataFieldAttribute">DataField</see>, this makes it so that the value of the field
/// is <b>never</b> given to a child when inheriting. For example with prototypes, this means the value of the
/// field will not be given to a child if it inherits from a parent with the field set. This is useful for
/// things like <see cref="AbstractDataFieldAttribute"/> where you do not want abstract-ness to pass on to the
/// child.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class NeverPushInheritanceAttribute : Attribute
{

View File

@@ -3,7 +3,8 @@ using System;
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// Used to denote that a type is not serializable to yaml, and should not be used as a data-field.
/// Used to denote that a type is not serializable to yaml, and should not be used as a data-field.
/// Types marked with this will cause an error early in serialization init if used as a field.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
internal sealed class NotYamlSerializableAttribute : Attribute;

View File

@@ -1,10 +1,13 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.Manager.Attributes
{
/// <summary>
/// Registers a <see cref="Robust.Shared.Serialization.TypeSerializers.Interfaces.ITypeSerializer"/> as a default serializer for its type
/// Registers a <see cref="ITypeSerializer{TType,TNode}"/> as a default serializer for its type.
/// This should only be used for serializers that should always be used unconditionally. If you're making a
/// custom serializer, do not apply this attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[MeansImplicitUse]

View File

@@ -4,9 +4,9 @@ using Robust.Shared.Prototypes;
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// This attribute should be used on static string or string collection fields to validate that they correspond to
/// valid YAML prototype ids. This attribute is not required for static <see cref="ProtoId{T}"/> and
/// <see cref="EntProtoId"/> fields, as they automatically get validated.
/// This attribute should be used on static string or string collection fields to validate that they correspond to
/// valid YAML prototype ids. This attribute is not required for static <see cref="ProtoId{T}"/> and
/// <see cref="EntProtoId"/> fields, as they automatically get validated.
/// </summary>
[Obsolete("Use a static readonly ProtoId<T> instead")]
[AttributeUsage(AttributeTargets.Field)]

View File

@@ -8,6 +8,22 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom
{
/// <summary>
/// Serializes or deserializes an integer from a set of known constants specified by an enum.
/// This is very niche in utility, for integer fields where yaml should only ever be using named
/// shorthands.
/// </summary>
/// <example>
/// <code>
/// public enum MyConstants {
/// Foo = 1,
/// Bar = 2,
/// Life = 42,
/// }
/// </code>
/// Using this serializer, an integer field can then be deserialized from, say, <c>"Life"</c> and correctly
/// be set to the value 42.
/// </example>
public sealed class ConstantSerializer<TTag> : ITypeSerializer<int, ValueDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,

View File

@@ -9,6 +9,24 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom
{
/// <summary>
/// Serializes or deserializes <see cref="FlagsAttribute"/> marked enums, allowing you to specify a list of
/// flags instead of writing magic numbers.
/// </summary>
/// <example>
/// <code>
/// [Flags]
/// public enum TestFlags {
/// Foo = 1,
/// Bar = 2,
/// Baz = 4,
/// }
/// </code>
/// which, using this serializer, can be deserialized from
/// <code>
/// [Foo, Baz]
/// </code>
/// </example>
public sealed class FlagSerializer<TTag> : ITypeSerializer<int, ValueDataNode>, ITypeReader<int, SequenceDataNode>, ITypeCopyCreator<int>
{
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,

View File

@@ -1,4 +1,6 @@
namespace Robust.Shared.Timing
using System;
namespace Robust.Shared.Timing
{
/// <summary>
/// Arguments of the GameLoop frame event.
@@ -8,6 +10,10 @@
/// <summary>
/// Seconds passed since this event was last called.
/// </summary>
/// <remarks>
/// Acceptable and simple to use for basic timing code, but accumulators/etc should prefer to use
/// <see cref="TimeSpan"/>s and <see cref="IGameTiming.CurTime"/> to avoid loss of precision.
/// </remarks>
public float DeltaSeconds { get; }
/// <summary>

View File

@@ -4,8 +4,25 @@ using Robust.Shared.Serialization;
namespace Robust.Shared.Timing
{
/// <summary>
/// Wraps a game tick value.
/// Represents a tick at some point in time over the game's runtime.
/// The actual span of time a tick <b>is</b> depends on the <see cref="F:Robust.Shared.CVars.NetTickrate"/>.
/// </summary>
/// <remarks>
/// <para>
/// While the game does use ticks for some timing, they are always an arbitrary time step. If you need to
/// measure exact passage of time, you should use <see cref="TimeSpan"/>s instead in your reference frame
/// (client, server, etc.) from <see cref="IGameTiming"/>.
/// </para>
/// <para>
/// Ticks are appropriate for thinking purely relative to previous game ticks, for example tracking the last
/// time modification occurred on a component for networking purposes.
/// </para>
/// <para>
/// The game can theoretically run out of ticks. At the default tickrate, this is after approximately 4.5 years.
/// It is recommended to reboot the game before that happens.
/// </para>
/// </remarks>
/// <seealso cref="IGameTiming"/>
[Serializable, NetSerializable]
public readonly struct GameTick : IEquatable<GameTick>, IComparable<GameTick>
{

View File

@@ -217,7 +217,7 @@ namespace Robust.Shared.Timing
}
/// <summary>
/// Resets the simulation time.
/// Resets the simulation time.
/// </summary>
public void ResetSimTime()
{

View File

@@ -1,5 +1,6 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Shared.Timing
@@ -10,13 +11,18 @@ namespace Robust.Shared.Timing
public interface IGameTiming
{
/// <summary>
/// Is program execution inside of the simulation, or rendering?
/// Is program execution inside the simulation, or outside of it (rendering, input handling, etc.)
/// </summary>
bool InSimulation { get; set; }
/// <summary>
/// Is the simulation currently paused?
/// </summary>
/// <remarks>
/// When true, system update loops are not ran and relative time (like <see cref="CurTime"/>) does not
/// advance. This is useful for fully idling a game server and can be automatically managed by
/// <see cref="CVars.GameAutoPauseEmpty"/>.
/// </remarks>
bool Paused { get; set; }
/// <summary>
@@ -26,7 +32,8 @@ namespace Robust.Shared.Timing
TimeSpan CurTime { get; }
/// <summary>
/// The current real uptime of the simulation. Use this for UI and out of game timing.
/// The current real uptime of the simulation. Use this for UI and out of game timing, it is not affected
/// by pausing, timescale, or lag.
/// </summary>
TimeSpan RealTime { get; }
@@ -142,34 +149,44 @@ namespace Robust.Shared.Timing
void StartFrame();
/// <summary>
/// Is this the first time CurTick has been predicted?
/// Is this the first time CurTick has been predicted?
/// </summary>
bool IsFirstTimePredicted { get; }
/// <summary>
/// True if CurTick is ahead of LastRealTick, and <see cref="ApplyingState"/> is false.
/// True if CurTick is ahead of LastRealTick, and <see cref="ApplyingState"/> is false.
/// </summary>
/// <remarks>
/// This means the client is currently running ahead of the server, to fill in the gaps for the player and
/// reduce latency while waiting for the next game state to arrive.
/// </remarks>
bool InPrediction { get; }
/// <summary>
/// If true, the game is currently in the process of applying a game server-state.
/// If true, the game is currently in the process of applying a game server-state.
/// </summary>
bool ApplyingState { get; }
string TickStamp => $"{CurTick}, predFirst: {IsFirstTimePredicted}, tickRem: {TickRemainder.TotalSeconds}, sim: {InSimulation}";
/// <summary>
/// Statically-accessible version of <see cref="TickStamp"/>.
/// Statically-accessible version of <see cref="TickStamp"/>.
/// </summary>
/// <remarks>
/// This is intended as a debugging aid, and should not be used in regular committed code.
/// This is intended as a debugging aid, and should not be used in regular committed code.
/// </remarks>
static string TickStampStatic => IoCManager.Resolve<IGameTiming>().TickStamp;
/// <summary>
/// Resets the simulation time. This should be called on round restarts.
/// Resets the simulation time completely. While functional, no mainstream RobustToolbox game currently uses
/// this outside of client synchronization with the server and it may have quirks on existing titles.
/// </summary>
/// <remarks>
/// To avoid potential desynchronization where some entities think they have changes from the far future,
/// this should be accompanied by a full ECS reset using <see cref="IEntityManager.FlushEntities"/>.
/// </remarks>
void ResetSimTime();
/// <inheritdoc cref="ResetSimTime()"/>
void ResetSimTime((TimeSpan, GameTick) timeBase);
void SetTickRateAt(ushort tickRate, GameTick atTick);

View File

@@ -4,8 +4,11 @@ namespace Robust.Shared.Timing
{
/// <summary>
/// Provides a set of methods and properties that you can use to accurately
/// measure elapsed time.
/// measure elapsed time.<br/>
/// <br/>
/// This is legacy and of low utility (it's for mocking), prefer using <see cref="RStopwatch"/>.
/// </summary>
/// <seealso cref="Stopwatch"/>
public interface IStopwatch
{
/// <summary>

View File

@@ -2,8 +2,19 @@
namespace Robust.Shared.Timing
{
/// <summary>
/// Manages <see cref="Timer"/>-based timing, allowing you to register new timers with optional cancellation.
/// </summary>
public interface ITimerManager
{
/// <summary>
/// Registers a timer with the manager, which will be executed on the main thread when its duration has
/// elapsed.
/// </summary>
/// <remarks>
/// Due to the granularity of the game simulation, the wait time for timers is will (effectively) round to
/// the nearest multiple of a tick, as they can only be processed on tick.
/// </remarks>
void AddTimer(Timer timer, CancellationToken cancellationToken = default);
void UpdateTimers(FrameEventArgs frameEventArgs);

View File

@@ -19,6 +19,9 @@ public struct RStopwatch
private static readonly double TicksToTimeTicks = (double)TimeSpan.TicksPerSecond / SStopwatch.Frequency;
/// <summary>
/// Creates and immediately starts a new stopwatch.
/// </summary>
public static RStopwatch StartNew()
{
RStopwatch watch = new();
@@ -26,6 +29,12 @@ public struct RStopwatch
return watch;
}
/// <summary>
/// Starts a stopwatch if it wasn't already.
/// </summary>
/// <remarks>
/// Starting the same stopwatch twice does nothing.
/// </remarks>
public void Start()
{
if (IsRunning)
@@ -36,12 +45,21 @@ public struct RStopwatch
IsRunning = true;
}
/// <summary>
/// Restarts the stopwatch, ensuring it is running regardless of the state it was in before.
/// </summary>
public void Restart()
{
IsRunning = true;
_curTicks = SStopwatch.GetTimestamp();
}
/// <summary>
/// Stops the stopwatch, freezing its count if it is running.
/// </summary>
/// <remarks>
/// Does nothing if the stopwatch wasn't already running.
/// </remarks>
public void Stop()
{
if (!IsRunning)
@@ -51,12 +69,23 @@ public struct RStopwatch
IsRunning = false;
}
/// <summary>
/// Completely resets the stopwatch to an unstarted state with no elapsed time.
/// Strictly equivalent to <c>default</c>.
/// </summary>
public void Reset()
{
this = default;
}
/// <summary>
/// The amount of elapsed time in <see cref="SStopwatch.Frequency"/>
/// </summary>
public readonly long ElapsedTicks => IsRunning ? SStopwatch.GetTimestamp() - _curTicks : _curTicks;
/// <summary>
/// The amount of elapsed time, in real time.
/// </summary>
public readonly TimeSpan Elapsed => new(ElapsedTimeTicks());
private readonly long ElapsedTimeTicks()

View File

@@ -2,10 +2,7 @@
namespace Robust.Shared.Timing
{
/// <summary>
/// Provides a set of methods and properties that you can use to accurately
/// measure elapsed time.
/// </summary>
/// <inheritdoc/>
public sealed class Stopwatch : IStopwatch
{
private readonly System.Diagnostics.Stopwatch _stopwatch;
@@ -18,23 +15,16 @@ namespace Robust.Shared.Timing
_stopwatch = new System.Diagnostics.Stopwatch();
}
/// <summary>
/// Gets the total elapsed time measured by the current instance.
/// </summary>
/// <inheritdoc/>
public TimeSpan Elapsed => _stopwatch.Elapsed;
/// <summary>
/// Starts, or resumes, measuring elapsed time for an interval.
/// </summary>
/// <inheritdoc/>
public void Start()
{
_stopwatch.Start();
}
/// <summary>
/// Stops time interval measurement, resets the elapsed time to zero,
/// and starts measuring elapsed time.
/// </summary>
/// <inheritdoc/>
public void Restart()
{
_stopwatch.Restart();

View File

@@ -6,6 +6,13 @@ using Robust.Shared.IoC;
namespace Robust.Shared.Timing
{
/// <summary>
/// Non-serializable, but async friendly timers.
/// </summary>
/// <remarks>
/// Using these in Space Station 14 is discouraged, it has its own idioms that are all serialization friendly.
/// </remarks>
/// <seealso cref="ITimerManager"/>
public sealed class Timer
{
/// <summary>