Skip to content

Commit

Permalink
Added layout phase listeners.
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielWillett committed Oct 25, 2024
1 parent 883960d commit e8bc8bd
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 11 deletions.
2 changes: 1 addition & 1 deletion UncreatedWarfare/Interaction/AnnouncementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public AnnouncementService(ILoopTickerFactory tickerFactory, IConfiguration syst
_ticker = tickerFactory.CreateTicker(TimeSpan.FromSeconds(systemConfig.GetValue<float>("interactions:announcement_time_sec")), false, true, HandleAnnouncementTick);
}

private void HandleAnnouncementTick(ILoopTicker ticker, TimeSpan timesincestart, TimeSpan deltatime)
private void HandleAnnouncementTick(ILoopTicker ticker, TimeSpan timeSinceStart, TimeSpan deltaTime)
{
if (_translations.Translations.Count == 0)
return;
Expand Down
66 changes: 58 additions & 8 deletions UncreatedWarfare/Layouts/Layout.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
using DanielWillett.ReflectionTools;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Autofac.Core;
using DanielWillett.ReflectionTools;
using Uncreated.Warfare.Configuration;
using Uncreated.Warfare.Layouts.Phases;
using Uncreated.Warfare.Layouts.Phases.Flags;
using Uncreated.Warfare.Layouts.Teams;
using Uncreated.Warfare.Logging;
using Uncreated.Warfare.Services;
using Uncreated.Warfare.Util;

namespace Uncreated.Warfare.Layouts;
Expand Down Expand Up @@ -265,6 +266,51 @@ protected internal virtual async UniTask BeginLayoutAsync(CancellationToken toke
await MoveToNextPhase(token);
}

private async UniTask InvokePhaseListenerAction(ILayoutPhase phase, bool end, CancellationToken token)
{
Type intxType = typeof(ILayoutPhaseListener<>).MakeGenericType(phase.GetType());

// find all services assignable from ILayoutPhaseListener<phase.GetType()>
List<object> listeners = ServiceProvider.ComponentRegistry.Registrations
.SelectMany(x => x.Services)
.OfType<IServiceWithType>()
.Where(x => intxType.IsAssignableFrom(x.ServiceType))
.Select(x => ServiceProvider.Resolve(x.ServiceType))
.OrderByDescending(x => x.GetType().GetPriority())
.ToList();

foreach (object service in listeners)
{
Type type = service.GetType();
Type implIntxType = type.GetInterfaces().First(x => x.GetGenericTypeDefinition() == typeof(ILayoutPhaseListener<>));

// invoke method from an unknown generic interface type
MethodInfo implementation = implIntxType.GetMethod(
end ? nameof(ILayoutPhaseListener<PreparationPhase>.OnPhaseEnded)
: nameof(ILayoutPhaseListener<PreparationPhase>.OnPhaseStarted),
BindingFlags.Public | BindingFlags.Instance) ?? throw new Exception("Unable to find phase listener method.");

implementation = Accessor.GetImplementedMethod(type, implementation) ?? throw new Exception("Unable to find phase listener implemented method.");

try
{
await (UniTask)implementation.Invoke(service, [ phase, token ]);
}
catch (TargetInvocationException ex)
{
if (end)
{
Logger.LogError(ex.InnerException, "Failed to end phase {0}. Listener {1} failed.", phase.GetType(), type);
await _factory.StartNextLayout(CancellationToken.None);
throw new OperationCanceledException();
}

Logger.LogWarning(ex.InnerException, "Error beginning phase {0}. Listener {1} failed.", phase.GetType(), type);
throw new OperationCanceledException();
}
}
}

public virtual async UniTask MoveToNextPhase(CancellationToken token = default)
{
// keep moving to the next phase until one is activated by BeginPhase.
Expand Down Expand Up @@ -312,13 +358,15 @@ public virtual async UniTask MoveToNextPhase(CancellationToken token = default)
throw new OperationCanceledException();
}

await UniTask.SwitchToMainThread(token);
await InvokePhaseListenerAction(oldPhase, end: true, CancellationToken.None);

await UniTask.SwitchToMainThread(CancellationToken.None);
try
{
if (oldPhase is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
await UniTask.SwitchToMainThread(token);
await UniTask.SwitchToMainThread(CancellationToken.None);
}
else if (oldPhase is IDisposable disposable)
{
Expand All @@ -327,7 +375,7 @@ public virtual async UniTask MoveToNextPhase(CancellationToken token = default)
}
catch (Exception ex)
{
await UniTask.SwitchToMainThread(token);
await UniTask.SwitchToMainThread(CancellationToken.None);
Logger.LogError(ex, "Error disposing phase {0}.", oldPhase);
}
}
Expand All @@ -343,12 +391,14 @@ public virtual async UniTask MoveToNextPhase(CancellationToken token = default)
if (!newPhase.IsActive)
{
Logger.LogError(ex, "Error beginning phase {0}.", newPhase.GetType());
await _factory.StartNextLayout(CancellationToken.None);
await _factory.StartNextLayout(token);
throw new OperationCanceledException();
}

Logger.LogWarning(ex, "Error beginning phase {0}.", newPhase.GetType());
}

await InvokePhaseListenerAction(newPhase, end: false, CancellationToken.None);
}
while (!newPhase.IsActive);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using Uncreated.Warfare.Exceptions;
using Uncreated.Warfare.Layouts.Teams;
using Uncreated.Warfare.Players.Management;
using Uncreated.Warfare.Util;
using Uncreated.Warfare.Zones;
using Uncreated.Warfare.Zones.Pathing;
Expand Down
20 changes: 20 additions & 0 deletions UncreatedWarfare/Services/ILayoutPhaseListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Uncreated.Warfare.Layouts.Phases;

namespace Uncreated.Warfare.Services;

/// <summary>
/// Allows a service to listen for a specific phase to start and/or end.
/// </summary>
/// <typeparam name="TPhaseType">The base type of the phase.</typeparam>
public interface ILayoutPhaseListener<in TPhaseType> where TPhaseType : ILayoutPhase
{
/// <summary>
/// Runs after <see cref="ILayoutPhase.BeginPhaseAsync"/>.
/// </summary>
UniTask OnPhaseStarted(TPhaseType phase, CancellationToken token = default);

/// <summary>
/// Runs after <see cref="ILayoutPhase.EndPhaseAsync"/>.
/// </summary>
UniTask OnPhaseEnded(TPhaseType phase, CancellationToken token = default);
}
2 changes: 1 addition & 1 deletion UncreatedWarfare/UncreatedWarfare.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<PropertyGroup>

<VersionPrefix>4.0.0</VersionPrefix>
<VersionBuild>013</VersionBuild>
<VersionBuild>014</VersionBuild>
<VersionSuffix>prerelease$(VersionBuild)</VersionSuffix>

</PropertyGroup>
Expand Down
5 changes: 5 additions & 0 deletions UncreatedWarfare/WarfareModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,11 @@ await Task.WhenAny(Task.Run(async () =>
return;
}

// this needs to run before hosted services start requesting translations
ICachableLanguageDataStore? dataStore = ServiceProvider.ResolveOptional<ICachableLanguageDataStore>();
if (dataStore != null)
await dataStore.ReloadCache(token);

await UniTask.SwitchToMainThread(token);

List<IHostedService> hostedServices = ServiceProvider
Expand Down

0 comments on commit e8bc8bd

Please sign in to comment.