Skip to content

Commit

Permalink
Add GetItems() extension for IRobustRandom (#4975)
Browse files Browse the repository at this point in the history
* refactor: RobustRandom and RandomExtensions namespace change to file-scoped

* refactor: IRobustRandom xml-doc methods rearranged to be more structured.

* feat: GetItems methods added to RandomExtensions, tests for new methods added.

* fix: GetItems will not request count-1 from next random, as System.Random.Next have upper bound excluded.

* fix: enforced standard deviation on picking next items in RandomExtensions.GetItems + fixed hashet initial capacity +removed mandatory hashset allocation

* refactor: specified border values interaction in IRobustRandom xml-doc

* refactor: updated relese-notes

* refactor: changed release-notes PROPERLY

* fix: order by which unique random items are picked in RandomExtensions.GetItems were fixed to ACTUALLY follow normal distribution

* refractor: added comment for devious RandomExtensions.GetItems only-unique logic

* reduce code duplication

* Cleanup code a bit

Rename variables, and make it a bit more compact.
Also, IMO the description is unnecessary

* Remove obsolete extension

* Remove incorrect O(n) comments.

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
  • Loading branch information
Fildrance and pa.pecherskij authored Mar 29, 2024
1 parent 958b5dd commit 73c1449
Show file tree
Hide file tree
Showing 7 changed files with 667 additions and 204 deletions.
6 changes: 3 additions & 3 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Release notes for RobustToolbox.
# Release notes for RobustToolbox.

<!--
NOTE: automatically updated sometimes by version.py.
Expand Down Expand Up @@ -39,7 +39,7 @@ END TEMPLATE-->

### New features

*None yet*
* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections.

### Bugfixes

Expand All @@ -51,7 +51,7 @@ END TEMPLATE-->

### Internal

*None yet*
* `Shuffle<T>(Span<T>, System.Random)` has been removed, just use the builtin method.


## 217.0.0
Expand Down
2 changes: 1 addition & 1 deletion Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void LoadDirectory(ResPath path, bool overwrite = false,
.ToArray();

// Shuffle to avoid input data patterns causing uneven thread workloads.
RandomExtensions.Shuffle(streams.AsSpan(), new System.Random());
(new System.Random()).Shuffle(streams.AsSpan());

var sawmill = _logManager.GetSawmill("eng");

Expand Down
2 changes: 1 addition & 1 deletion Robust.Shared/Prototypes/PrototypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ public void ResolveResults()
}).ToArray();

// Randomize to remove any patterns that could cause uneven load.
RandomExtensions.Shuffle(allResults.AsSpan(), rand);
rand.Shuffle(allResults.AsSpan());

// Create channel that all AfterDeserialization hooks in this group will be sent into.
var hooksChannelOptions = new UnboundedChannelOptions
Expand Down
141 changes: 105 additions & 36 deletions Robust.Shared/Random/IRobustRandom.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,141 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Collections;
using Robust.Shared.Maths;
using Robust.Shared.Toolshed.Commands.Generic;

namespace Robust.Shared.Random;

