Merge branch 'master' into window-backend-removal

This commit is contained in:
Dean Herbert
2020-11-17 02:15:08 +09:00
9 changed files with 120 additions and 68 deletions

View File

@@ -13,6 +13,18 @@
"commands": [
"dotnet-format"
]
},
"jetbrains.resharper.globaltools": {
"version": "2020.2.4",
"commands": [
"jb"
]
},
"nvika": {
"version": "2.0.0",
"commands": [
"nvika"
]
}
}
}

12
.vscode/tasks.json vendored
View File

@@ -9,7 +9,6 @@
"command": "dotnet",
"args": [
"build",
"--no-restore",
"-p:GenerateFullPaths=true",
"-m",
"-verbosity:m",
@@ -24,7 +23,6 @@
"command": "dotnet",
"args": [
"build",
"--no-restore",
"-p:Configuration=Release",
"-p:GenerateFullPaths=true",
"-m",
@@ -33,16 +31,6 @@
],
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Restore",
"type": "shell",
"command": "dotnet",
"args": [
"restore",
"osu-framework.sln"
],
"problemMatcher": []
}
]
}

View File

@@ -28,7 +28,6 @@ This framework is intended to take steps beyond what you would normally expect f
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
- Visual Studio / Rider users should load the project via one of the platform-specific .slnf files, rather than the main .sln. This will allow access to template run configurations.
- Visual Studio Code users must run the `Restore` task before any build attempt.
### Code analysis

View File

@@ -1,9 +1,6 @@
using System.Threading;
#addin "nuget:?package=CodeFileSanity&version=0.0.36"
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
#tool "nuget:?package=Python&version=3.7.2"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
var pythonPath = GetFiles("./tools/python.*/tools/python.exe").First();
var waitressPath = pythonPath.GetDirectory().CombineWithFilePath("Scripts/waitress-serve.exe");
@@ -107,22 +104,15 @@ Task("Test")
DotNetCoreVSTest(testAssemblies, settings);
});
// windows only because both inspectcore and nvika depend on net45
Task("InspectCode")
.WithCriteria(IsRunningOnWindows())
.IsDependentOn("Compile")
.Does(() => {
var inspectcodereport = tempDirectory.CombineWithFilePath("inspectcodereport.xml");
var cacheDir = tempDirectory.Combine("inspectcode");
InspectCode(desktopSlnf, new InspectCodeSettings {
CachesHome = tempDirectory.Combine("inspectcode"),
OutputFile = inspectcodereport,
ArgumentCustomization = args => args.Append("--verbosity=WARN")
});
int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors");
if (returnCode != 0)
throw new Exception($"inspectcode failed with return code {returnCode}");
DotNetCoreTool(rootDirectory.FullPath,
"jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity=WARN");
DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors");
});
Task("CodeFileSanity")

View File

@@ -3,6 +3,7 @@
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Emp3/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Epng/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ewav/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Eh/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=2A66DD92_002DADB1_002D4994_002D89E2_002DC94E04ACDA0D_002Fd_003AMigrations/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D9A367C9_002D4C1A_002D489F_002D9B05_002DA0CEA2B53B58/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String>

View File

