chore: cleanup

This commit is contained in:
redglow
2026-07-29 11:05:27 +02:00
parent 2c0a3f47a4
commit 5c4519ed26
20 changed files with 110 additions and 70 deletions
+7 -2
View File
@@ -1,4 +1,5 @@
using Godot;
using GodotHostTest.AetherBind;
using GodotHostTest.Game.Level;
using GodotHostTest.Game.Score;
using GodotHostTest.Game.Target;
@@ -6,7 +7,6 @@ using GodotHostTest.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using OwofGames.GodotHost.Observability;
using OwofGames.GodotLume;
using RootServiceSource = GodotHostTest.AetherBind.RootServiceSource;
namespace GodotHostTest.Game.Game;
@@ -17,9 +17,14 @@ public partial class GameServiceSource : RootServiceSource
protected override void Configure(Builder builder)
{
builder
// root scope services
.AddSingleton<ITargetEventBus, TargetEventBus>()
.AddSingleton<IScore, TotalScore>()
.AddLevelServices(_levelsData);
// current level / level data management
.AddInstance<ILevelDataProvider, LevelsData>(_levelsData)
.AddSingleton<ICurrentLevel, CurrentLevel>()
// level scope services
.AddLevelServices();
}
protected override void ConfigureServices(IServiceCollection serviceCollection)
+6 -7
View File
@@ -13,7 +13,8 @@ namespace GodotHostTest.Game.Level;
public partial class LevelServiceSource : ServiceSource
{
private Activity? _activityValue;
private Activity? _levelActivity;
[Export] private TargetSpawner _targetSpawner = null!;
protected override void OnScopeCreated(IProvider serviceProvider)
@@ -22,15 +23,13 @@ public partial class LevelServiceSource : ServiceSource
}
[WithProvider]
private void OnScopeCreated(IValueSetter<ITargetCollector> targetCollector,
IValueSetter<ITargetSpawner> targetSpawner, IActivitySourceFactory activitySourceFactory,
IValueSetter<Activity> activity)
private void OnScopeCreated(IActivitySourceFactory activitySourceFactory,
IValueSetter<ITargetCollector> targetCollector, IValueSetter<ITargetSpawner> targetSpawner)
{
// ReSharper disable once ExplicitCallerInfoArgument - the caller name is not significant in this case
_activityValue = activitySourceFactory.ActivitySource.StartActivity("Level") ??
_levelActivity = activitySourceFactory.ActivitySource.StartActivity("Level") ??
throw new InvalidOperationException(
"No listeners for the activity - is OpenTelemetry running?");
activity.Set(_activityValue);
targetCollector.Set(_targetSpawner);
targetSpawner.Set(_targetSpawner);
}
@@ -38,6 +37,6 @@ public partial class LevelServiceSource : ServiceSource
public override void _Ready()
{
// after 3 seconds, dispose the activity for simulation purposes
GetTree().CreateTimer(3).Timeout += _activityValue!.Dispose;
GetTree().CreateTimer(3).Timeout += _levelActivity!.Dispose;
}
}
+3 -9
View File
@@ -1,4 +1,3 @@
using System.Diagnostics;
using GodotHostTest.AetherBind;
using GodotHostTest.Interfaces;
using OwofGames.GodotLume;
@@ -7,17 +6,12 @@ namespace GodotHostTest.Game.Level;
public static class LevelServices
{
public static void AddLevelServices(this Builder builder, LevelsData levelsData)
public static void AddLevelServices(this Builder builder)
{
builder
// current level / level data management
.AddSingleton<ICurrentLevel, CurrentLevel>()
.AddInstance<ILevelDataProvider, LevelsData>(levelsData)
// value provider for activity
.AddValueProvider<Activity, LevelScope>()
// value providers to send data into level scope
// value providers to send level data from root to level scope
.AddValueProvider<ILevel, RootScope, LevelScope>()
// value provider for level scope setup
// value providers for level scope setup
.AddValueProvider<ITargetCollector, LevelScope>()
.AddValueProvider<ITargetSpawner, LevelScope>();
}
+12 -4
View File
@@ -9,16 +9,24 @@ namespace GodotHostTest.Game.Score;
public partial class Score : Control
{
[Export] private AnimationPlayer _animationPlayer = null!;
[Inject] private ICurrentLevel _currentLevel = null!;
[Export] private Label _label = null!;
[Inject] private IScore _score = null!;
public override void _Ready()
{
_score.Score.Subscribe(UpdateScore).AddTo(this);
_score.Score.Chunk(2, 1).Where(values => values.Length == 2 && values[1] < values[0])
.Subscribe(OnScoreDecreasing).AddTo(this);
// update the score label whenever the score changes
_score.Score
.Subscribe(UpdateScore)
.AddTo(this);
// run an animation whenever the score decreases
_score.Score
.Chunk(2, 1)
.Where(values => values.Length == 2 && values[1] < values[0])
.Subscribe(OnScoreDecreasing)
.AddTo(this);
}
private void OnScoreDecreasing<T>(T _)
+10 -11
View File
@@ -6,24 +6,23 @@ namespace GodotHostTest.Game.Score;
public class TotalScore : IScore, IDisposable
{
private readonly IDisposable _connectedObservableDisposable;
public TotalScore(ITargetEventBus targetEventBus)
{
var connectedObservable = targetEventBus.Hit.Select(_ => 20)
.Merge(targetEventBus.TimedOut.Select(_ => -5))
.Scan(0, (x1, x2) => x1 + x2)
.Prepend(0)
.Replay(1);
_connectedObservableDisposable = connectedObservable.Connect();
Score = connectedObservable.AsObservable();
Score =
// map every hit target to +20 points
targetEventBus.Hit.Select(_ => 20)
// map every missed target to -5 points
.Merge(targetEventBus.TimedOut.Select(_ => -5))
// sum them all (starting with 0)
.Scan(0, (x1, x2) => x1 + x2)
.ToReadOnlyReactiveProperty();
}
public void Dispose()
{
_connectedObservableDisposable.Dispose();
Score.Dispose();
GC.SuppressFinalize(this);
}
public Observable<int> Score { get; }
public ReadOnlyReactiveProperty<int> Score { get; }
}
+13 -17
View File
@@ -8,14 +8,14 @@ namespace GodotHostTest.Game.Target;
public partial class Target : Node2D, ITarget
{
private readonly Subject<Unit> _hit = new();
private readonly Subject<Unit> _timedOut = new();
[Export] public required Area2D CollisionArea;
[Export] public required Sprite2D Image;
[Export] public required Timer TimeOutTimer;
[Export] private PackedScene _explosionPackedScene = null!;
[Inject] private ILevel _level = null!;
[Inject] private ILevel _level = null!;
[Inject] private ITargetCollector _targetCollector = null!;
public Observable<Unit> TimedOut => _timedOut.AsObservable();
@@ -30,26 +30,30 @@ public partial class Target : Node2D, ITarget
TimeOutTimer.Start(_level.SpawnDuration.TotalSeconds);
}
private void Disable()
{
Image.Visible = false;
// can't set monitoring/monitorable when inside collision code, like during OnHit, so let's call them deferred
CollisionArea.SetDeferred(Area2D.PropertyName.Monitoring, false);
CollisionArea.SetDeferred(Area2D.PropertyName.Monitorable, false);
TimeOutTimer.Stop();
}
public override void _Ready()
{
_targetCollector.NewTarget(this);
}
public override void _ExitTree()
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_timedOut.Dispose();
_hit.Dispose();
}
private void OnAreaEntered(Area2D _)
{
OnHit();
}
private void OnHit()
{
Disable();
TimeOutTimer.Stop();
_hit.OnNext(Unit.Default);
var explosion = (GpuParticles2D)_explosionPackedScene.Instantiate();
@@ -58,14 +62,6 @@ public partial class Target : Node2D, ITarget
GetTree().CurrentScene.AddChild(explosion);
}
private void Disable()
{
Image.Visible = false;
// can't se monitoring/monitorable when inside the collision code, like during OnHit
CollisionArea.SetDeferred(Area2D.PropertyName.Monitoring, false);
CollisionArea.SetDeferred(Area2D.PropertyName.Monitorable, false);
}
private void OnTimeout()
{
Disable();
+13 -8
View File
@@ -10,31 +10,36 @@ namespace GodotHostTest.Game.Target;
public partial class TargetSpawner : Node2D, ITargetCollector, ITargetSpawner
{
private readonly List<ITarget> _availableTargets = [];
private readonly Subject<ITarget> _targetSpawned = new();
[Export] public required Timer Timer;
[Inject] private ILevel _level = null!;
[Inject] private ITargetEventBus _targetEventBus = null!;
/// <inheritdoc />
public void NewTarget(ITarget target)
{
// record the target as available
_availableTargets.Add(target);
target.Hit.Merge(target.TimedOut).Select(_ => target).Subscribe(MakeTargetAvailable).AddTo(this);
// whenever the target gets disabled, mark it as available again
target.Hit.Merge(target.TimedOut)
.Select(_ => target)
.Subscribe(_availableTargets.Add)
.AddTo(this);
}
public Observable<ITarget> TargetSpawned => _targetSpawned.AsObservable();
public override void _Ready()
{
// inform the target event bus that there's a new target spawner
_targetEventBus.NewTargetSpawner(this);
Spawn();
Timer.Start(_level.TimeBetweenSpawns.TotalSeconds);
}
private void MakeTargetAvailable(ITarget target)
{
_availableTargets.Add(target);
// spawn every total seconds (and immediately spawn something at ready)
Timer.Start(_level.TimeBetweenSpawns.TotalSeconds);
Spawn();
}
private void OnTimer()
-1
View File
@@ -11,7 +11,6 @@ public partial class Turret : Sprite2D
[Inject] private ILogger<Turret> _logger = null!;
[Export] private PackedScene _projectilePackedScene = null!;
private double _rotationDirection;
[Export] private double _rotationSpeed = 6;
[Inject] private ISceneInstantiator _sceneInstantiator = null!;