Skip to content

Commit

Permalink
Merge pull request #6362 from peppy/has-flags-fast
Browse files Browse the repository at this point in the history
Bring back `HasFlagsFast`
  • Loading branch information
smoogipoo authored Aug 19, 2024
2 parents 69fcbfe + 23a3369 commit 45af6e9
Show file tree
Hide file tree
Showing 44 changed files with 265 additions and 137 deletions.
1 change: 1 addition & 0 deletions CodeAnalysis/BannedSymbols.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use obj
M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable<T> or EqualityComparer<T>.Default instead.
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.
F:System.UriKind.RelativeOrAbsolute;Incompatible results when run on mono (see https://www.mono-project.com/docs/faq/known-issues/urikind-relativeorabsolute/). Use Validation.TryParseUri(string, out Uri?) instead.
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty.
Expand Down
3 changes: 2 additions & 1 deletion osu.Framework.Android/Graphics/Video/AndroidVideoDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Runtime.InteropServices;
using FFmpeg.AutoGen;
using Java.Interop;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Video;
using osu.Framework.Logging;
Expand Down Expand Up @@ -154,7 +155,7 @@ public AndroidVideoDecoder(IRenderer renderer, Stream videoStream)
HardwareVideoDecoder targetHwDecoders
)
{
if (targetHwDecoders.HasFlag(HardwareVideoDecoder.MediaCodec))
if (targetHwDecoders.HasFlagFast(HardwareVideoDecoder.MediaCodec))
{
string? formatName = Marshal.PtrToStringAnsi((IntPtr)inputFormat->name);

Expand Down
42 changes: 42 additions & 0 deletions osu.Framework.Benchmarks/BenchmarkEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using BenchmarkDotNet.Attributes;
using osu.Framework.Extensions.EnumExtensions;

namespace osu.Framework.Benchmarks
{
[MemoryDiagnoser]
public class BenchmarkEnum
{
[Benchmark]
public bool HasFlag()
{
#pragma warning disable RS0030 // (banned API)
return (FlagsEnum.Flag2 | FlagsEnum.Flag3).HasFlag(FlagsEnum.Flag2);
#pragma warning restore RS0030
}

[Benchmark]
public bool BitwiseAnd()
{
return ((FlagsEnum.Flag2 | FlagsEnum.Flag3) & FlagsEnum.Flag2) > 0;
}

[Benchmark]
public bool HasFlagFast()
{
return (FlagsEnum.Flag2 | FlagsEnum.Flag3).HasFlagFast(FlagsEnum.Flag2);
}

[Flags]
private enum FlagsEnum
{
Flag1 = 1,
Flag2 = 2,
Flag3 = 4,
Flag4 = 8
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
Expand Down Expand Up @@ -50,10 +51,10 @@ public TestSceneSafeAreaOverrides()

private void addBoxAssert(OverrideTestContainer container)
{
bool leftOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlag(Edges.Left);
bool topOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlag(Edges.Top);
bool rightOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlag(Edges.Right);
bool bottomOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlag(Edges.Bottom);
bool leftOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlagFast(Edges.Left);
bool topOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlagFast(Edges.Top);
bool rightOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlagFast(Edges.Right);
bool bottomOverridden = container.SafeAreaContainer.SafeAreaOverrideEdges.HasFlagFast(Edges.Bottom);

AddAssert($"\"{container.Name}\" overrides correctly", () =>
leftOverridden == container.SafeAreaContainer.Padding.Left < 0
Expand Down
5 changes: 3 additions & 2 deletions osu.Framework.Tests/Visual/Drawables/TestSceneIsMaskedAway.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#nullable disable

using NUnit.Framework;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
Expand Down Expand Up @@ -84,7 +85,7 @@ public void TestBoxSlightlyOutOfBoundsMasking(Anchor anchor)
Anchor = anchor,
Origin = anchor,
Size = new Vector2(10),
Position = new Vector2(anchor.HasFlag(Anchor.x0) ? -5 : 5, anchor.HasFlag(Anchor.y0) ? -5 : 5),
Position = new Vector2(anchor.HasFlagFast(Anchor.x0) ? -5 : 5, anchor.HasFlagFast(Anchor.y0) ? -5 : 5),
}
};
});
Expand Down Expand Up @@ -117,7 +118,7 @@ public void TestBoxFullyOutOfBoundsMasking(Anchor anchor)
Anchor = anchor,
Origin = anchor,
Size = new Vector2(10),
Position = new Vector2(anchor.HasFlag(Anchor.x0) ? -20 : 20, anchor.HasFlag(Anchor.y0) ? -20 : 20),
Position = new Vector2(anchor.HasFlagFast(Anchor.x0) ? -20 : 20, anchor.HasFlagFast(Anchor.y0) ? -20 : 20),
}
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
Expand Down Expand Up @@ -199,14 +200,14 @@ public void TestMenuNotOnScreenWhenTargetSignificantlyOffScreen(Anchor anchor)
{
box.Anchor = anchor;
if (anchor.HasFlag(Anchor.x0))
if (anchor.HasFlagFast(Anchor.x0))
box.X -= contextMenuContainer.CurrentMenu.DrawWidth + 10;
else if (anchor.HasFlag(Anchor.x2))
else if (anchor.HasFlagFast(Anchor.x2))
box.X += 10;
if (anchor.HasFlag(Anchor.y0))
if (anchor.HasFlagFast(Anchor.y0))
box.Y -= contextMenuContainer.CurrentMenu.DrawHeight + 10;
else if (anchor.HasFlag(Anchor.y2))
else if (anchor.HasFlagFast(Anchor.y2))
box.Y += 10;
});

Expand Down
3 changes: 2 additions & 1 deletion osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Runtime.InteropServices;
using ManagedBass;
using ManagedBass.Mix;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Statistics;

namespace osu.Framework.Audio.Mixing.Bass
Expand Down Expand Up @@ -158,7 +159,7 @@ public PlaybackState ChannelIsActive(IBassAudioChannel channel)

// The channel is always in a playing state unless stopped or stalled as it's a decoding channel. Retrieve the true playing state from the mixer channel.
if (state == PlaybackState.Playing)
state = BassMix.ChannelFlags(channel.Handle, BassFlags.Default, BassFlags.Default).HasFlag(BassFlags.MixerChanPause) ? PlaybackState.Paused : state;
state = BassMix.ChannelFlags(channel.Handle, BassFlags.Default, BassFlags.Default).HasFlagFast(BassFlags.MixerChanPause) ? PlaybackState.Paused : state;

return state;
}
Expand Down
17 changes: 9 additions & 8 deletions osu.Framework/Extensions/AnchorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osuTK;
Expand All @@ -21,9 +22,9 @@ public static Anchor Opposite(this Anchor anchor)
if (anchor == Anchor.Custom)
throw new ArgumentException($"{nameof(Anchor.Custom)} is not supported.", nameof(anchor));