/// <summary>
/// Wrapper around random number generator helping methods.
/// </summary>
public interface IRobustRandom
{
/// <summary>
/// Get the underlying System.Random
/// </summary>
/// <returns></returns>
/// <summary> Get the underlying <see cref="Random"/>.</summary>
System.Random GetRandom();

/// <summary> Set seed for underlying <see cref="Random"/>. </summary>
void SetSeed(int seed);

/// <summary> Get random <see cref="float"/> value between 0 (included) and 1 (excluded). </summary>
float NextFloat();

/// <summary> Get random <see cref="float"/> value in range of <paramref name="minValue"/> (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="minValue">Random value should be greater or equal to this value.</param>
/// <param name="maxValue">Random value should be less then this value.</param>
public float NextFloat(float minValue, float maxValue)
=> NextFloat() * (maxValue - minValue) + minValue;

/// <summary> Get random <see cref="float"/> value in range of 0 (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="maxValue">Random value should be less then this value.</param>
public float NextFloat(float maxValue) => NextFloat() * maxValue;

/// <summary> Get random <see cref="int"/> value. </summary>
int Next();
int Next(int minValue, int maxValue);
TimeSpan Next(TimeSpan minTime, TimeSpan maxTime);
TimeSpan Next(TimeSpan maxTime);

/// <summary> Get random <see cref="int"/> value in range of 0 (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="maxValue">Random value should be less then this value.</param>
int Next(int maxValue);

/// <summary> Get random <see cref="int"/> value in range of <paramref name="minValue"/> (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="minValue">Random value should be greater or equal to this value.</param>
/// <param name="maxValue">Random value should be less then this value.</param>
int Next(int minValue, int maxValue);

/// <summary> Get random <see cref="byte"/> value between 0 (included) and <see cref="byte.MaxValue"/> (excluded). </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte NextByte()
=> NextByte(byte.MaxValue);

/// <summary> Get random <see cref="byte"/> value in range of 0 (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="maxValue">Random value should be less then this value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte NextByte(byte maxValue)
=> NextByte(0, maxValue);

/// <summary> Get random <see cref="byte"/> value in range of <paramref name="minValue"/> (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="minValue">Random value should be greater or equal to this value.</param>
/// <param name="maxValue">Random value should be less then this value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte NextByte(byte minValue, byte maxValue)
=> (byte)Next(minValue, maxValue);

/// <summary> Get random <see cref="double"/> value between 0 (included) and 1 (excluded). </summary>
double NextDouble();
double NextDouble(double minValue, double maxValue) => NextDouble() * (maxValue - minValue) + minValue;

/// <summary> Get random <see cref="double"/> value in range of 0 (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="maxValue">Random value should be less then this value.</param>
double Next(double maxValue)
=> NextDouble() * maxValue;

/// <summary> Get random <see cref="double"/> value in range of <paramref name="minValue"/> (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="minValue">Random value should be greater or equal to this value.</param>
/// <param name="maxValue">Random value should be less then this value.</param>
double NextDouble(double minValue, double maxValue)
=> NextDouble() * (maxValue - minValue) + minValue;

/// <summary> Get random <see cref="TimeSpan"/> value in range of <see cref="TimeSpan.Zero"/> (included) and <paramref name="maxTime"/> (excluded). </summary>
/// <param name="maxTime">Random value should be less then this value.</param>
TimeSpan Next(TimeSpan maxTime);

/// <summary> Get random <see cref="TimeSpan"/> value in range of <paramref name="minTime"/> (included) and <paramref name="maxTime"/> (excluded). </summary>
/// <param name="minTime">Random value should be greater or equal to this value.</param>
/// <param name="maxTime">Random value should be less then this value.</param>
TimeSpan Next(TimeSpan minTime, TimeSpan maxTime);

/// <summary> Fill buffer with random bytes (values). </summary>
void NextBytes(byte[] buffer);

public Angle NextAngle() => NextFloat() * MathF.Tau;
public Angle NextAngle(Angle minValue, Angle maxValue) => NextFloat() * (maxValue - minValue) + minValue;
public Angle NextAngle(Angle maxValue) => NextFloat() * maxValue;
/// <summary> Get random <see cref="Angle"/> value in range of 0 (included) and <see cref="MathF.Tau"/> (excluded). </summary>
public Angle NextAngle()
=> NextFloat() * MathF.Tau;

/// <summary> Get random <see cref="Angle"/> value in range of 0 (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="maxValue">Random value should be less then this value.</param>
public Angle NextAngle(Angle maxValue)
=> NextFloat() * maxValue;

/// <summary> Get random <see cref="Angle"/> value in range of <paramref name="minValue"/> (included) and <paramref name="maxValue"/> (excluded). </summary>
/// <param name="minValue">Random value should be greater or equal to this value.</param>
/// <param name="maxValue">Random value should be less then this value.</param>
public Angle NextAngle(Angle minValue, Angle maxValue)
=> NextFloat() * (maxValue - minValue) + minValue;

/// <summary>
/// Random vector, created from a uniform distribution of magnitudes and angles.
/// </summary>
/// <param name="maxMagnitude">Max value for randomized vector magnitude (excluded).</param>
public Vector2 NextVector2(float maxMagnitude = 1)
=> NextVector2(0, maxMagnitude);

/// <summary>
/// Random vector, created from a uniform distribution of magnitudes and angles.
/// </summary>
/// <param name="minMagnitude">Min value for randomized vector magnitude (included).</param>
/// <param name="maxMagnitude">Max value for randomized vector magnitude (excluded).</param>
/// <remarks>
/// In general, NextVector2(1) will tend to result in vectors with smaller magnitudes than
/// NextVector2Box(1,1), even if you ignored any vectors with a magnitude larger than one.
/// </remarks>
public Vector2 NextVector2(float minMagnitude, float maxMagnitude) => NextAngle().RotateVec(new Vector2(NextFloat(minMagnitude, maxMagnitude), 0));
public Vector2 NextVector2(float maxMagnitude = 1) => NextVector2(0, maxMagnitude);
public Vector2 NextVector2(float minMagnitude, float maxMagnitude)
=> NextAngle().RotateVec(new Vector2(NextFloat(minMagnitude, maxMagnitude), 0));

/// <summary>
/// Random vector, created from a uniform distribution of x and y coordinates lying inside some box.
/// </summary>
public Vector2 NextVector2Box(float minX, float minY, float maxX, float maxY) => new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY));
public Vector2 NextVector2Box(float maxAbsX = 1, float maxAbsY = 1) => NextVector2Box(-maxAbsX, -maxAbsY, maxAbsX, maxAbsY);
public Vector2 NextVector2Box(float minX, float minY, float maxX, float maxY)
=> new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY));

