Skip to content

Commit

Permalink
Merge pull request #339 from MUnique/performance-ai
Browse files Browse the repository at this point in the history
Optimization for the monster/npc intelligence
  • Loading branch information
sven-n authored Apr 3, 2023
2 parents f59df34 + 7d19282 commit b8ec349
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 10 deletions.
5 changes: 5 additions & 0 deletions src/GameLogic/INpcIntelligence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ public interface INpcIntelligence
/// Starts the actions.
/// </summary>
void Start();

/// <summary>
/// Pauses the actions.
/// </summary>
void Pause();
}
21 changes: 14 additions & 7 deletions src/GameLogic/NPC/BasicMonsterIntelligence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ public Monster Monster
/// </summary>
protected IAttackable? CurrentTarget { get; private set; }

private async ValueTask<bool> IsObservedByAttackerAsync()
/// <inheritdoc/>
public void Start()
{
using var readerLock = await this.Monster.ObserverLock.ReaderLockAsync();
return this.Monster.Observers.OfType<IAttacker>().Any();
var startDelay = this.Npc.Definition.AttackDelay + TimeSpan.FromMilliseconds(Rand.NextInt(0, 1000));
this._aiTimer ??= new Timer(_ => this.SafeTick(), null, startDelay, this.Npc.Definition.AttackDelay);
}

/// <inheritdoc/>
public void Start()
public void Pause()
{
TimeSpan randomized = this.Npc.Definition.AttackDelay + TimeSpan.FromMilliseconds(Rand.NextInt(0, 1000));
// TODO: Optimize this: start timer when first observer is added. stop timer when last observer is removed.
this._aiTimer = new Timer(_ => this.SafeTick(), null, randomized, this.Npc.Definition.AttackDelay);
this._aiTimer?.Dispose();
this._aiTimer = null;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -156,6 +156,7 @@ private async ValueTask TickAsync()
{
if (!this.Monster.IsAlive)
{
this.CurrentTarget = null;
return;
}

Expand Down Expand Up @@ -235,4 +236,10 @@ private async ValueTask TickAsync()
// we move around randomly, so the monster does not look dead when watched from distance.
await this.Monster.RandomMoveAsync().ConfigureAwait(false);
}

private async ValueTask<bool> IsObservedByAttackerAsync()
{
using var readerLock = await this.Monster.ObserverLock.ReaderLockAsync();
return this.Monster.Observers.OfType<IAttacker>().Any();
}
}
15 changes: 14 additions & 1 deletion src/GameLogic/NPC/Monster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public Monster(MonsterSpawnArea spawnInfo, MonsterDefinition stats, GameMap map,
(this._skillPowerUp, this._skillPowerUpDuration, this._skillPowerUpTarget) = this.CreateMagicEffectPowerUp();

this._intelligence.Npc = this;
this._intelligence.Start();
}

/// <summary>
Expand Down Expand Up @@ -234,6 +233,20 @@ internal async ValueTask RandomMoveAsync()
}
}

/// <inheritdoc/>
protected override void OnFirstObserverAdded()
{
base.OnFirstObserverAdded();
this._intelligence.Start();
}

/// <inheritdoc/>
protected override void OnLastObserverRemoved()
{
base.OnLastObserverRemoved();
this._intelligence.Pause();
}

/// <inheritdoc/>
protected override void Dispose(bool managed)
{
Expand Down
44 changes: 44 additions & 0 deletions src/GameLogic/NPC/NonPlayerCharacter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,25 @@ public async ValueTask AddObserverAsync(IWorldObserver observer)
{
using var writerLock = await this.ObserverLock.WriterLockAsync();
this.Observers.Add(observer);
if (this.Observers.Count == 1)
{
this.OnFirstObserverAdded();
}

this.OnObserverAdded();
}

/// <inheritdoc/>
public async ValueTask RemoveObserverAsync(IWorldObserver observer)
{
using var writerLock = await this.ObserverLock.WriterLockAsync();
this.Observers.Remove(observer);
if (this.Observers.Count == 0)
{
this.OnLastObserverRemoved();
}

this.OnObserverRemoved();
}

/// <inheritdoc/>
Expand All @@ -111,6 +123,38 @@ public override string ToString()
return $"{this.Definition.Designation} - Id: {this.Id} - Position: {this.Position}";
}

/// <summary>
/// Called when an observer has been added.
/// </summary>
protected virtual void OnObserverAdded()
{
// can be overwritten.
}

/// <summary>
/// Called when an observer has been removed.
/// </summary>
protected virtual void OnObserverRemoved()
{
// can be overwritten.
}

/// <summary>
/// Called when the first observer has been added.
/// </summary>
protected virtual void OnFirstObserverAdded()
{
// can be overwritten.
}

/// <summary>
/// Called when the last observer has been removed.
/// </summary>
protected virtual void OnLastObserverRemoved()
{
// can be overwritten.
}

/// <inheritdoc />
protected override async ValueTask DisposeAsyncCore()
{
Expand Down
6 changes: 6 additions & 0 deletions src/GameLogic/NPC/NullMonsterIntelligence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ public void Start()
{
// do nothing
}

/// <inheritdoc />
public void Pause()
{
// do nothing
}
}
15 changes: 14 additions & 1 deletion src/GameLogic/NPC/Trap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public Trap(MonsterSpawnArea spawnInfo, MonsterDefinition stats, GameMap map, IN
this.Attributes = new TrapAttributeHolder(this);
this._intelligence = trapIntelligence;
this._intelligence.Npc = this;
this._intelligence.Start();
}

/// <inheritdoc/>
Expand All @@ -51,6 +50,20 @@ public async Task AttackAsync(IAttackable player)
await this.ForEachWorldObserverAsync<IShowAnimationPlugIn>(p => p.ShowAnimationAsync(this, TrapAttackAnimation, player, this.Rotation), true).ConfigureAwait(false);
}

/// <inheritdoc/>
protected override void OnFirstObserverAdded()
{
base.OnFirstObserverAdded();
this._intelligence.Start();
}

/// <inheritdoc/>
protected override void OnLastObserverRemoved()
{
base.OnLastObserverRemoved();
this._intelligence.Pause();
}

/// <inheritdoc/>
protected override void Dispose(bool managed)
{
Expand Down
10 changes: 9 additions & 1 deletion src/GameLogic/NPC/TrapIntelligenceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,15 @@ public void RegisterHit(IAttacker attacker)
/// <inheritdoc/>
public void Start()
{
this._aiTimer = new Timer(state => this.SafeTick(), null, this.Trap.Definition.AttackDelay, this.Trap.Definition.AttackDelay);
var startDelay = this.Npc.Definition.AttackDelay + TimeSpan.FromMilliseconds(Rand.NextInt(0, 1000));
this._aiTimer ??= new Timer(_ => this.SafeTick(), null, startDelay, this.Npc.Definition.AttackDelay);
}

/// <inheritdoc/>
public void Pause()
{
this._aiTimer?.Dispose();
this._aiTimer = null;
}

/// <inheritdoc/>
Expand Down

0 comments on commit b8ec349

Please sign in to comment.