if (anchor.HasFlag(Anchor.x0) || anchor.HasFlag(Anchor.x2))
if (anchor.HasFlagFast(Anchor.x0) || anchor.HasFlagFast(Anchor.x2))
anchor ^= Anchor.x0 | Anchor.x2;
if (anchor.HasFlag(Anchor.y0) || anchor.HasFlag(Anchor.y2))
if (anchor.HasFlagFast(Anchor.y0) || anchor.HasFlagFast(Anchor.y2))
anchor ^= Anchor.y0 | Anchor.y2;

return anchor;
Expand All @@ -39,18 +40,18 @@ public static Vector2 PositionOnQuad(this Anchor anchor, Quad quad)

Vector2 position = new Vector2();

if (anchor.HasFlag(Anchor.x0))
if (anchor.HasFlagFast(Anchor.x0))
position.X = quad.TopLeft.X;
else if (anchor.HasFlag(Anchor.x1))
else if (anchor.HasFlagFast(Anchor.x1))
position.X = quad.Centre.X;
else if (anchor.HasFlag(Anchor.x2))
else if (anchor.HasFlagFast(Anchor.x2))
position.X = quad.BottomRight.X;

if (anchor.HasFlag(Anchor.y0))
if (anchor.HasFlagFast(Anchor.y0))
position.Y = quad.TopLeft.Y;
else if (anchor.HasFlag(Anchor.y1))
else if (anchor.HasFlagFast(Anchor.y1))
position.Y = quad.Centre.Y;
else if (anchor.HasFlag(Anchor.y2))
else if (anchor.HasFlagFast(Anchor.y2))
position.Y = quad.BottomRight.Y;