/// <summary>
/// Random vector, created from a uniform distribution of x and y coordinates lying inside some box.
/// Box will have coordinates starting at [-<paramref name="maxAbsX"/> , -<paramref name="maxAbsY"/>]
/// and ending in [<paramref name="maxAbsX"/> , <paramref name="maxAbsY"/>]
/// </summary>
public Vector2 NextVector2Box(float maxAbsX = 1, float maxAbsY = 1)
=> NextVector2Box(-maxAbsX, -maxAbsY, maxAbsX, maxAbsY);

/// <summary> Randomly switches positions in collection. </summary>
void Shuffle<T>(IList<T> list)
{
var n = list.Count;
Expand All @@ -62,6 +147,7 @@ void Shuffle<T>(IList<T> list)
}
}

/// <summary> Randomly switches positions in collection. </summary>
void Shuffle<T>(Span<T> list)
{
var n = list.Length;
Expand All @@ -73,6 +159,7 @@ void Shuffle<T>(Span<T> list)
}
}

/// <summary> Randomly switches positions in collection. </summary>
void Shuffle<T>(ValueList<T> list)
{
var n = list.Count;
Expand All @@ -83,24 +170,6 @@ void Shuffle<T>(ValueList<T> list)
(list[k], list[n]) = (list[n], list[k]);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte NextByte(byte maxValue)
{
return NextByte(0, maxValue);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte NextByte()
{
return NextByte(byte.MaxValue);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte NextByte(byte minValue, byte maxValue)
{
return (byte) Next(minValue, maxValue);
}
}

public static class RandomHelpers
Expand Down Expand Up @@ -136,6 +205,6 @@ public static byte NextByte(this System.Random random)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte NextByte(this System.Random random, byte minValue, byte maxValue)
{
return (byte) random.Next(minValue, maxValue);
return (byte)random.Next(minValue, maxValue);
}
}
Loading

0 comments on commit 73c1449

Please sign in to comment.