using System; using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Network; using Robust.Shared.Players; using Robust.Shared.Reflection; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Shared.GameObjects { /// [Reflect(false)] [ImplicitDataDefinitionForInheritors] public abstract class Component : IComponent { /// [ViewVariables(VVAccess.ReadOnly)] public virtual string Name => IoCManager.Resolve().GetComponentName(GetType()); /// [ViewVariables] [DataField("netsync")] public bool NetSyncEnabled { get; set; } = true; /// [ViewVariables] public EntityUid Owner { get; set; } = EntityUid.Invalid; /// [ViewVariables] public ComponentLifeStage LifeStage { get; private set; } = ComponentLifeStage.PreAdd; /// /// Increases the life stage from to , /// calling . /// internal void LifeAddToEntity(IEntityManager entManager) { DebugTools.Assert(LifeStage == ComponentLifeStage.PreAdd); LifeStage = ComponentLifeStage.Adding; CreationTick = entManager.CurrentTick; entManager.EventBus.RaiseComponentEvent(this, CompAddInstance); OnAdd(); #if DEBUG if (LifeStage != ComponentLifeStage.Added) { DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(OnAdd)} in derived method."); } #endif } /// /// Increases the life stage from to , /// calling . /// internal void LifeInitialize(IEntityManager entManager) { DebugTools.Assert(LifeStage == ComponentLifeStage.Added); LifeStage = ComponentLifeStage.Initializing; entManager.EventBus.RaiseComponentEvent(this, CompInitInstance); Initialize(); #if DEBUG if (LifeStage != ComponentLifeStage.Initialized) { DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Initialize)} in derived method."); } #endif } /// /// Increases the life stage from to /// , calling . /// internal void LifeStartup(IEntityManager entManager) { DebugTools.Assert(LifeStage == ComponentLifeStage.Initialized); LifeStage = ComponentLifeStage.Starting; entManager.EventBus.RaiseComponentEvent(this, CompStartupInstance); Startup(); #if DEBUG if (LifeStage != ComponentLifeStage.Running) { DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Startup)} in derived method."); } #endif } /// /// Increases the life stage from to , /// calling . /// /// /// Components are allowed to remove themselves in their own Startup function. /// internal void LifeShutdown(IEntityManager entManager) { // Starting allows a component to remove itself in it's own Startup function. DebugTools.Assert(LifeStage == ComponentLifeStage.Starting || LifeStage == ComponentLifeStage.Running); LifeStage = ComponentLifeStage.Stopping; entManager.EventBus.RaiseComponentEvent(this, CompShutdownInstance); Shutdown(); #if DEBUG if (LifeStage != ComponentLifeStage.Stopped) { DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Shutdown)} in derived method."); } #endif } /// /// Increases the life stage from to , /// calling . /// internal void LifeRemoveFromEntity(IEntityManager entManager) { // can be called at any time after PreAdd, including inside other life stage events. DebugTools.Assert(LifeStage != ComponentLifeStage.PreAdd); LifeStage = ComponentLifeStage.Removing; entManager.EventBus.RaiseComponentEvent(this, CompRemoveInstance); OnRemove(); #if DEBUG if (LifeStage != ComponentLifeStage.Deleted) { DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(OnRemove)} in derived method."); } #endif } /// [ViewVariables] public bool Initialized => LifeStage >= ComponentLifeStage.Initializing; /// [ViewVariables] public bool Running => ComponentLifeStage.Starting <= LifeStage && LifeStage <= ComponentLifeStage.Stopping; /// [ViewVariables] public bool Deleted => LifeStage >= ComponentLifeStage.Removing; /// [ViewVariables] public GameTick CreationTick { get; private set; } /// [ViewVariables] public GameTick LastModifiedTick { get; internal set; } private static readonly ComponentAdd CompAddInstance = new(); private static readonly ComponentInit CompInitInstance = new(); private static readonly ComponentStartup CompStartupInstance = new(); private static readonly ComponentShutdown CompShutdownInstance = new(); private static readonly ComponentRemove CompRemoveInstance = new(); /// /// Called when the component gets added to an entity. /// protected virtual void OnAdd() { LifeStage = ComponentLifeStage.Added; } /// /// Called when all of the entity's other components have been added and are available, /// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct. /// protected virtual void Initialize() { LifeStage = ComponentLifeStage.Initialized; } /// /// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized. /// /// /// Components are allowed to remove themselves in their own Startup function. /// protected virtual void Startup() { LifeStage = ComponentLifeStage.Running; } /// /// Shuts down the component. The is called Automatically by OnRemove. /// protected virtual void Shutdown() { LifeStage = ComponentLifeStage.Stopped; } /// /// Called when the component is removed from an entity. /// Shuts down the component. /// The component has already been marked as deleted in the component manager. /// protected virtual void OnRemove() { LifeStage = ComponentLifeStage.Deleted; } /// public void Dirty(IEntityManager? entManager = null) { IoCManager.Resolve(ref entManager); entManager.Dirty(this); } private static readonly ComponentState DefaultComponentState = new(); /// public virtual ComponentState GetComponentState() { if (!(Attribute.GetCustomAttribute(GetType(), typeof(NetworkedComponentAttribute)) is NetworkedComponentAttribute)) throw new InvalidOperationException($"Calling base {nameof(GetComponentState)} without being networked."); return DefaultComponentState; } /// public virtual void HandleComponentState(ComponentState? curState, ComponentState? nextState) { } // these two methods clear the LastModifiedTick/CreationTick to mark it as "not different from prototype load". // This is used as optimization in the game state system to avoid sending redundant component data. internal virtual void ClearTicks() { LastModifiedTick = GameTick.Zero; ClearCreationTick(); } internal void ClearCreationTick() { CreationTick = GameTick.Zero; } } /// /// The life stages of an ECS component. /// public enum ComponentLifeStage { /// /// The component has just been allocated. /// PreAdd = 0, /// /// Currently being added to an entity. /// Adding, /// /// Has been added to an entity. /// Added, /// /// Currently being initialized. /// Initializing, /// /// Has been initialized. /// Initialized, /// /// Currently being started up. /// Starting, /// /// Has started up. /// Running, /// /// Currently shutting down. /// Stopping, /// /// Has been shut down. /// Stopped, /// /// Currently being removed from its entity. /// Removing, /// /// Removed from its entity, and is deleted. /// Deleted, } /// /// The component has been added to the entity. This is the first function /// to be called after the component has been allocated and (optionally) deserialized. /// public sealed class ComponentAdd : EntityEventArgs { } /// /// Raised when all of the entity's other components have been added and are available, /// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct. /// public sealed class ComponentInit : EntityEventArgs { } /// /// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized. /// This can be called multiple times during the component's life, and at any time. /// public sealed class ComponentStartup : EntityEventArgs { } /// /// Shuts down the component. The is called Automatically by OnRemove. This can be called multiple times during /// the component's life, and at any time. /// public sealed class ComponentShutdown : EntityEventArgs { } /// /// The component has been removed from the entity. This is the last function /// that is called before the component is freed. /// public sealed class ComponentRemove : EntityEventArgs { } }