A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using NUnit.Framework;
5using osu.Framework.Allocation;
6using osu.Framework.Bindables;
7using osu.Framework.Graphics;
8using osu.Framework.Graphics.Containers;
9using osu.Framework.Graphics.Shapes;
10using osu.Framework.Graphics.Sprites;
11using osuTK;
12using osuTK.Graphics;
13
14namespace osu.Framework.Tests.Visual.Bindables
15{
16 public class TestSceneBindableAutoUnbinding : FrameworkTestScene
17 {
18 [Test]
19 public void TestBindableAutoUnbindingAssign()
20 {
21 TestExposedBindableDrawable drawable1 = null, drawable2 = null, drawable3 = null, drawable4 = null;
22
23 AddStep("add drawables", () =>
24 {
25 Child = new FillFlowContainer
26 {
27 RelativeSizeAxes = Axes.Both,
28 Direction = FillDirection.Vertical,
29 Spacing = new Vector2(5),
30 Children = new Drawable[]
31 {
32 drawable1 = new TestExposedBindableDrawable { Bindable = new Bindable<int>() },
33 drawable2 = new TestExposedBindableDrawable { Bindable = drawable1.Bindable.GetBoundCopy() },
34 drawable3 = new TestExposedBindableDrawable(true) { Bindable = drawable1.Bindable }, // an example of a bad usage of bindables
35 drawable4 = new TestExposedBindableDrawable { Bindable = drawable1.Bindable.GetBoundCopy() },
36 }
37 };
38 });
39
40 AddStep("attempt value transfer", () => drawable1.Bindable.Value = 10);
41
42 AddAssert("transfer 1-2 completed", () => drawable1.Bindable.Value == drawable2.Bindable.Value);
43 AddAssert("transfer 1-3 completed", () => drawable1.Bindable.Value == drawable3.Bindable.Value);
44 AddAssert("transfer 1-4 completed", () => drawable1.Bindable.Value == drawable4.Bindable.Value);
45
46 AddStep("expire child 4", () => drawable4.Expire());
47
48 AddStep("attempt value transfer", () => drawable1.Bindable.Value = 20);
49
50 AddAssert("transfer 1-2 completed", () => drawable1.Bindable.Value == drawable2.Bindable.Value);
51 AddAssert("transfer 1-3 completed", () => drawable1.Bindable.Value == drawable3.Bindable.Value);
52 AddAssert("transfer 1-4 skipped", () => drawable1.Bindable.Value != drawable4.Bindable.Value);
53
54 AddStep("expire child 3", () => drawable3.Expire());
55
56 AddStep("attempt value transfer", () => drawable1.Bindable.Value = 10);
57
58 // fails due to drawable3 being expired/disposed with a direct reference to drawable1's bindable.
59 AddAssert("transfer 1-2 fails", () => drawable1.Bindable.Value != drawable2.Bindable.Value);
60 }
61
62 [Test]
63 public void TestBindableAutoUnbindingResolution()
64 {
65 TestResolvedBindableDrawable drawable1 = null, drawable2 = null, drawable3 = null, drawable4 = null;
66
67 AddStep("add drawables", () =>
68 {
69 Child = new BindableExposingFillFlowContainer
70 {
71 RelativeSizeAxes = Axes.Both,
72 Direction = FillDirection.Vertical,
73 Spacing = new Vector2(5),
74 Children = new Drawable[]
75 {
76 drawable1 = new TestResolvedBindableDrawable(),
77 drawable2 = new TestResolvedBindableDrawable(),
78 drawable3 = new TestResolvedBindableDrawable(true), // an example of a bad usage of bindables
79 drawable4 = new TestResolvedBindableDrawable(),
80 }
81 };
82 });
83
84 AddStep("attempt value transfer", () => drawable1.Bindable.Value = 10);
85
86 AddAssert("transfer 1-2 completed", () => drawable1.Bindable.Value == drawable2.Bindable.Value);
87 AddAssert("transfer 1-3 completed", () => drawable1.Bindable.Value == drawable3.Bindable.Value);
88 AddAssert("transfer 1-4 completed", () => drawable1.Bindable.Value == drawable4.Bindable.Value);
89
90 AddStep("expire child 4", () => drawable4.Expire());
91
92 AddStep("attempt value transfer", () => drawable1.Bindable.Value = 20);
93
94 AddAssert("transfer 1-2 completed", () => drawable1.Bindable.Value == drawable2.Bindable.Value);
95 AddAssert("transfer 1-3 completed", () => drawable1.Bindable.Value == drawable3.Bindable.Value);
96 AddAssert("transfer 1-4 skipped", () => drawable1.Bindable.Value != drawable4.Bindable.Value);
97
98 AddStep("expire child 3", () => drawable3.Expire());
99
100 AddStep("attempt value transfer", () => drawable1.Bindable.Value = 10);
101
102 // fails due to drawable3 being expired/disposed with a direct reference to drawable1's bindable.
103 AddAssert("transfer 1-2 fails", () => drawable1.Bindable.Value != drawable2.Bindable.Value);
104 }
105
106 public class BindableExposingFillFlowContainer : FillFlowContainer
107 {
108 [Cached]
109 private Bindable<int> bindable = new Bindable<int>();
110 }
111
112 public class TestResolvedBindableDrawable : TestExposedBindableDrawable
113 {
114 private readonly bool badActor;
115
116 public TestResolvedBindableDrawable(bool badActor = false)
117 : base(badActor)
118 {
119 this.badActor = badActor;
120 }
121
122 [BackgroundDependencyLoader]
123 private void load(Bindable<int> parentBindable)
124 {
125 if (badActor)
126 Bindable = parentBindable;
127 else
128 {
129 Bindable = new Bindable<int>();
130 Bindable.BindTo(parentBindable);
131 }
132 }
133 }
134
135 public class TestExposedBindableDrawable : CompositeDrawable
136 {
137 public Bindable<int> Bindable;
138
139 private readonly SpriteText spriteText;
140
141 public TestExposedBindableDrawable(bool badActor = false)
142 {
143 Size = new Vector2(50);
144 InternalChildren = new Drawable[]
145 {
146 new Box
147 {
148 Colour = badActor ? Color4.Red : Color4.Green,
149 RelativeSizeAxes = Axes.Both,
150 },
151 spriteText = new SpriteText
152 {
153 Anchor = Anchor.Centre,
154 Origin = Anchor.Centre,
155 }
156 };
157 }
158
159 protected override void LoadComplete()
160 {
161 base.LoadComplete();
162 Bindable.BindValueChanged(val => spriteText.Text = val.NewValue.ToString(), true);
163 }
164 }
165 }
166}