Skip to content

Commit

Permalink
Fix similar Bindable-related crashes
Browse files Browse the repository at this point in the history
  • Loading branch information
smoogipoo committed Sep 25, 2024
1 parent 3ab04d9 commit fd4891c
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 7 deletions.
52 changes: 52 additions & 0 deletions osu.Game.Tests/Utils/BindableValueAccessorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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 NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Utils;

namespace osu.Game.Tests.Utils
{
[TestFixture]
public class BindableValueAccessorTest
{
[Test]
public void GetValue()
{
const int value = 1337;

BindableInt bindable = new BindableInt(value);
Assert.That(BindableValueAccessor.GetValue(bindable), Is.EqualTo(value));
}

[Test]
public void SetValue()
{
const int value = 1337;

BindableInt bindable = new BindableInt();
BindableValueAccessor.SetValue(bindable, value);

Assert.That(bindable.Value, Is.EqualTo(value));
}

[Test]
public void GetInvalidBindable()
{
BindableList<object> list = new BindableList<object>();
Assert.That(BindableValueAccessor.GetValue(list), Is.EqualTo(list));
}

[Test]
public void SetInvalidBindable()
{
const int value = 1337;

BindableList<int> list = new BindableList<int> { value };
BindableValueAccessor.SetValue(list, 2);

Assert.That(list, Has.Exactly(1).Items);
Assert.That(list[0], Is.EqualTo(value));
}
}
}
7 changes: 2 additions & 5 deletions osu.Game/Configuration/SettingSourceAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
Expand All @@ -15,6 +14,7 @@
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Utils;

namespace osu.Game.Configuration
{
Expand Down Expand Up @@ -228,10 +228,7 @@ public static object GetUnderlyingSettingValue(this object setting)
return b.Value;

case IBindable u:
// An unknown (e.g. enum) generic type.
var valueMethod = u.GetType().GetProperty(nameof(IBindable<int>.Value));
Debug.Assert(valueMethod != null);
return valueMethod.GetValue(u)!;
return BindableValueAccessor.GetValue(u);

default:
// fall back for non-bindable cases.
Expand Down
3 changes: 1 addition & 2 deletions osu.Game/Rulesets/Mods/Mod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,7 @@ public void CopyCommonSettingsFrom(Mod source)

// TODO: special case for handling number types

PropertyInfo property = targetSetting.GetType().GetProperty(nameof(Bindable<bool>.Value))!;
property.SetValue(targetSetting, property.GetValue(sourceSetting));
BindableValueAccessor.SetValue(targetSetting, BindableValueAccessor.GetValue(sourceSetting));
}
}

Expand Down
44 changes: 44 additions & 0 deletions osu.Game/Utils/BindableValueAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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 System.Linq;
using System.Reflection;
using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;

namespace osu.Game.Utils
{
internal static class BindableValueAccessor
{
private static readonly MethodInfo get_method = typeof(BindableValueAccessor).GetMethod(nameof(getValue), BindingFlags.Static | BindingFlags.NonPublic)!;
private static readonly MethodInfo set_method = typeof(BindableValueAccessor).GetMethod(nameof(setValue), BindingFlags.Static | BindingFlags.NonPublic)!;

public static object GetValue(IBindable bindable)
{
Type? bindableWithValueType = bindable.GetType().GetInterfaces().FirstOrDefault(isBindableT);
if (bindableWithValueType == null)
return bindable;

return get_method.MakeGenericMethod(bindableWithValueType.GenericTypeArguments[0]).Invoke(null, [bindable])!;
}

public static void SetValue(IBindable bindable, object value)
{
Type? bindableWithValueType = bindable.GetType().EnumerateBaseTypes().FirstOrDefault(isBindableT);
if (bindableWithValueType == null)
return;

set_method.MakeGenericMethod(bindableWithValueType.GenericTypeArguments[0]).Invoke(null, [bindable, value]);
}

private static bool isBindableT(Type type)
=> type.IsGenericType
&& (type.GetGenericTypeDefinition() == typeof(Bindable<>)
|| type.GetGenericTypeDefinition() == typeof(IBindable<>));

private static object getValue<T>(object bindable) => ((IBindable<T>)bindable).Value!;

private static object setValue<T>(object bindable, object value) => ((Bindable<T>)bindable).Value = (T)value;
}
}

0 comments on commit fd4891c

Please sign in to comment.