@@ -47,12 +47,12 @@ namespace osu.Framework.Tests.Text
public TextBuilderTest()
{
fontStore = new TestStore(
(normal_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
(normal_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
(normal_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning)),
(fixed_width_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
(fixed_width_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
(fixed_width_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning))
new GlyphEntry(normal_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
new GlyphEntry(normal_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
new GlyphEntry(normal_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning)),
new GlyphEntry(fixed_width_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
new GlyphEntry(fixed_width_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
new GlyphEntry(fixed_width_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning))
);
}
@@ -326,10 +326,10 @@ namespace osu.Framework.Tests.Text
var font = new TestFontUsage("test");
var nullFont = new TestFontUsage(null);
var builder = new TextBuilder(new TestStore(
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
(nullFont, new TestGlyph('a', 0, 0, 0, 0, 0, 0)),
(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
(nullFont, new TestGlyph('?', 0, 0, 0, 0, 0, 0))
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(nullFont, new TestGlyph('a', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(nullFont, new TestGlyph('?', 0, 0, 0, 0, 0, 0))
), font);
builder.AddText("a");
@@ -346,10 +346,10 @@ namespace osu.Framework.Tests.Text
var font = new TestFontUsage("test");
var nullFont = new TestFontUsage(null);
var builder = new TextBuilder(new TestStore(
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
), font);
builder.AddText("a");
@@ -367,10 +367,10 @@ namespace osu.Framework.Tests.Text
var font = new TestFontUsage("test");
var nullFont = new TestFontUsage(null);
var builder = new TextBuilder(new TestStore(
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
new GlyphEntry(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
), font);
builder.AddText("a");
@@ -414,9 +414,9 @@ namespace osu.Framework.Tests.Text
private class TestStore : ITexturedGlyphLookupStore
{
private readonly (FontUsage font, ITexturedCharacterGlyph glyph)[] glyphs;
private readonly GlyphEntry[] glyphs;
public TestStore(params (FontUsage font, ITexturedCharacterGlyph glyph)[] glyphs)
public TestStore(params GlyphEntry[] glyphs)
{
this.glyphs = glyphs;
}
@@ -424,14 +424,28 @@ namespace osu.Framework.Tests.Text
public ITexturedCharacterGlyph Get(string fontName, char character)
{
if (string.IsNullOrEmpty(fontName))
return glyphs.FirstOrDefault(g => g.glyph.Character == character).glyph;
{
return glyphs.FirstOrDefault(g => g.Glyph.Character == character).Glyph;
}
return glyphs.FirstOrDefault(g => g.font.FontName == fontName && g.glyph.Character == character).glyph;
return glyphs.FirstOrDefault(g => g.Font.FontName == fontName && g.Glyph.Character == character).Glyph;
}
public Task<ITexturedCharacterGlyph> GetAsync(string fontName, char character) => throw new System.NotImplementedException();
}
private readonly struct GlyphEntry
{
public readonly FontUsage Font;
public readonly ITexturedCharacterGlyph Glyph;
public GlyphEntry(FontUsage font, ITexturedCharacterGlyph glyph)
{
Font = font;
Glyph = glyph;
}
}
private readonly struct TestGlyph : ITexturedCharacterGlyph
{
public Texture Texture => new Texture(1, 1);

View File

@@ -90,31 +90,35 @@ namespace osu.Framework.Allocation
foreach (var type in typeof(TModel).EnumerateBaseTypes())
{
foreach (var field in type.GetFields(activator_flags))
perform(targetShadowModel, field, lastModel, t => t.shadowProp.UnbindFrom(t.modelProp));
{
perform(targetShadowModel, field, lastModel, (shadowProp, modelProp) => shadowProp.UnbindFrom(modelProp));
}
}
foreach (var type in typeof(TModel).EnumerateBaseTypes())
{
foreach (var field in type.GetFields(activator_flags))
perform(targetShadowModel, field, newModel, t => t.shadowProp.BindTo(t.modelProp));
{
perform(targetShadowModel, field, newModel, (shadowProp, modelProp) => shadowProp.BindTo(modelProp));
}
}
}
/// <summary>
/// Perform an arbitrary action across a shadow model and model.
/// </summary>
private void perform(TModel targetShadowModel, MemberInfo member, TModel target, Action<(IBindable shadowProp, IBindable modelProp)> action)
private void perform(TModel targetShadowModel, MemberInfo member, TModel target, Action<IBindable, IBindable> action)
{
if (target == null) return;
switch (member)
{
case PropertyInfo pi:
action(((IBindable)pi.GetValue(targetShadowModel), (IBindable)pi.GetValue(target)));
action((IBindable)pi.GetValue(targetShadowModel), (IBindable)pi.GetValue(target));
break;
case FieldInfo fi:
action(((IBindable)fi.GetValue(targetShadowModel), (IBindable)fi.GetValue(target)));
action((IBindable)fi.GetValue(targetShadowModel), (IBindable)fi.GetValue(target));
break;
}
}

View File

@@ -303,7 +303,7 @@ namespace osu.Framework.Graphics.Containers
int[] distributedIndices = Enumerable.Range(0, cellSizes.Length).Where(i => i >= dimensions.Length || dimensions[i].Mode == GridSizeMode.Distributed).ToArray();
// The dimensions corresponding to all distributed cells
IEnumerable<(int i, Dimension dim)> distributedDimensions = distributedIndices.Select(i => (i, i >= dimensions.Length ? new Dimension() : dimensions[i]));
IEnumerable<DimensionEntry> distributedDimensions = distributedIndices.Select(i => new DimensionEntry(i, i >= dimensions.Length ? new Dimension() : dimensions[i]));
// Total number of distributed cells
int distributionCount = distributedIndices.Length;
@@ -315,20 +315,32 @@ namespace osu.Framework.Graphics.Containers
float distributionSize = Math.Max(0, spanLength - requiredSize) / distributionCount;
// Write the sizes of distributed cells. Ordering is important to maximize excess at every step
foreach (var (i, dim) in distributedDimensions.OrderBy(d => d.dim.Range))
foreach (var entry in distributedDimensions.OrderBy(d => d.Dimension.Range))
{
// Cells start off at their minimum size, and the total size should not exceed their maximum size
cellSizes[i] = Math.Min(dim.MaxSize, dim.MinSize + distributionSize);
cellSizes[entry.Index] = Math.Min(entry.Dimension.MaxSize, entry.Dimension.MinSize + distributionSize);
// If there's no excess, any further distributions are guaranteed to also have no excess, so this becomes a null-op
// If there is an excess, the excess should be re-distributed among all other n-1 distributed cells
if (--distributionCount > 0)
distributionSize += Math.Max(0, distributionSize - dim.Range) / distributionCount;
distributionSize += Math.Max(0, distributionSize - entry.Dimension.Range) / distributionCount;
}
return cellSizes;
}
private readonly struct DimensionEntry
{
public readonly int Index;
public readonly Dimension Dimension;
public DimensionEntry(int index, Dimension dimension)
{
Index = index;
Dimension = dimension;
}
}
/// <summary>
/// Represents one cell of the <see cref="GridContainer"/>.
/// </summary>

View File

@@ -236,19 +236,36 @@ namespace osu.Framework.Graphics.Transforms
AddDelay(delay, recursive);
double newTransformDelay = TransformDelay;
return new ValueInvokeOnDisposal<(Transformable transformable, double delay, bool recursive, double newTransformDelay)>((this, delay, recursive, newTransformDelay), sender =>
return new ValueInvokeOnDisposal<DelayedSequenceSender>(new DelayedSequenceSender(this, delay, recursive, newTransformDelay), sender =>
{
if (!Precision.AlmostEquals(sender.newTransformDelay, sender.transformable.TransformDelay))
if (!Precision.AlmostEquals(sender.NewTransformDelay, sender.Transformable.TransformDelay))
{
throw new InvalidOperationException(
$"{nameof(sender.transformable.TransformStartTime)} at the end of delayed sequence is not the same as at the beginning, but should be. " +
$"(begin={sender.newTransformDelay} end={sender.transformable.TransformDelay})");
$"{nameof(sender.Transformable.TransformStartTime)} at the end of delayed sequence is not the same as at the beginning, but should be. " +
$"(begin={sender.NewTransformDelay} end={sender.Transformable.TransformDelay})");
}
AddDelay(-sender.delay, sender.recursive);
AddDelay(-sender.Delay, sender.Recursive);
});
}
/// An ad-hoc struct used as a closure environment in <see cref="BeginDelayedSequence" />.
private readonly struct DelayedSequenceSender
{
public readonly Transformable Transformable;
public readonly double Delay;
public readonly bool Recursive;
public readonly double NewTransformDelay;
public DelayedSequenceSender(Transformable transformable, double delay, bool recursive, double newTransformDelay)
{
Transformable = transformable;
Delay = delay;
Recursive = recursive;
NewTransformDelay = newTransformDelay;
}
}
/// <summary>
/// Start a sequence of <see cref="Transform"/>s from an absolute time value (adjusts <see cref="TransformStartTime"/>).
/// </summary>
@@ -261,19 +278,34 @@ namespace osu.Framework.Graphics.Transforms
double oldTransformDelay = TransformDelay;
double newTransformDelay = TransformDelay = newTransformStartTime - (Clock?.CurrentTime ?? 0);
return new ValueInvokeOnDisposal<(Transformable transformable, double oldTransformDelay, double newTransformDelay)>((this, oldTransformDelay, newTransformDelay), sender =>
return new ValueInvokeOnDisposal<AbsoluteSequenceSender>(new AbsoluteSequenceSender(this, oldTransformDelay, newTransformDelay), sender =>
{
if (!Precision.AlmostEquals(sender.newTransformDelay, sender.transformable.TransformDelay))
if (!Precision.AlmostEquals(sender.NewTransformDelay, sender.Transformable.TransformDelay))
{
throw new InvalidOperationException(
$"{nameof(sender.transformable.TransformStartTime)} at the end of absolute sequence is not the same as at the beginning, but should be. " +
$"(begin={sender.newTransformDelay} end={sender.transformable.TransformDelay})");
$"{nameof(sender.Transformable.TransformStartTime)} at the end of absolute sequence is not the same as at the beginning, but should be. " +
$"(begin={sender.NewTransformDelay} end={sender.Transformable.TransformDelay})");
}
sender.transformable.TransformDelay = sender.oldTransformDelay;
sender.Transformable.TransformDelay = sender.OldTransformDelay;
});
}
/// An ad-hoc struct used as a closure environment in <see cref="BeginAbsoluteSequence" />.
private readonly struct AbsoluteSequenceSender
{
public readonly Transformable Transformable;
public readonly double OldTransformDelay;
public readonly double NewTransformDelay;
public AbsoluteSequenceSender(Transformable transformable, double oldTransformDelay, double newTransformDelay)
{
Transformable = transformable;
OldTransformDelay = oldTransformDelay;
NewTransformDelay = newTransformDelay;
}
}
/// <summary>
/// Adds to this object a <see cref="Transform"/> which was previously populated using this object via
/// <see cref="TransformableExtensions.PopulateTransform{TValue, TEasing, TThis}"/>.