return position;
Expand Down
44 changes: 44 additions & 0 deletions osu.Framework/Extensions/EnumExtensions/EnumExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Utils;

Expand Down Expand Up @@ -54,5 +55,48 @@ public static IEnumerable<T> GetValuesInOrder<T>(this IEnumerable<T> items)
throw new ArgumentException($"Not all values of {typeof(T)} have {nameof(OrderAttribute)} specified.");
});
}

#pragma warning disable RS0030 // (banned API)
/// <summary>
/// A fast alternative functionally equivalent to <see cref="Enum.HasFlag"/>, eliminating boxing in all scenarios.
/// </summary>
/// <param name="enumValue">The enum to check.</param>
/// <param name="flag">The flag to check for.</param>
#pragma warning restore RS0030
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagFast<T>(this T enumValue, T flag) where T : unmanaged, Enum
{
// Note: Using a switch statement would eliminate inlining.

if (sizeof(T) == 1)
{
byte value1 = Unsafe.As<T, byte>(ref enumValue);
byte value2 = Unsafe.As<T, byte>(ref flag);
return (value1 & value2) == value2;
}

if (sizeof(T) == 2)
{
short value1 = Unsafe.As<T, short>(ref enumValue);
short value2 = Unsafe.As<T, short>(ref flag);
return (value1 & value2) == value2;
}

if (sizeof(T) == 4)
{
int value1 = Unsafe.As<T, int>(ref enumValue);
int value2 = Unsafe.As<T, int>(ref flag);
return (value1 & value2) == value2;
}

if (sizeof(T) == 8)
{
long value1 = Unsafe.As<T, long>(ref enumValue);
long value2 = Unsafe.As<T, long>(ref flag);
return (value1 & value2) == value2;
}

throw new ArgumentException($"Invalid enum type provided: {typeof(T)}.");
}
}
}
3 changes: 2 additions & 1 deletion osu.Framework/Extensions/TaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Extensions.EnumExtensions;

