Skip to content

Commit

Permalink
Add: LeaderBoard data reading (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotrekol committed Mar 30, 2021
1 parent 6a8d767 commit eb462fb
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 39 deletions.
16 changes: 15 additions & 1 deletion StreamCompanion.Common/Helpers/Retry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace StreamCompanion.Common.Helpers
public static class Retry
{
//We don't need Polly here..yet
public static T RetryMe<T>(Func<T> func, Func<T, bool> isValid, int maxRetries) where T : class
public static T RetryMe<T>(Func<T> func, Func<T, bool> isValid, int maxRetries)
{
for (int i = 0; i < maxRetries; i++)
{
Expand All @@ -17,5 +17,19 @@ public static T RetryMe<T>(Func<T> func, Func<T, bool> isValid, int maxRetries)

return default;
}

public static TV RetryMe<TV, T>(Func<T> func, Func<T, (bool, TV)> isValid, int maxRetries)
{
for (int i = 0; i < maxRetries; i++)
{
var result = func();

var validResult = isValid(result);
if (validResult.Item1)
return validResult.Item2;
}

return default;
}
}
}
5 changes: 4 additions & 1 deletion plugins/OsuMemoryEventSource/FirstRunMemoryCalibration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ public void GotMemory(int mapId, OsuStatus status, string mapString)
SetCalibrationText("Initial search delay... waiting 3 seconds");
await Task.Delay(3000);
return ((OsuMemoryDataProvider.OsuMemoryModels.Abstract.Mods)memoryReader.ReadProperty(memoryReader.OsuMemoryAddresses.Player, nameof(Player.Mods))).Value;
if (memoryReader.TryReadProperty(memoryReader.OsuMemoryAddresses.Player, nameof(Player.Mods), out var rawMods))
return ((OsuMemoryDataProvider.OsuMemoryModels.Abstract.Mods)rawMods)?.Value ?? -3;
return -2;
}, 20000).Result;

