-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathPregeneratedSongProvider.cs
118 lines (103 loc) · 4.46 KB
/
PregeneratedSongProvider.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
namespace BillionSongs {
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BillionSongs.Data;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
public class PregeneratedSongProvider: IRandomSongProvider {
readonly ConcurrentQueue<PregeneratedSong> pregenerated = new ConcurrentQueue<PregeneratedSong>();
readonly int desiredPoolSize = 50000;
readonly int reuseLimit = 10;
readonly TrulyRandomSongProvider randomProvider = new TrulyRandomSongProvider();
readonly TimeSpan emptyDelayInterval = TimeSpan.FromSeconds(1);
readonly TimeSpan fullDelayInterval = TimeSpan.FromMinutes(1);
readonly ISongDatabase songDatabase;
readonly CancellationToken stopToken;
readonly ILogger<PregeneratedSongProvider> logger;
readonly DbSet<Song> prebuiltSongs;
public async Task<uint> GetRandomSongID(CancellationToken cancellation) {
while (true) {
if (!this.pregenerated.TryDequeue(out PregeneratedSong song)) {
this.logger.LogWarning("pregenerated song queue is dry");
await Task.Delay(this.emptyDelayInterval, cancellation).ConfigureAwait(false);
} else {
int usesLeft = Interlocked.Decrement(ref song.usesLeft);
if (usesLeft > 0) {
this.pregenerated.Enqueue(song);
return song.id;
}
}
cancellation.ThrowIfCancellationRequested();
this.stopToken.ThrowIfCancellationRequested();
}
}
const int UseOldPercentage = 75;
async void Generator(CancellationToken cancellation) {
var fromDatabase = new List<PregeneratedSong>();
await this.prebuiltSongs.AsNoTracking().Take(this.desiredPoolSize * UseOldPercentage / 100)
.ForEachAsync(song => {
if (song.GeneratorError == null)
fromDatabase.Add(new PregeneratedSong {
id = song.ID,
usesLeft = this.reuseLimit,
});
}, cancellation).ConfigureAwait(false);
Shuffle(fromDatabase);
foreach (PregeneratedSong song in fromDatabase)
this.pregenerated.Enqueue(song);
this.logger.LogInformation($"loaded {this.pregenerated.Count} pregenerated songs");
while (!cancellation.IsCancellationRequested) {
if (this.pregenerated.Count >= this.desiredPoolSize) {
this.logger.LogDebug("pregen queue full");
await Task.Delay(this.fullDelayInterval, cancellation).ConfigureAwait(false);
continue;
}
uint id = this.randomProvider.GetRandomSongID();
try {
Song song = await this.songDatabase.GetSong(id, cancellation).ConfigureAwait(false);
if (song.GeneratorError == null) {
this.pregenerated.Enqueue(new PregeneratedSong {
id = id,
usesLeft = this.reuseLimit,
});
int tenPercent = this.desiredPoolSize / 10;
if (this.pregenerated.Count % tenPercent == 0)
this.logger.LogDebug($"pregen queue: {this.pregenerated.Count * 100 / tenPercent}%");
}
}
catch (LyricsGeneratorException) { }
catch (OperationCanceledException) { }
}
}
static void Shuffle<T>(IList<T> array, Random rng = null) {
rng = rng ?? new Random();
int n = array.Count;
while (n > 1)
{
int k = rng.Next(n--);
T temp = array[n];
array[n] = array[k];
array[k] = temp;
}
}
public PregeneratedSongProvider([NotNull] ISongDatabase songDatabase,
[NotNull] DbSet<Song> prebuiltSongs,
[NotNull] ILogger<PregeneratedSongProvider> logger,
CancellationToken stopToken) {
this.songDatabase = songDatabase ?? throw new ArgumentNullException(nameof(songDatabase));
this.stopToken = stopToken;
this.prebuiltSongs = prebuiltSongs ?? throw new ArgumentNullException(nameof(prebuiltSongs));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.Generator(stopToken);
}
class PregeneratedSong {
internal uint id;
internal int usesLeft;
}
}
}