Refactor AdvertiseComponent to be ECS, improve performance. (#4676)
This commit is contained in:
committed by
GitHub
parent
c4ec9a143d
commit
08184bc928
@@ -1,144 +1,49 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.Threading;
|
|
||||||
using Content.Server.Advertisements;
|
using Content.Server.Advertisements;
|
||||||
using Content.Server.Chat.Managers;
|
using Robust.Shared.Analyzers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.Advertise
|
namespace Content.Server.Advertise
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent, Friend(typeof(AdvertiseSystem))]
|
||||||
public class AdvertiseComponent : Component
|
public class AdvertiseComponent : Component
|
||||||
{
|
{
|
||||||
public override string Name => "Advertise";
|
public override string Name => "Advertise";
|
||||||
|
|
||||||
private CancellationTokenSource _cancellationSource = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimum time to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
|
/// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
[DataField("minWait")]
|
[DataField("minWait")]
|
||||||
private int MinWait { get; } = 480; // 8 minutes
|
public int MinimumWait { get; } = 8 * 60;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum time to wait before saying a new ad, in seconds. Has to be larger than or equal
|
/// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal
|
||||||
/// to <see cref="MinWait"/>
|
/// to <see cref="MinimumWait"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
[DataField("maxWait")]
|
[DataField("maxWait")]
|
||||||
private int MaxWait { get; } = 600; // 10 minutes
|
public int MaximumWait { get; } = 10 * 60;
|
||||||
|
|
||||||
[DataField("pack")]
|
|
||||||
private string PackPrototypeId { get; } = string.Empty;
|
|
||||||
|
|
||||||
private List<string> _advertisements = new();
|
|
||||||
|
|
||||||
protected override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
IoCManager.Resolve<IPrototypeManager>().TryIndex(PackPrototypeId, out AdvertisementsPackPrototype? packPrototype);
|
|
||||||
|
|
||||||
// Load advertisements pack
|
|
||||||
if (string.IsNullOrEmpty(PackPrototypeId) || packPrototype == null)
|
|
||||||
{
|
|
||||||
// If there is no pack, log a warning and don't start timer
|
|
||||||
Logger.Warning($"{Owner} has {Name}Component but no advertisments pack.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_advertisements = packPrototype.Advertisements;
|
|
||||||
|
|
||||||
// Do not start timer if advertisement list is empty
|
|
||||||
if (_advertisements.Count == 0)
|
|
||||||
{
|
|
||||||
// If no advertisements could be loaded, log a warning and don't start timer
|
|
||||||
Logger.Warning($"{Owner} tried to load advertisements pack without ads.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw exception if MinWait is smaller than 1.
|
|
||||||
if (MinWait < 1)
|
|
||||||
{
|
|
||||||
throw new PrototypeLoadException($"{Owner} has illegal minWait for {Name}Component: {MinWait}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw exception if MinWait larger than MaxWait.
|
|
||||||
if (MinWait > MaxWait)
|
|
||||||
{
|
|
||||||
throw new PrototypeLoadException($"{Owner} should have minWait greater than or equal to maxWait for {Name}Component.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start timer at initialization, without MinWait bound
|
|
||||||
RefreshTimer(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRemove()
|
|
||||||
{
|
|
||||||
_cancellationSource.Cancel();
|
|
||||||
_cancellationSource.Dispose();
|
|
||||||
|
|
||||||
base.OnRemove();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Say advertisement and restart timer.
|
/// The identifier for the advertisements pack prototype.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SayAndRefresh()
|
[DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer<AdvertisementsPackPrototype>))]
|
||||||
{
|
public string PackPrototypeId { get; } = string.Empty;
|
||||||
IRobustRandom random = IoCManager.Resolve<IRobustRandom>();
|
|
||||||
IChatManager chatManager = IoCManager.Resolve<IChatManager>();
|
|
||||||
|
|
||||||
// Say advertisement
|
|
||||||
chatManager.EntitySay(Owner, Loc.GetString(random.Pick(_advertisements)));
|
|
||||||
|
|
||||||
// Refresh timer to repeat cycle
|
|
||||||
RefreshTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refresh cancellation token and spawn new timer with random wait between <see cref="MinWait"/>
|
/// The next time an advertisement will be said.
|
||||||
/// and <see cref="MaxWait"/>.
|
|
||||||
/// <param name="minBound">
|
|
||||||
/// Whether <see cref="MinWait"/> should be used to have a minimum waiting time.
|
|
||||||
/// </param>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void RefreshTimer(bool minBound = true)
|
[ViewVariables]
|
||||||
{
|
public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero;
|
||||||
// Generate new source
|
|
||||||
_cancellationSource.Cancel();
|
|
||||||
_cancellationSource.Dispose();
|
|
||||||
_cancellationSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
// Generate random wait time, then create timer
|
|
||||||
IRobustRandom random = IoCManager.Resolve<IRobustRandom>();
|
|
||||||
var wait = minBound ? random.Next(MinWait * 1000, MaxWait * 1000) : random.Next(MaxWait * 1000);
|
|
||||||
Owner.SpawnTimer(wait, SayAndRefresh, _cancellationSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pause the advertising until <see cref="Resume"/> is called.
|
/// Whether the entity will say advertisements or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Pause()
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
{
|
public bool Enabled { get; set; } = true;
|
||||||
// Cancel current timer
|
|
||||||
_cancellationSource.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resume the advertising after pausing.
|
|
||||||
/// </summary>
|
|
||||||
public void Resume()
|
|
||||||
{
|
|
||||||
// Restart timer, without minBound
|
|
||||||
RefreshTimer(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
136
Content.Server/Advertise/AdvertiseSystem.cs
Normal file
136
Content.Server/Advertise/AdvertiseSystem.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.Advertisements;
|
||||||
|
using Content.Server.Chat.Managers;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Server.VendingMachines;
|
||||||
|
using Content.Shared.Acts;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Server.Advertise
|
||||||
|
{
|
||||||
|
public class AdvertiseSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
|
private const float UpdateTimer = 5f;
|
||||||
|
|
||||||
|
private float _timer = 0f;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<AdvertiseComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<AdvertiseComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<ApcPowerReceiverComponent, AdvertiseEnableChangeAttemptEvent>(OnPowerReceiverEnableChangeAttempt);
|
||||||
|
SubscribeLocalEvent<VendingMachineComponent, AdvertiseEnableChangeAttemptEvent>(OnVendingEnableChangeAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnComponentInit(EntityUid uid, AdvertiseComponent advertise, ComponentInit args)
|
||||||
|
{
|
||||||
|
RefreshTimer(uid, true, advertise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPowerChanged(EntityUid uid, AdvertiseComponent advertise, PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
SetEnabled(uid, args.Powered, advertise);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshTimer(EntityUid uid, bool minimumBound = true, AdvertiseComponent? advertise = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref advertise))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var minWait = Math.Max(1, advertise.MinimumWait);
|
||||||
|
var maxWait = Math.Max(minWait, advertise.MaximumWait);
|
||||||
|
|
||||||
|
var waitSeconds = minimumBound ? _random.Next(minWait, maxWait) : _random.Next(maxWait);
|
||||||
|
advertise.NextAdvertisementTime = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(waitSeconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SayAdvertisement(EntityUid uid, bool refresh = true, AdvertiseComponent? advertise = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref advertise))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements))
|
||||||
|
_chatManager.EntitySay(advertise.Owner, Loc.GetString(_random.Pick(advertisements.Advertisements)));
|
||||||
|
|
||||||
|
if(refresh)
|
||||||
|
RefreshTimer(uid, true, advertise);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEnabled(EntityUid uid, bool enabled, AdvertiseComponent? advertise = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref advertise))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var attemptEvent = new AdvertiseEnableChangeAttemptEvent(enabled, advertise.Enabled);
|
||||||
|
RaiseLocalEvent(uid, attemptEvent, false);
|
||||||
|
|
||||||
|
if (attemptEvent.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(enabled)
|
||||||
|
RefreshTimer(uid, !advertise.Enabled, advertise);
|
||||||
|
|
||||||
|
advertise.Enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPowerReceiverEnableChangeAttempt(EntityUid uid, ApcPowerReceiverComponent component, AdvertiseEnableChangeAttemptEvent args)
|
||||||
|
{
|
||||||
|
if(args.NewState && !component.Powered)
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVendingEnableChangeAttempt(EntityUid uid, VendingMachineComponent component, AdvertiseEnableChangeAttemptEvent args)
|
||||||
|
{
|
||||||
|
// TODO: Improve this...
|
||||||
|
if(args.NewState && component.Broken)
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
_timer += frameTime;
|
||||||
|
|
||||||
|
if (_timer < UpdateTimer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_timer -= UpdateTimer;
|
||||||
|
|
||||||
|
var curTime = _gameTiming.CurTime;
|
||||||
|
|
||||||
|
foreach (var advertise in ComponentManager.EntityQuery<AdvertiseComponent>())
|
||||||
|
{
|
||||||
|
if (!advertise.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If it's still not time for the advertisement, do nothing.
|
||||||
|
if (advertise.NextAdvertisementTime > curTime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SayAdvertisement(advertise.Owner.Uid, true, advertise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AdvertiseEnableChangeAttemptEvent : CancellableEntityEventArgs
|
||||||
|
{
|
||||||
|
public bool NewState { get; }
|
||||||
|
public bool OldState { get; }
|
||||||
|
|
||||||
|
public AdvertiseEnableChangeAttemptEvent(bool newState, bool oldEnabledState)
|
||||||
|
{
|
||||||
|
NewState = newState;
|
||||||
|
OldState = oldEnabledState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,8 @@ namespace Content.Server.VendingMachines
|
|||||||
|
|
||||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key);
|
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key);
|
||||||
|
|
||||||
|
public bool Broken => _broken;
|
||||||
|
|
||||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if(!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
if(!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||||
@@ -129,18 +131,6 @@ namespace Content.Server.VendingMachines
|
|||||||
{
|
{
|
||||||
var state = args.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off;
|
var state = args.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off;
|
||||||
TrySetVisualState(state);
|
TrySetVisualState(state);
|
||||||
|
|
||||||
// Pause/resume advertising if advertising component exists and not broken
|
|
||||||
if (!Owner.TryGetComponent(out AdvertiseComponent? advertiseComponent) || _broken) return;
|
|
||||||
|
|
||||||
if (Powered)
|
|
||||||
{
|
|
||||||
advertiseComponent.Resume();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
advertiseComponent.Pause();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
|
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
|
||||||
@@ -250,11 +240,6 @@ namespace Content.Server.VendingMachines
|
|||||||
{
|
{
|
||||||
_broken = true;
|
_broken = true;
|
||||||
TrySetVisualState(VendingMachineVisualState.Broken);
|
TrySetVisualState(VendingMachineVisualState.Broken);
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out AdvertiseComponent? advertiseComponent))
|
|
||||||
{
|
|
||||||
advertiseComponent.Pause();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Wires
|
public enum Wires
|
||||||
|
|||||||
Reference in New Issue
Block a user