Passed = mods == ExpectedMods;
Expand Down
1 change: 1 addition & 0 deletions plugins/OsuMemoryEventSource/LivePerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace OsuMemoryEventSource
public class LivePerformanceCalculator
{
public RulesetPlayData Play { get; set; } = new Player();
public LeaderBoard LeaderBoard { get; set; } = new LeaderBoard();
public int PlayTime { get; set; }
private PlayMode _currentPlayMode;

Expand Down
91 changes: 75 additions & 16 deletions plugins/OsuMemoryEventSource/MemoryDataProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using StreamCompanionTypes.Interfaces.Services;
using static StreamCompanion.Common.Helpers.OsuScore;
using CollectionManager.DataTypes;
using Newtonsoft.Json;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;

namespace OsuMemoryEventSource
{
Expand All @@ -31,6 +33,7 @@ public class MemoryDataProcessor : IDisposable
private Tokens.TokenSetter _liveTokenSetter => OsuMemoryEventSourceBase.LiveTokenSetter;
private Tokens.TokenSetter _tokenSetter => OsuMemoryEventSourceBase.TokenSetter;
public static ConfigEntry StrainsAmount = new ConfigEntry("StrainsAmount", (int?)100);
public static ConfigEntry MultiplayerLeaderBoardUpdateRate = new ConfigEntry("MultiplayerLeaderBoardUpdateRate", 250);
private Mods _mods;
private PlayMode _playMode = PlayMode.Osu;

Expand Down Expand Up @@ -135,7 +138,8 @@ public async Task SetNewMap(IMapSearchResult map, CancellationToken cancellation
}
}


private bool ReadLeaderboard = false;
private DateTime _nextLeaderBoardUpdate = DateTime.MaxValue;
public void Tick(OsuStatus status, OsuMemoryStatus rawStatus, StructuredOsuMemoryReader reader)
{
_notUpdatingTokens.WaitOne();
Expand All @@ -155,33 +159,58 @@ public void Tick(OsuStatus status, OsuMemoryStatus rawStatus, StructuredOsuMemor
{
case OsuStatus.Playing:
case OsuStatus.Watching:
if (_lastStatus != OsuStatus.Playing && _lastStatus != OsuStatus.Watching)
{
if (_lastStatus != OsuStatus.Playing && _lastStatus != OsuStatus.Watching)
{
_newPlayStarted.Set();
Thread.Sleep(500);//Initial play delay
}
_newPlayStarted.Set();
Thread.Sleep(500);//Initial play delay
}

reader.Read(OsuMemoryData.Player);
if (!ReferenceEquals(_rawData.Play, OsuMemoryData.Player))
_rawData.Play = OsuMemoryData.Player;
reader.TryRead(OsuMemoryData.Player);
if (!ReferenceEquals(_rawData.Play, OsuMemoryData.Player))
_rawData.Play = OsuMemoryData.Player;

//TODO: support for live multiplayer leaderboard
if (!ReadLeaderboard)
{
//Read whole leaderboard once
if (reader.TryRead(OsuMemoryData.LeaderBoard))
{
ReadLeaderboard = true;
if (!ReferenceEquals(_rawData.LeaderBoard, OsuMemoryData.LeaderBoard))
_rawData.LeaderBoard = OsuMemoryData.LeaderBoard;
}
}
else
{
//Throttle whole leaderboard reads - Temporary solution until multiplayer detection is implemented, this should be only done in multiplayer
if (_nextLeaderBoardUpdate > DateTime.UtcNow)
{
reader.TryRead(OsuMemoryData.LeaderBoard);
_nextLeaderBoardUpdate = DateTime.UtcNow.AddMilliseconds(_settings.Get<int>(MultiplayerLeaderBoardUpdateRate));
}
else
{
//...then update main player data only
reader.TryRead(OsuMemoryData.LeaderBoard.MainPlayer);
}
}

break;
case OsuStatus.ResultsScreen:
reader.Read(OsuMemoryData.ResultsScreen);
ReadLeaderboard = false;
reader.TryRead(OsuMemoryData.ResultsScreen);
if (!ReferenceEquals(_rawData.Play, OsuMemoryData.ResultsScreen))
_rawData.Play = OsuMemoryData.ResultsScreen;

playTime = Convert.ToInt32(_rawData.PpCalculator?.BeatmapLength ?? 0);
break;
default:
{
reader.Read(OsuMemoryData.Skin);
UpdateLiveTokens(status);
_lastStatus = status;
break;
}
ReadLeaderboard = false;
_rawData.LeaderBoard = new LeaderBoard();
reader.TryRead(OsuMemoryData.Skin);
_lastStatus = status;
break;

}

_rawData.PlayTime = playTime;
Expand Down Expand Up @@ -356,6 +385,36 @@ private void InitLiveTokens()
return InterpolatedValues[InterpolatedValueName.liveStarRating].Current;
});
CreateLiveToken("isBreakTime", 0, TokenType.Live, "{0}", 0, OsuStatus.All, () => _rawData.PpCalculator?.IsBreakTime(_rawData.PlayTime) ?? false ? 1 : 0);

var leaderBoardSerializerSettings = new JsonSerializerSettings
{
Error = SerializationError
};
// object lastLeaderBoardObject = null;
string lastLeaderBoardData = "{}";
DateTime lastLeaderBoardUpdate = DateTime.MinValue.AddMilliseconds(1);
CreateLiveToken("leaderBoardPlayers", "[]", TokenType.Live, "", "[]", playingOrWatching, () =>
{
////TODO: assumes singleplayer(other player data never updates)
//if (ReferenceEquals(_rawData.LeaderBoard, lastLeaderBoardObject))
// return lastLeaderBoardData;
//lastLeaderBoardObject = _rawData.LeaderBoard;
var nextUpdateAt = _nextLeaderBoardUpdate;
if (nextUpdateAt == lastLeaderBoardUpdate)
return lastLeaderBoardData;
lastLeaderBoardUpdate = nextUpdateAt;
return lastLeaderBoardData = JsonConvert.SerializeObject(_rawData.LeaderBoard.Players, leaderBoardSerializerSettings);
});

CreateLiveToken("leaderBoardMainPlayer", "{}", TokenType.Live, "", "{}", playingOrWatching, () => JsonConvert.SerializeObject(_rawData.LeaderBoard.MainPlayer, leaderBoardSerializerSettings));
}

private void SerializationError(object sender, ErrorEventArgs e)
{
_logger.Log("Failed to serialize leaderBoard token data.", LogLevel.Debug);
_logger.Log(e, LogLevel.Trace);
}

