Remove obsolete project files for MrGameEng.AI, MrGameEng.Assets, MrGameEng.Atlases, and MrGameEng.Collisions modules. Introduce new AssetManager and related classes for asset loading and management, including support for texture atlases and mod definitions. Enhance mod loading capabilities with DefDatabase and LanguageManager for JSON-based definitions and localization. Implement a shelf packing algorithm for efficient texture atlas creation.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
namespace MrGameEng.AI;
|
||||
|
||||
/// <summary>
|
||||
/// A utility reasoner over a fixed set of <see cref="UtilityAction{TContext}"/>s. Each evaluation
|
||||
/// scores every action for the given context and picks one. Scoring writes into a buffer owned by the
|
||||
/// reasoner, so repeated evaluations allocate nothing; keep one instance per agent kind (or one per
|
||||
/// system, reused across agents) and pass each agent's context in. Not thread-safe: the score buffer
|
||||
/// is shared between calls, so a single instance must not be evaluated from two threads at once.
|
||||
/// </summary>
|
||||
public sealed class UtilityAi<TContext>
|
||||
{
|
||||
private readonly UtilityAction<TContext>[] _actions;
|
||||
private readonly float[] _scores;
|
||||
|
||||
/// <summary>Creates a reasoner choosing between <paramref name="actions"/> (at least one required).</summary>
|
||||
/// <exception cref="ArgumentException"><paramref name="actions"/> is empty.</exception>
|
||||
public UtilityAi(params UtilityAction<TContext>[] actions)
|
||||
{
|
||||
if (actions is null || actions.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("A UtilityAi needs at least one action.", nameof(actions));
|
||||
}
|
||||
|
||||
_actions = actions;
|
||||
_scores = new float[actions.Length];
|
||||
}
|
||||
|
||||
/// <summary>The actions this reasoner chooses between, in evaluation order.</summary>
|
||||
public IReadOnlyList<UtilityAction<TContext>> Actions => _actions;
|
||||
|
||||
/// <summary>
|
||||
/// The scores from the most recent <see cref="Select"/> / <see cref="SelectWeighted"/> call, aligned
|
||||
/// with <see cref="Actions"/>. Useful for debug overlays and console dumps.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<float> LastScores => _scores;
|
||||
|
||||
/// <summary>
|
||||
/// Scores every action for <paramref name="context"/> and returns the highest, or <c>null</c> when no
|
||||
/// action scores strictly above <paramref name="threshold"/>. Ties resolve to the earliest action,
|
||||
/// so selection is fully deterministic for identical inputs.
|
||||
/// </summary>
|
||||
public UtilityAction<TContext>? Select(TContext context, float threshold = 0f)
|
||||
{
|
||||
var best = -1;
|
||||
var bestScore = threshold;
|
||||
for (var i = 0; i < _actions.Length; i++)
|
||||
{
|
||||
var score = _actions[i].Score(context);
|
||||
_scores[i] = score;
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
|
||||
return best >= 0 ? _actions[best] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scores every action and picks one at random in proportion to its score (roulette selection over
|
||||
/// the actions above <paramref name="threshold"/>), giving believable variety while staying
|
||||
/// deterministic for a given <paramref name="random"/> sequence. Returns <c>null</c> when nothing
|
||||
/// qualifies. Pass a seeded <see cref="Random"/> owned by the calling system — never
|
||||
/// <see cref="Random.Shared"/> — to keep the simulation reproducible.
|
||||
/// </summary>
|
||||
public UtilityAction<TContext>? SelectWeighted(
|
||||
TContext context,
|
||||
Random random,
|
||||
float threshold = 0f
|
||||
)
|
||||
{
|
||||
var total = 0f;
|
||||
for (var i = 0; i < _actions.Length; i++)
|
||||
{
|
||||
var score = _actions[i].Score(context);
|
||||
_scores[i] = score;
|
||||
if (score > threshold)
|
||||
{
|
||||
total += score;
|
||||
}
|
||||
}
|
||||
|
||||
if (total <= 0f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var roll = (float)random.NextDouble() * total;
|
||||
for (var i = 0; i < _actions.Length; i++)
|
||||
{
|
||||
if (_scores[i] <= threshold)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
roll -= _scores[i];
|
||||
if (roll <= 0f)
|
||||
{
|
||||
return _actions[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Floating-point slack can leave roll just above 0; fall back to the last qualifying action.
|
||||
for (var i = _actions.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (_scores[i] > threshold)
|
||||
{
|
||||
return _actions[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user