New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of an osu!lazer-based diffcalc server #2
Merged
Merged
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
82550b8
Initial implementation of difficulty calculator server
smoogipoo bf394bd
Fix license header
smoogipoo 2719ed8
Cleanups
smoogipoo f0fa98d
Add app config
smoogipoo 9125fb1
Add appsettings to gitignore
smoogipoo 69dfe69
Fix license header
smoogipoo d48c51a
Migrate to Dapper
smoogipoo 7905d5b
Alleviate some rider issues
smoogipoo 837ac65
Use a singular connection for some queries + add some output
smoogipoo 4abbf7b
Cleanup async stuff
smoogipoo 820fb8d
Add method to choose multi or single threaded mode
smoogipoo 26817b0
Allow more fine-grained control over concurrency level
smoogipoo 20566c2
Move connection outside of foreach
smoogipoo 4ab88e1
Adjust with difficulty mod changes
smoogipoo 33a0b3c
Compute difficulties for all rulesets
smoogipoo 651872a
Add option for converts
smoogipoo 40db028
Remove framework project
smoogipoo 1ba6001
Add ability to process only specific rulesets
smoogipoo a591f97
Bring up to date with the latest osu!
smoogipoo dc6bf0c
Update submodule
smoogipoo File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -257,3 +257,5 @@ paket-files/ | |
__pycache__/ | ||
*.pyc | ||
Staging/ | ||
|
||
appsettings.*.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. | ||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-server/master/LICENCE | ||
|
||
using System; | ||
using System.IO; | ||
using Microsoft.Extensions.Configuration; | ||
|
||
namespace osu.Server.DifficultyCalculator | ||
{ | ||
public class AppSettings | ||
{ | ||
public static string ConnectionString { get; } | ||
public static string BeatmapsPath { get; } | ||
|
||
static AppSettings() | ||
{ | ||
var env = Environment.GetEnvironmentVariable("APP_ENV") ?? "development"; | ||
var config = new ConfigurationBuilder() | ||
.SetBasePath(Directory.GetCurrentDirectory()) | ||
.AddJsonFile("appsettings.json", true, false) | ||
.AddJsonFile($"appsettings.{env}.json", true, false) | ||
.AddEnvironmentVariables() | ||
.Build(); | ||
|
||
ConnectionString = config.GetConnectionString("osu"); | ||
BeatmapsPath = config["beatmaps_path"]; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. | ||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-server/master/LICENCE | ||
|
||
using MySql.Data.MySqlClient; | ||
|
||
namespace osu.Server.DifficultyCalculator | ||
{ | ||
public class Database | ||
{ | ||
private readonly string connectionString; | ||
|
||
public Database(string connectionString) | ||
{ | ||
this.connectionString = connectionString; | ||
} | ||
|
||
public MySqlConnection GetConnection() | ||
{ | ||
var connection = new MySqlConnection(connectionString); | ||
connection.Open(); | ||
return connection; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. | ||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-server/master/LICENCE | ||
|
||
using System.IO; | ||
using osu.Framework.Audio.Track; | ||
using osu.Framework.Graphics.Textures; | ||
using osu.Game.Beatmaps; | ||
using osu.Game.Beatmaps.Formats; | ||
using osu.Game.Rulesets.Catch; | ||
using osu.Game.Rulesets.Mania; | ||
using osu.Game.Rulesets.Osu; | ||
using osu.Game.Rulesets.Taiko; | ||
|
||
namespace osu.Server.DifficultyCalculator | ||
{ | ||
/// <summary> | ||
/// A <see cref="WorkingBeatmap"/> which reads from a .osu file. | ||
/// </summary> | ||
public class LocalWorkingBeatmap : WorkingBeatmap | ||
{ | ||
private readonly Beatmap beatmap; | ||
|
||
/// <summary> | ||
/// Constructs a new <see cref="LocalWorkingBeatmap"/> from a .osu file. | ||
/// </summary> | ||
/// <param name="file">The .osu file.</param> | ||
public LocalWorkingBeatmap(string file) | ||
: this(File.OpenRead(file)) | ||
{ | ||
} | ||
|
||
private LocalWorkingBeatmap(Stream stream) | ||
: this(new StreamReader(stream)) | ||
{ | ||
stream.Dispose(); | ||
} | ||
|
||
private LocalWorkingBeatmap(StreamReader streamReader) | ||
: this(Decoder.GetDecoder<Beatmap>(streamReader).Decode(streamReader)) | ||
{ | ||
} | ||
|
||
private LocalWorkingBeatmap(Beatmap beatmap) | ||
: base(beatmap.BeatmapInfo) | ||
{ | ||
this.beatmap = beatmap; | ||
|
||
switch (beatmap.BeatmapInfo.RulesetID) | ||
{ | ||
case 0: | ||
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; | ||
break; | ||
case 1: | ||
beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo; | ||
break; | ||
case 2: | ||
beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo; | ||
break; | ||
case 3: | ||
beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo; | ||
break; | ||
} | ||
} | ||
|
||
protected override IBeatmap GetBeatmap() => beatmap; | ||
protected override Texture GetBackground() => null; | ||
protected override Track GetTrack() => null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. | ||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-server/master/LICENCE | ||
|
||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Dapper; | ||
using McMaster.Extensions.CommandLineUtils; | ||
using osu.Game.Beatmaps.Legacy; | ||
using osu.Game.Rulesets.Mania.Mods; | ||
using osu.Game.Rulesets.Mods; | ||
|
||
namespace osu.Server.DifficultyCalculator | ||
{ | ||
[Command] | ||
public class Program | ||
{ | ||
public static void Main(string[] args) | ||
=> CommandLineApplication.Execute<Program>(args); | ||
|
||
[Option] | ||
public int Concurrency { get; set; } = 1; | ||
|
||
private readonly Dictionary<string, int> attributeIds = new Dictionary<string, int>(); | ||
|
||
private Database database; | ||
|
||
private readonly ConcurrentQueue<int> beatmaps = new ConcurrentQueue<int>(); | ||
|
||
private int totalBeatmaps; | ||
private int processedBeatmaps; | ||
|
||
public void OnExecute(CommandLineApplication app, IConsole console) | ||
{ | ||
if (Concurrency < 1) | ||
{ | ||
console.Error.WriteLine("Concurrency level must be above 1."); | ||
return; | ||
} | ||
|
||
database = new Database(AppSettings.ConnectionString); | ||
|
||
var tasks = new List<Task>(); | ||
|
||
using (var conn = database.GetConnection()) | ||
{ | ||
foreach ((int Id, string Name) attrib in conn.Query<(int, string)>("SELECT attrib_id, name FROM osu_difficulty_attribs")) | ||
attributeIds[attrib.Name] = attrib.Id; | ||
|
||
totalBeatmaps = conn.ExecuteScalar<int>("SELECT COUNT(*) FROM osu_beatmaps"); | ||
|
||
foreach (int id in conn.Query<int>("SELECT beatmap_id FROM osu_beatmaps ORDER BY beatmap_id DESC")) | ||
beatmaps.Enqueue(id); | ||
} | ||
|
||
for (int i = 0; i < Concurrency; i++) | ||
tasks.Add(processBeatmaps()); | ||
|
||
Task.WaitAll(tasks.ToArray()); | ||
} | ||
|
||
private Task processBeatmaps() => Task.Factory.StartNew(() => | ||
{ | ||
while (beatmaps.TryDequeue(out int toProcess)) | ||
{ | ||
try | ||
{ | ||
processBeatmap(toProcess); | ||
} | ||
catch (Exception e) | ||
{ | ||
Console.WriteLine(e); | ||
} | ||
} | ||
}, TaskCreationOptions.LongRunning); | ||
|
||
private void processBeatmap(int beatmapId) | ||
{ | ||
string path = Path.Combine(AppSettings.BeatmapsPath, beatmapId + ".osu"); | ||
if (!File.Exists(path)) | ||
{ | ||
finish($"Beatmap {beatmapId} skipped (beatmap file not found)."); | ||
return; | ||
} | ||
|
||
var localBeatmap = new LocalWorkingBeatmap(path); | ||
|
||
// Todo: For each ruleset | ||
var playable = localBeatmap.GetPlayableBeatmap(localBeatmap.BeatmapInfo.Ruleset); | ||
var ruleset = localBeatmap.BeatmapInfo.Ruleset.CreateInstance(); | ||
|
||
foreach (var mod in ruleset.GetModsFor(ModType.DifficultyCalculation)) | ||
{ | ||
var legacyMod = toLegacyMod(mod); | ||
|
||
var attributes = new Dictionary<string, object>(); | ||
double starRating = ruleset.CreateDifficultyCalculator(playable, toModArray(mod)).Calculate(attributes); | ||
|
||
using (var conn = database.GetConnection()) | ||
This comment was marked as off-topic.
Sorry, something went wrong. |
||
{ | ||
conn.Execute( | ||
"INSERT INTO osu_beatmap_difficulty (beatmap_id, mode, mods, diff_unified) " | ||
+ "VALUES (@BeatmapId, @Mode, @Mods, @Diff) " | ||
+ "ON DUPLICATE KEY UPDATE diff_unified = @Diff", | ||
new | ||
{ | ||
BeatmapId = beatmapId, | ||
Mode = ruleset.RulesetInfo.ID, | ||
Mods = (int)legacyMod, | ||
Diff = starRating | ||
}); | ||
} | ||
|
||
if (attributes.Count > 0) | ||
{ | ||
var parameters = new List<object>(); | ||
foreach (var kvp in attributes) | ||
{ | ||
if (!attributeIds.ContainsKey(kvp.Key)) | ||
continue; | ||
|
||
parameters.Add(new | ||
{ | ||
BeatmapId = beatmapId, | ||
Mode = ruleset.RulesetInfo.ID, | ||
Mods = (int)legacyMod, | ||
Attribute = attributeIds[kvp.Key], | ||
Value = Convert.ToSingle(kvp.Value) | ||
}); | ||
} | ||
|
||
using (var conn = database.GetConnection()) | ||
{ | ||
conn.Execute( | ||
"INSERT INTO osu_beatmap_difficulty_attribs (beatmap_id, mode, mods, attrib_id, value) " | ||
+ "VALUES (@BeatmapId, @Mode, @Mods, @Attribute, @Value) " | ||
+ "ON DUPLICATE KEY UPDATE value = VALUES(value)", | ||
parameters.ToArray()); | ||
} | ||
} | ||
|
||
if (legacyMod == LegacyMods.None && ruleset.RulesetInfo.Equals(localBeatmap.BeatmapInfo.Ruleset)) | ||
{ | ||
using (var conn = database.GetConnection()) | ||
{ | ||
conn.Execute( | ||
"UPDATE osu_beatmaps SET difficultyrating=@Diff, diff_approach=@AR, diff_overall=@OD, diff_drain=@HP, diff_size=@CS " | ||
+ "WHERE beatmap_id=@BeatmapId", | ||
new | ||
{ | ||
BeatmapId = beatmapId, | ||
Diff = starRating, | ||
AR = localBeatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, | ||
OD = localBeatmap.Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, | ||
HP = localBeatmap.Beatmap.BeatmapInfo.BaseDifficulty.DrainRate, | ||
CS = localBeatmap.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize | ||
}); | ||
} | ||
} | ||
} | ||
|
||
finish($"Difficulty updated for beatmap {beatmapId}."); | ||
} | ||
|
||
private void finish(string message) | ||
{ | ||
Interlocked.Increment(ref processedBeatmaps); | ||
Console.WriteLine($"{processedBeatmaps} / {totalBeatmaps} : {message}"); | ||
} | ||
|
||
private Mod[] toModArray(Mod mod) | ||
{ | ||
switch (mod) | ||
{ | ||
case MultiMod multi: | ||
return multi.Mods?.SelectMany(toModArray).ToArray() ?? Array.Empty<Mod>(); | ||
default: | ||
return new[] { mod }; | ||
} | ||
} | ||
|
||
private LegacyMods toLegacyMod(Mod mod) | ||
{ | ||
var value = LegacyMods.None; | ||
|
||
switch (mod) | ||
{ | ||
case MultiMod multi: | ||
if (multi.Mods == null) | ||
break; | ||
foreach (var m in multi.Mods) | ||
value |= toLegacyMod(m); | ||
break; | ||
case ModNoFail _: | ||
value |= LegacyMods.NoFail; | ||
break; | ||
case ModEasy _: | ||
value |= LegacyMods.Easy; | ||
break; | ||
case ModHidden _: | ||
value |= LegacyMods.Hidden; | ||
break; | ||
case ModHardRock _: | ||
value |= LegacyMods.HardRock; | ||
break; | ||
case ModSuddenDeath _: | ||
value |= LegacyMods.SuddenDeath; | ||
break; | ||
case ModDoubleTime _: | ||
value |= LegacyMods.DoubleTime; | ||
break; | ||
case ModRelax _: | ||
value |= LegacyMods.Relax; | ||
break; | ||
case ModHalfTime _: | ||
value |= LegacyMods.HalfTime; | ||
break; | ||
case ModFlashlight _: | ||
value |= LegacyMods.Flashlight; | ||
break; | ||
case ManiaModKey1 _: | ||
value |= LegacyMods.Key1; | ||
break; | ||
case ManiaModKey2 _: | ||
value |= LegacyMods.Key2; | ||
break; | ||
case ManiaModKey3 _: | ||
value |= LegacyMods.Key3; | ||
break; | ||
case ManiaModKey4 _: | ||
value |= LegacyMods.Key4; | ||
break; | ||
case ManiaModKey5 _: | ||
value |= LegacyMods.Key5; | ||
break; | ||
case ManiaModKey6 _: | ||
value |= LegacyMods.Key6; | ||
break; | ||
case ManiaModKey7 _: | ||
value |= LegacyMods.Key7; | ||
break; | ||
case ManiaModKey8 _: | ||
value |= LegacyMods.Key8; | ||
break; | ||
case ManiaModKey9 _: | ||
value |= LegacyMods.Key9; | ||
break; | ||
case ManiaModFadeIn _: | ||
value |= LegacyMods.FadeIn; | ||
break; | ||
} | ||
|
||
return value; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This comment was marked as off-topic.
Sorry, something went wrong.