private void UpdateLiveTokens(OsuStatus status)
Expand Down
38 changes: 23 additions & 15 deletions plugins/OsuMemoryEventSource/MemoryListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ public void Tick(List<StructuredOsuMemoryReader> clientReaders, bool sendEvents)
{
var reader = clientReaders[0];
var osuData = reader.OsuMemoryAddresses;
reader.Read(osuData.GeneralData);
if (!reader.TryRead(osuData.GeneralData))
return;

_currentStatus = osuData.GeneralData.OsuStatus;
if (_lastStatusLog != _currentStatus)
{
Expand All @@ -71,14 +73,17 @@ public void Tick(List<StructuredOsuMemoryReader> clientReaders, bool sendEvents)
if (_currentStatus == OsuMemoryStatus.NotRunning)
return;

reader.Read(osuData.Beatmap);
_currentMapId = osuData.Beatmap.Id;
var isReplay = (bool?)reader.ReadProperty(osuData.Player, nameof(Player.IsReplay));
if (isReplay == null)
if (!reader.TryRead(osuData.Beatmap))
return;

if (!reader.TryReadProperty(osuData.Player, nameof(Player.IsReplay), out var rawIsReplay))
return;

_currentMapId = osuData.Beatmap.Id;
var isReplay = (bool)rawIsReplay;

OsuStatus status = _currentStatus.Convert();
status = status == OsuStatus.Playing && isReplay.Value
status = status == OsuStatus.Playing && isReplay
? OsuStatus.Watching
: status;

Expand All @@ -88,19 +93,17 @@ public void Tick(List<StructuredOsuMemoryReader> clientReaders, bool sendEvents)
var mods = osuData.GeneralData.Mods;
if (status == OsuStatus.Playing || status == OsuStatus.Watching)
{
var rawMods = ((Mods)reader.ReadProperty(osuData.Player, nameof(Player.Mods)))?.Value;
if (rawMods == null)
if (!reader.TryReadProperty(osuData.Player, nameof(Player.Mods), out var rawMods) || rawMods == null)
return;

mods = rawMods.Value;
mods = ((Mods)rawMods).Value;
}
else if (status == OsuStatus.ResultsScreen)
{
var rawMods = ((Mods)reader.ReadProperty(osuData.ResultsScreen, nameof(ResultsScreen.Mods)))?.Value;
if (rawMods == null)
if (!reader.TryReadProperty(osuData.ResultsScreen, nameof(ResultsScreen.Mods), out var rawMods) || rawMods == null)
return;

mods = rawMods.Value;
mods = ((Mods)rawMods).Value;
}

if (Helpers.IsInvalidCombination((CollectionManager.DataTypes.Mods)mods))
Expand All @@ -116,7 +119,7 @@ public void Tick(List<StructuredOsuMemoryReader> clientReaders, bool sendEvents)
var modsDiffer = mods != _lastSentMods;
OsuEventType? osuEventType = null;
//"good enough" replay retry detection.
if (isReplay.Value && _currentStatus == OsuMemoryStatus.Playing && _lastTime > currentTime && DateTime.UtcNow > _nextReplayRetryAllowedAt)
if (isReplay && _currentStatus == OsuMemoryStatus.Playing && _lastTime > currentTime && DateTime.UtcNow > _nextReplayRetryAllowedAt)
{
osuEventType = OsuEventType.PlayChange;
_nextReplayRetryAllowedAt = DateTime.UtcNow.AddMilliseconds(500);
Expand Down Expand Up @@ -147,8 +150,13 @@ public void Tick(List<StructuredOsuMemoryReader> clientReaders, bool sendEvents)
_lastGameMode = gameMode;
_lastMapHash = mapHash;
_lastSentMods = mods;
var rawString = Retry.RetryMe(() => (string)reader.ReadProperty(osuData.Beatmap, nameof(CurrentBeatmap.Md5)),
s => (System.Text.Encoding.UTF8.GetByteCount(s ?? string.Empty) == s?.Length), 5) ?? string.Empty;
var rawString = Retry.RetryMe(
() =>
{
var validRead = reader.TryReadProperty(osuData.Beatmap, nameof(CurrentBeatmap.MapString), out var result);
return (validRead, (string)result);
},
s => (s.validRead, s.Item2), 5) ?? string.Empty;

NewOsuEvent?.Invoke(this, new MemoryMapSearchArgs(osuEventType.Value)
{
Expand Down
12 changes: 7 additions & 5 deletions plugins/OsuMemoryEventSource/OsuMemoryEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,16 @@ private int ReadMods(IMapSearchArgs searchArgs, int retryCount = 0)
if (searchArgs.Status == OsuStatus.Playing || searchArgs.Status == OsuStatus.Watching)
{
Thread.Sleep(250);
var maybeMods = Retry.RetryMe(() => ((OsuMemoryDataProvider.OsuMemoryModels.Abstract.Mods)MemoryReader.ReadProperty(MemoryReader.OsuMemoryAddresses.Player, nameof(Player.Mods))), m => m != null, 5);
mods = maybeMods?.Value ?? -1;

mods = MemoryReader.TryReadProperty(MemoryReader.OsuMemoryAddresses.Player, nameof(Player.Mods), out var rawMods)
? ((OsuMemoryDataProvider.OsuMemoryModels.Abstract.Mods)rawMods)?.Value ?? -1
: -1;
}
else
{
var maybeMods = Retry.RetryMe(() =>
MemoryReader.ReadProperty(MemoryReader.OsuMemoryAddresses.GeneralData, nameof(GeneralData.Mods)), (m => m != null), 5);
mods = int.TryParse(maybeMods?.ToString(), out var readMods) ? readMods : -1;
mods = MemoryReader.TryReadProperty(MemoryReader.OsuMemoryAddresses.GeneralData, nameof(GeneralData.Mods), out var rawMods)
? (int)rawMods
: -1;
}

if ((mods < 0 || Helpers.IsInvalidCombination((Mods)mods)))
Expand Down
2 changes: 1 addition & 1 deletion plugins/OsuMemoryEventSource/OsuMemoryEventSource.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="OsuMemoryDataProvider" Version="0.2.2" />
<PackageReference Include="OsuMemoryDataProvider" Version="0.3.2" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
Expand Down

0 comments on commit eb462fb

Please sign in to comment.