namespace osu.Framework.Extensions
{
Expand Down Expand Up @@ -42,7 +43,7 @@ public static T GetResultSafely<T>(this Task<T> task)
private static bool isWaitingValid(Task task)
{
// In the case the task has been started with the LongRunning flag, it will not be in the TPL thread pool and we can allow waiting regardless.
if (task.CreationOptions.HasFlag(TaskCreationOptions.LongRunning))
if (task.CreationOptions.HasFlagFast(TaskCreationOptions.LongRunning))
return true;

// Otherwise only allow waiting from a non-TPL thread pool thread.
Expand Down
5 changes: 3 additions & 2 deletions osu.Framework/Graphics/Containers/AudioContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections;
using System.Collections.Generic;
using osu.Framework.Audio;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Effects;
using osuTK;
Expand Down Expand Up @@ -40,8 +41,8 @@ public override Vector2 Size
set
{
base.Size = new Vector2(
RelativeSizeAxes.HasFlag(Axes.X) ? 1 : value.X,
RelativeSizeAxes.HasFlag(Axes.Y) ? 1 : value.Y);
RelativeSizeAxes.HasFlagFast(Axes.X) ? 1 : value.X,
RelativeSizeAxes.HasFlagFast(Axes.Y) ? 1 : value.Y);

container.Size = value;
}
Expand Down
21 changes: 11 additions & 10 deletions osu.Framework/Graphics/Containers/CompositeDrawable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Development;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Primitives;
Expand Down Expand Up @@ -722,9 +723,9 @@ protected virtual bool CheckChildrenLife()
{
var state = checkChildLife(internalChildren[i]);

anyAliveChanged |= state.HasFlag(ChildLifeStateChange.MadeAlive) || state.HasFlag(ChildLifeStateChange.MadeDead);
anyAliveChanged |= state.HasFlagFast(ChildLifeStateChange.MadeAlive) || state.HasFlagFast(ChildLifeStateChange.MadeDead);

if (state.HasFlag(ChildLifeStateChange.Removed))
if (state.HasFlagFast(ChildLifeStateChange.Removed))
i--;
}

Expand Down Expand Up @@ -1839,7 +1840,7 @@ public override float Width
{
get
{
if (!isComputingChildrenSizeDependencies && AutoSizeAxes.HasFlag(Axes.X))
if (!isComputingChildrenSizeDependencies && AutoSizeAxes.HasFlagFast(Axes.X))
updateChildrenSizeDependencies();
return base.Width;
}
Expand All @@ -1857,7 +1858,7 @@ public override float Height
{
get
{
if (!isComputingChildrenSizeDependencies && AutoSizeAxes.HasFlag(Axes.Y))
if (!isComputingChildrenSizeDependencies && AutoSizeAxes.HasFlagFast(Axes.Y))
updateChildrenSizeDependencies();
return base.Height;
}
Expand Down Expand Up @@ -1913,16 +1914,16 @@ private Vector2 computeAutoSize()

Vector2 cBound = c.RequiredParentSizeToFit;

if (!c.BypassAutoSizeAxes.HasFlag(Axes.X))
if (!c.BypassAutoSizeAxes.HasFlagFast(Axes.X))
maxBoundSize.X = Math.Max(maxBoundSize.X, cBound.X);

if (!c.BypassAutoSizeAxes.HasFlag(Axes.Y))
if (!c.BypassAutoSizeAxes.HasFlagFast(Axes.Y))
maxBoundSize.Y = Math.Max(maxBoundSize.Y, cBound.Y);
}

if (!AutoSizeAxes.HasFlag(Axes.X))
if (!AutoSizeAxes.HasFlagFast(Axes.X))
maxBoundSize.X = DrawSize.X;
if (!AutoSizeAxes.HasFlag(Axes.Y))
if (!AutoSizeAxes.HasFlagFast(Axes.Y))
maxBoundSize.Y = DrawSize.Y;

return new Vector2(maxBoundSize.X, maxBoundSize.Y);
Expand All @@ -1942,8 +1943,8 @@ private void updateAutoSize()
Vector2 b = computeAutoSize() + Padding.Total;

autoSizeResizeTo(new Vector2(
AutoSizeAxes.HasFlag(Axes.X) ? b.X : base.Width,
AutoSizeAxes.HasFlag(Axes.Y) ? b.Y : base.Height
AutoSizeAxes.HasFlagFast(Axes.X) ? b.X : base.Width,
AutoSizeAxes.HasFlagFast(Axes.Y) ? b.Y : base.Height
), AutoSizeDuration, AutoSizeEasing);

//note that this is called before autoSize becomes valid. may be something to consider down the line.
Expand Down
5 changes: 3 additions & 2 deletions osu.Framework/Graphics/Containers/ContainerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using osuTK;
using System;
using System.Collections.Generic;
using osu.Framework.Extensions.EnumExtensions;

namespace osu.Framework.Graphics.Containers
{
Expand Down Expand Up @@ -41,13 +42,13 @@ public static TContainer Wrap<TContainer, TChild>(this TContainer container, TCh

// For anchor/origin positioning to be preserved correctly,
// relatively sized axes must be lifted to the wrapping container.
if (container.RelativeSizeAxes.HasFlag(Axes.X))
if (container.RelativeSizeAxes.HasFlagFast(Axes.X))
{
container.Width = drawable.Width;
drawable.Width = 1;
}

if (container.RelativeSizeAxes.HasFlag(Axes.Y))
if (container.RelativeSizeAxes.HasFlagFast(Axes.Y))
{
container.Height = drawable.Height;
drawable.Height = 1;
Expand Down
Loading

0 comments on commit 45af6e9

Please sign in to comment.