Coverage Report

Created: 2025-07-18 07:04

/src/vlc/src/playlist/randomizer.c
Line
Count
Source (jump to first uncovered line)
1
/*****************************************************************************
2
 * randomizer.c
3
 *****************************************************************************
4
 * Copyright (C) 2018 VLC authors and VideoLAN
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 2.1 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program; if not, write to the Free Software Foundation,
18
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19
 *****************************************************************************/
20
21
#ifdef HAVE_CONFIG_H
22
# include "config.h"
23
#endif
24
25
#ifdef TEST_RANDOMIZER
26
# undef NDEBUG
27
#endif
28
29
#include <assert.h>
30
31
#include <vlc_common.h>
32
#include <vlc_rand.h>
33
#include "randomizer.h"
34
35
/**
36
 * \addtogroup playlist_randomizer Playlist randomizer helper
37
 * \ingroup playlist
38
 *
39
 * Playlist helper to manage random playback.
40
 *
41
 * The purpose is to guarantee the following rules:
42
 *  - an item must never be selected twice
43
 *  - "prev" navigates back in the history of previously selected items
44
 *  - insertions and removals can occur at any time; all these rules must still
45
 *    apply
46
 *  - the user can request to select a specific item (typically by
47
 *    double-clicking on an item in the playlist)
48
 *
49
 * If loop (repeat) is enabled:
50
 *  - the random order must be "reshuffled" on every cycle
51
 *  - an item must never be selected twice in each cycle
52
 *  - the same item must never be selected twice in a row; in particular, it
53
 *    must not be the first item of a new cycle if it was the last item of the
54
 *    previous one (except if the playlist contains only one item)
55
 *  - crossing a new "cycle" is invisible to the user (e.g. it is still
56
 *    possible to navigate to previous selected items)
57
 *
58
 * To achieve these goals, a "randomizer" stores a single vector containing all
59
 * the items of the playlist, along with 3 indexes.
60
 *
61
 * The whole vector is not shuffled at once: instead, steps of the Fisher-Yates
62
 * algorithm are executed one-by-one on demand. This has several advantages:
63
 *  - on insertions and removals, there is no need to reshuffle or shift the
64
 *    whole array;
65
 *  - if loop is enabled, the history of the last cycle can be kept in place.
66
 *
67
 * 'head' indicates the end of the items already determined for the current
68
 * cycle (if loop is disabled, there is only one cycle).
69
 * (0 <= head <= size)
70
 *
71
 * 'next' points to the item after the current one (we use 'next' instead of
72
 * 'current' so that all indexes are unsigned, while 'current' could be -1).
73
 * The current item is the one returned by the previous call to _Prev() or
74
 * _Next(). Each call to _Next() makes 'next' (and possibly 'head') move
75
 * forward, each call to _Prev() makes it move back (modulo size).
76
 * 'next' is always in the determined range (0 <= next <= head) or in the
77
 * "history" range (history < next < size).
78
 *
79
 * 'history' is only used in loop mode, and references the first item of the
80
 * ordered history from the last cycle.
81
 *
82
 * 0              next  head          history       size
83
 * |---------------|-----|.............|-------------|
84
 *  <------------------->               <----------->
85
 *   determinated range                 history range
86
 *
87
 * Here is a sample scenario to understand how it works.
88
 *
89
 * The playlist initially adds 5 items (A, B, C, D and E).
90
 *
91
 *                                          history
92
 *                 next                     |
93
 *                 head                     |
94
 *                 |                        |
95
 *                 A    B    C    D    E
96
 *
97
 * The playlist calls _Next() to retrieve the next random item. The randomizer
98
 * picks one item (say, D), and swaps it with the current head (A). _Next()
99
 * returns D.
100
 *
101
 *                                          history
102
 *                      next                |
103
 *                      head                |
104
 *                      |                   |
105
 *                 D    B    C    A    E
106
 *               <--->
107
 *            determined range
108
 *
109
 * The playlist calls _Next() one more time. The randomizer selects one item
110
 * outside the determined range (say, E). _Next() returns E.
111
 *
112
 *                                          history
113
 *                           next           |
114
 *                           head           |
115
 *                           |              |
116
 *                 D    E    C    A    B
117
 *               <-------->
118
 *            determined range
119
 *
120
 * The playlist calls _Next() one more time. The randomizer selects C (already
121
 * in place). _Next() returns C.
122
 *
123
 *                                          history
124
 *                                next      |
125
 *                                head      |
126
 *                                |         |
127
 *                 D    E    C    A    B
128
 *               <------------->
129
 *             determined range
130
 *
131
 * The playlist then calls _Prev(). Since the "current" item is C, the previous
132
 * one is E, so _Prev() returns E, and 'next' moves back.
133
 *
134
 *                                          history
135
 *                           next           |
136
 *                           |    head      |
137
 *                           |    |         |
138
 *                 D    E    C    A    B
139
 *               <------------->
140
 *             determined range
141
 *
142
 * The playlist calls _Next(), which returns C, as expected.
143
 *
144
 *                                          history
145
 *                                next      |
146
 *                                head      |
147
 *                                |         |
148
 *                 D    E    C    A    B
149
 *               <------------->
150
 *             determined range
151
 *
152
 * The playlist calls _Next(), the randomizer selects B, and returns it.
153
 *
154
 *                                          history
155
 *                                     next |
156
 *                                     head |
157
 *                                     |    |
158
 *                 D    E    C    B    A
159
 *               <------------------>
160
 *                determined range
161
 *
162
 * The playlist calls _Next(), the randomizer selects the last item (it has no
163
 * choice). 'next' and 'head' now point one item past the end (their value is
164
 * the vector size).
165
 *
166
 *                                          history
167
 *                                          next
168
 *                                          head
169
 *                                          |
170
 *                 D    E    C    B    A
171
 *               <----------------------->
172
 *                  determined range
173
 *
174
 * At this point, if loop is disabled, it is not possible to call _Next()
175
 * anymore (_HasNext() returns false). So let's enable it by calling
176
 * _SetLoop(), then let's call _Next() again.
177
 *
178
 * This will start a new loop cycle. Firstly, 'next' and 'head' are reset, and
179
 * the whole vector belongs to the last cycle history.
180
 *
181
 *                  history
182
 *                  next
183
 *                  head
184
 *                  |
185
 *                  D    E    C    B    A
186
 *               <------------------------>
187
 *                     history range
188
 *
189
 * Secondly, to avoid selecting A twice in a row (as the last item of the
190
 * previous cycle and the first item of the new one), the randomizer will
191
 * immediately determine another item in the vector (say C) to be the first of
192
 * the new cycle. The items that belong to the history are kept in order.
193
 * 'head' and 'history' move forward.
194
 *
195
 *                      history
196
 *                 next |
197
 *                 |    head
198
 *                 |    |
199
 *                 C    D    E    B    A
200
 *               <---><------------------>
201
 *         determined     history range
202
 *              range
203
 *
204
 * Finally, it will actually select and return the first item (C).
205
 *
206
 *                      history
207
 *                      next
208
 *                      head
209
 *                      |
210
 *                 C    D    E    B    A
211
 *               <---><------------------>
212
 *         determined     history range
213
 *              range
214
 *
215
 * Then, the user adds an item to the playlist (F). This item is added in front
216
 * of history.
217
 *
218
 *                           history
219
 *                      next |
220
 *                      head |
221
 *                      |    |
222
 *                 C    F    D    E    B    A
223
 *               <--->     <------------------>
224
 *         determined          history range
225
 *              range
226
 *
227
 * The playlist calls _Next(), the randomizer randomly selects E. E
228
 * "disappears" from the history of the last cycle. This is a general property:
229
 * each item may not appear more than once in the "history" (both from the last
230
 * and the new cycle). The history order is preserved.
231
 *
232
 *                                history
233
 *                           next |
234
 *                           head |
235
 *                           |    |
236
 *                 C    E    F    D    B    A
237
 *               <-------->     <-------------->
238
 *               determined      history range
239
 *                 range
240
 *
241
 * The playlist then calls _Prev() 3 times, that yields C, then A, then B.
242
 * 'next' is decremented (modulo size) on each call.
243
 *
244
 *                                history
245
 *                                |    next
246
 *                           head |    |
247
 *                           |    |    |
248
 *                 C    E    F    D    B    A
249
 *               <-------->     <-------------->
250
 *               determined      history range
251
 *                 range
252
 */
253
254
/* On auto-reshuffle, avoid selecting the same item before at least
255
 * NOT_SAME_BEFORE other items have been selected (between the end of the
256
 * previous shuffle and the start of the new shuffle). */
257
0
#define NOT_SAME_BEFORE 1
258
259
void
260
randomizer_Init(struct randomizer *r)
261
0
{
262
0
    vlc_vector_init(&r->items);
263
264
    /* initialize separately instead of using vlc_lrand48() to avoid locking
265
     * the mutex for every random number generation */
266
0
    vlc_rand_bytes(r->xsubi, sizeof(r->xsubi));
267
268
0
    r->loop = false;
269
0
    r->head = 0;
270
0
    r->next = 0;
271
0
    r->history = 0;
272
0
}
273
274
void
275
randomizer_Destroy(struct randomizer *r)
276
0
{
277
0
    vlc_vector_destroy(&r->items);
278
0
}
279
280
void
281
randomizer_SetLoop(struct randomizer *r, bool loop)
282
0
{
283
0
    r->loop = loop;
284
0
}
285
286
static inline ssize_t
287
randomizer_IndexOf(struct randomizer *r, const vlc_playlist_item_t *item)
288
0
{
289
0
    ssize_t index;
290
0
    vlc_vector_index_of(&r->items, item, &index);
291
0
    return index;
292
0
}
293
294
bool
295
randomizer_Count(struct randomizer *r)
296
0
{
297
0
    return r->items.size;
298
0
}
299
300
void
301
randomizer_Reshuffle(struct randomizer *r)
302
0
{
303
    /* yeah, it's that simple */
304
0
    r->head = 0;
305
0
    r->next = 0;
306
0
    r->history = r->items.size;
307
0
}
308
309
static inline void
310
swap_items(struct randomizer *r, int i, int j)
311
0
{
312
0
    vlc_playlist_item_t *item = r->items.data[i];
313
0
    r->items.data[i] = r->items.data[j];
314
0
    r->items.data[j] = item;
315
0
}
316
317
static inline void
318
randomizer_DetermineOne_(struct randomizer *r, size_t avoid_last_n)
319
0
{
320
0
    assert(r->head < r->items.size);
321
0
    assert(r->items.size - r->head > avoid_last_n);
322
0
    size_t range_len = r->items.size - r->head - avoid_last_n;
323
0
    size_t selected = r->head + (nrand48(r->xsubi) % range_len);
324
0
    swap_items(r, r->head, selected);
325
326
0
    if (r->head == r->history)
327
0
        r->history++;
328
0
    r->head++;
329
0
}
330
331
static inline void
332
randomizer_DetermineOne(struct randomizer *r)
333
0
{
334
0
    randomizer_DetermineOne_(r, 0);
335
0
}
336
337
/* An autoreshuffle occurs if loop is enabled, once all item have been played.
338
 * In that case, we reshuffle and determine first items so that the same item
339
 * may not be selected before NOT_SAME_BEFORE selections. */
340
static void
341
randomizer_AutoReshuffle(struct randomizer *r)
342
0
{
343
0
    assert(r->items.size > 0);
344
0
    r->head = 0;
345
0
    r->next = 0;
346
0
    r->history = 0; /* the whole content is history */
347
0
    size_t avoid_last_n = NOT_SAME_BEFORE;
348
0
    if (avoid_last_n > r->items.size - 1)
349
        /* cannot ignore all */
350
0
        avoid_last_n = r->items.size - 1;
351
0
    while (avoid_last_n)
352
0
        randomizer_DetermineOne_(r, avoid_last_n--);
353
0
}
354
355
bool
356
randomizer_HasPrev(struct randomizer *r)
357
0
{
358
0
    if (!r->loop)
359
        /* a previous exists if the current is > 0, i.e. next > 1 */
360
0
        return r->next > 1;
361
362
0
    if (!r->items.size)
363
        /* avoid modulo 0 */
364
0
        return false;
365
366
    /* there is no previous only if (current - history) == 0 (modulo size),
367
     * i.e. (next - history) == 1 (modulo size) */
368
0
    return (r->next + r->items.size - r->history) % r->items.size != 1;
369
0
}
370
371
bool
372
randomizer_HasNext(struct randomizer *r)
373
0
{
374
0
    return r->loop || r->next < r->items.size;
375
0
}
376
377
vlc_playlist_item_t *
378
randomizer_PeekPrev(struct randomizer *r)
379
0
{
380
0
    assert(randomizer_HasPrev(r));
381
0
    size_t index = (r->next + r->items.size - 2) % r->items.size;
382
0
    return r->items.data[index];
383
0
}
384
385
vlc_playlist_item_t *
386
randomizer_PeekNext(struct randomizer *r)
387
0
{
388
0
    assert(randomizer_HasNext(r));
389
390
0
    if (r->next == r->items.size && r->next == r->history)
391
0
    {
392
0
        assert(r->loop);
393
0
        randomizer_AutoReshuffle(r);
394
0
    }
395
396
0
    if (r->next == r->head)
397
        /* execute 1 step of the Fisher-Yates shuffle */
398
0
        randomizer_DetermineOne(r);
399
400
0
    return r->items.data[r->next];
401
0
}
402
403
vlc_playlist_item_t *
404
randomizer_Prev(struct randomizer *r)
405
0
{
406
0
    assert(randomizer_HasPrev(r));
407
0
    vlc_playlist_item_t *item = randomizer_PeekPrev(r);
408
0
    r->next = r->next ? r->next - 1 : r->items.size - 1;
409
0
    return item;
410
0
}
411
412
vlc_playlist_item_t *
413
randomizer_Next(struct randomizer *r)
414
0
{
415
0
    assert(randomizer_HasNext(r));
416
0
    vlc_playlist_item_t *item = randomizer_PeekNext(r);
417
0
    r->next++;
418
0
    if (r->next == r->items.size && r->next != r->head)
419
0
        r->next = 0;
420
0
    return item;
421
0
}
422
423
bool
424
randomizer_Add(struct randomizer *r, vlc_playlist_item_t *items[], size_t count)
425
0
{
426
0
    if (!vlc_vector_insert_all(&r->items, r->history, items, count))
427
0
        return false;
428
    /* the insertion shifted history (and possibly next) */
429
0
    if (r->next > r->history)
430
0
        r->next += count;
431
0
    r->history += count;
432
0
    return true;
433
0
}
434
435
static void
436
randomizer_SelectIndex(struct randomizer *r, size_t index)
437
0
{
438
0
    vlc_playlist_item_t *selected = r->items.data[index];
439
0
    if (r->history && index >= r->history)
440
0
    {
441
0
        if (index > r->history)
442
0
        {
443
0
            memmove(&r->items.data[r->history + 1],
444
0
                    &r->items.data[r->history],
445
0
                    (index - r->history) * sizeof(selected));
446
0
            index = r->history;
447
0
        }
448
0
        r->history = (r->history + 1) % r->items.size;
449
0
    }
450
451
0
    if (index >= r->head)
452
0
    {
453
0
        r->items.data[index] = r->items.data[r->head];
454
0
        r->items.data[r->head] = selected;
455
0
        r->head++;
456
0
    }
457
0
    else if (index < r->items.size - 1)
458
0
    {
459
0
        memmove(&r->items.data[index],
460
0
                &r->items.data[index + 1],
461
0
                (r->head - index - 1) * sizeof(selected));
462
0
        r->items.data[r->head - 1] = selected;
463
0
    }
464
465
0
    r->next = r->head;
466
0
}
467
468
void
469
randomizer_Select(struct randomizer *r, const vlc_playlist_item_t *item)
470
0
{
471
0
    ssize_t index = randomizer_IndexOf(r, item);
472
0
    assert(index != -1); /* item must exist */
473
0
    randomizer_SelectIndex(r, (size_t) index);
474
0
}
475
476
static void
477
randomizer_RemoveAt(struct randomizer *r, size_t index)
478
0
{
479
    /*
480
     * 0          head                                history   next  size
481
     * |-----------|...................................|---------|-----|
482
     * |<--------->|                                   |<------------->|
483
     *    ordered            order irrelevant               ordered
484
     */
485
486
    /* update next before index may be updated */
487
0
    if (index < r->next)
488
0
        r->next--;
489
490
0
    if (index < r->head)
491
0
    {
492
        /* item was selected, keep the selected part ordered */
493
0
        memmove(&r->items.data[index],
494
0
                &r->items.data[index + 1],
495
0
                (r->head - index - 1) * sizeof(*r->items.data));
496
0
        r->head--;
497
0
        index = r->head; /* the new index to remove */
498
0
    }
499
500
0
    if (index < r->history)
501
0
    {
502
        /* this part is unordered, no need to shift all items */
503
0
        r->items.data[index] = r->items.data[r->history - 1];
504
0
        index = r->history - 1;
505
0
        r->history--;
506
0
    }
507
508
0
    if (index < r->items.size - 1)
509
0
    {
510
        /* shift the ordered history part by one */
511
0
        memmove(&r->items.data[index],
512
0
                &r->items.data[index + 1],
513
0
                (r->items.size - index - 1) * sizeof(*r->items.data));
514
0
    }
515
516
0
    r->items.size--;
517
0
}
518
519
static void
520
randomizer_RemoveOne(struct randomizer *r, const vlc_playlist_item_t *item)
521
0
{
522
0
    ssize_t index = randomizer_IndexOf(r, item);
523
0
    assert(index >= 0); /* item must exist */
524
0
    randomizer_RemoveAt(r, index);
525
0
}
526
527
void
528
randomizer_Remove(struct randomizer *r, vlc_playlist_item_t *const items[],
529
                  size_t count)
530
0
{
531
0
    for (size_t i = 0; i < count; ++i)
532
0
        randomizer_RemoveOne(r, items[i]);
533
534
0
    vlc_vector_autoshrink(&r->items);
535
0
}
536
537
void
538
randomizer_Clear(struct randomizer *r)
539
0
{
540
0
    vlc_vector_clear(&r->items);
541
0
    r->head = 0;
542
0
    r->next = 0;
543
0
    r->history = 0;
544
0
}
545
546
#ifndef DOC
547
#ifdef TEST_RANDOMIZER
548
549
/* fake structure to simplify tests */
550
struct vlc_playlist_item {
551
    size_t index;
552
};
553
554
static void
555
ArrayInit(vlc_playlist_item_t *array[], size_t len)
556
{
557
    for (size_t i = 0; i < len; ++i)
558
    {
559
        array[i] = malloc(sizeof(*array[i]));
560
        assert(array[i]);
561
        array[i]->index = i;
562
    }
563
}
564
565
static void
566
ArrayDestroy(vlc_playlist_item_t *array[], size_t len)
567
{
568
    for (size_t i = 0; i < len; ++i)
569
        free(array[i]);
570
}
571
572
static void
573
test_all_items_selected_exactly_once(void)
574
{
575
    struct randomizer randomizer;
576
    randomizer_Init(&randomizer);
577
578
    #define SIZE 100
579
    vlc_playlist_item_t *items[SIZE];
580
    ArrayInit(items, SIZE);
581
582
    bool ok = randomizer_Add(&randomizer, items, SIZE);
583
    assert(ok);
584
585
    bool selected[SIZE] = {0};
586
    for (int i = 0; i < SIZE; ++i)
587
    {
588
        assert(randomizer_HasNext(&randomizer));
589
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
590
        assert(item);
591
        assert(!selected[item->index]); /* never selected twice */
592
        selected[item->index] = true;
593
    }
594
595
    assert(!randomizer_HasNext(&randomizer)); /* no more items */
596
597
    for (int i = 0; i < SIZE; ++i)
598
        assert(selected[i]); /* all selected */
599
600
    ArrayDestroy(items, SIZE);
601
    randomizer_Destroy(&randomizer);
602
    #undef SIZE
603
}
604
605
static void
606
test_all_items_selected_exactly_once_per_cycle(void)
607
{
608
    struct randomizer randomizer;
609
    randomizer_Init(&randomizer);
610
    randomizer_SetLoop(&randomizer, true);
611
612
    #define SIZE 100
613
    vlc_playlist_item_t *items[SIZE];
614
    ArrayInit(items, SIZE);
615
616
    bool ok = randomizer_Add(&randomizer, items, SIZE);
617
    assert(ok);
618
619
    for (int cycle = 0; cycle < 4; ++cycle)
620
    {
621
        bool selected[SIZE] = {0};
622
        for (int i = 0; i < SIZE; ++i)
623
        {
624
            assert(randomizer_HasNext(&randomizer));
625
            vlc_playlist_item_t *item = randomizer_Next(&randomizer);
626
            assert(item);
627
            assert(!selected[item->index]); /* never selected twice */
628
            selected[item->index] = true;
629
        }
630
631
        assert(randomizer_HasNext(&randomizer)); /* still has items in loop */
632
633
        for (int i = 0; i < SIZE; ++i)
634
            assert(selected[i]); /* all selected */
635
    }
636
637
    ArrayDestroy(items, SIZE);
638
    randomizer_Destroy(&randomizer);
639
    #undef SIZE
640
}
641
642
static void
643
test_all_items_selected_exactly_once_with_additions(void)
644
{
645
    struct randomizer randomizer;
646
    randomizer_Init(&randomizer);
647
648
    #define SIZE 100
649
    vlc_playlist_item_t *items[SIZE];
650
    ArrayInit(items, SIZE);
651
652
    bool ok = randomizer_Add(&randomizer, items, 75);
653
    assert(ok);
654
655
    bool selected[SIZE] = {0};
656
    for (int i = 0; i < 50; ++i)
657
    {
658
        assert(randomizer_HasNext(&randomizer));
659
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
660
        assert(item);
661
        assert(!selected[item->index]); /* never selected twice */
662
        selected[item->index] = true;
663
    }
664
665
    ok = randomizer_Add(&randomizer, &items[75], 25);
666
    assert(ok);
667
    for (int i = 50; i < SIZE; ++i)
668
    {
669
        assert(randomizer_HasNext(&randomizer));
670
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
671
        assert(item);
672
        assert(!selected[item->index]); /* never selected twice */
673
        selected[item->index] = true;
674
    }
675
676
    assert(!randomizer_HasNext(&randomizer)); /* no more items */
677
678
    for (int i = 0; i < SIZE; ++i)
679
        assert(selected[i]); /* all selected */
680
681
    ArrayDestroy(items, SIZE);
682
    randomizer_Destroy(&randomizer);
683
    #undef SIZE
684
}
685
686
static void
687
test_all_items_selected_exactly_once_with_removals(void)
688
{
689
    struct randomizer randomizer;
690
    randomizer_Init(&randomizer);
691
692
    #define SIZE 100
693
    vlc_playlist_item_t *items[SIZE];
694
    ArrayInit(items, SIZE);
695
696
    bool ok = randomizer_Add(&randomizer, items, SIZE);
697
    assert(ok);
698
699
    bool selected[SIZE] = {0};
700
    for (int i = 0; i < 50; ++i)
701
    {
702
        assert(randomizer_HasNext(&randomizer));
703
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
704
        assert(item);
705
        assert(!selected[item->index]); /* never selected twice */
706
        selected[item->index] = true;
707
    }
708
709
    vlc_playlist_item_t *to_remove[20];
710
    /* copy 10 items already selected */
711
    memcpy(to_remove, &randomizer.items.data[20], 10 * sizeof(*to_remove));
712
    /* copy 10 items not already selected */
713
    memcpy(&to_remove[10], &randomizer.items.data[70], 10 * sizeof(*to_remove));
714
715
    randomizer_Remove(&randomizer, to_remove, 20);
716
717
    for (int i = 50; i < SIZE - 10; ++i)
718
    {
719
        assert(randomizer_HasNext(&randomizer));
720
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
721
        assert(item);
722
        assert(!selected[item->index]); /* never selected twice */
723
        selected[item->index] = true;
724
    }
725
726
    assert(!randomizer_HasNext(&randomizer)); /* no more items */
727
728
    int count = 0;
729
    for (int i = 0; i < SIZE; ++i)
730
        if (selected[i])
731
            count++;
732
733
    assert(count == SIZE - 10);
734
735
    ArrayDestroy(items, SIZE);
736
    randomizer_Destroy(&randomizer);
737
    #undef SIZE
738
}
739
740
static void
741
test_cycle_after_manual_selection(void)
742
{
743
    struct randomizer randomizer;
744
    randomizer_Init(&randomizer);
745
    randomizer_SetLoop(&randomizer, true);
746
747
    #define SIZE 100
748
    vlc_playlist_item_t *items[SIZE];
749
    ArrayInit(items, SIZE);
750
751
    bool ok = randomizer_Add(&randomizer, items, 100);
752
    assert(ok);
753
754
    /* force selection of the first item */
755
    randomizer_Select(&randomizer, randomizer.items.data[0]);
756
757
    for (int i = 0; i < 2 * SIZE; ++i)
758
    {
759
        assert(randomizer_HasNext(&randomizer));
760
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
761
        assert(item);
762
    }
763
764
    assert(randomizer_HasNext(&randomizer)); /* still has items in loop */
765
766
    ArrayDestroy(items, SIZE);
767
    randomizer_Destroy(&randomizer);
768
    #undef SIZE
769
}
770
771
static void
772
test_cycle_with_additions_and_removals(void)
773
{
774
    struct randomizer randomizer;
775
    randomizer_Init(&randomizer);
776
    randomizer_SetLoop(&randomizer, true);
777
778
    #define SIZE 100
779
    vlc_playlist_item_t *items[SIZE];
780
    ArrayInit(items, SIZE);
781
782
    bool ok = randomizer_Add(&randomizer, items, 80);
783
    assert(ok);
784
785
    for (int i = 0; i < 30; ++i)
786
    {
787
        assert(randomizer_HasNext(&randomizer));
788
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
789
        assert(item);
790
    }
791
792
    vlc_playlist_item_t *to_remove[20];
793
    /* copy 10 items already selected */
794
    memcpy(to_remove, &randomizer.items.data[15], 10 * sizeof(*to_remove));
795
    /* copy 10 items not already selected */
796
    memcpy(&to_remove[10], &randomizer.items.data[60], 10 * sizeof(*to_remove));
797
798
    randomizer_Remove(&randomizer, to_remove, 20);
799
800
    /* it remains 40 items in the first cycle (30 already selected, and 10
801
     * removed from the 50 remaining) */
802
    for (int i = 0; i < 40; ++i)
803
    {
804
        assert(randomizer_HasNext(&randomizer));
805
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
806
        assert(item);
807
    }
808
809
    /* the first cycle is complete */
810
    assert(randomizer_HasNext(&randomizer));
811
    /* force the determination of the first item of the next cycle */
812
    vlc_playlist_item_t *item = randomizer_PeekNext(&randomizer);
813
    assert(item);
814
815
    assert(randomizer.items.size == 60);
816
    assert(randomizer.history == 1);
817
818
    /* save current history */
819
    vlc_playlist_item_t *history[59];
820
    memcpy(history, &randomizer.items.data[1], 59 * sizeof(*history));
821
822
    /* insert 20 new items */
823
    ok = randomizer_Add(&randomizer, &items[80], 20);
824
    assert(ok);
825
826
    assert(randomizer.items.size == 80);
827
    assert(randomizer.history == 21);
828
829
    for (int i = 0; i < 59; ++i)
830
        assert(history[i] == randomizer.items.data[21 + i]);
831
832
    /* remove 10 items in the history part */
833
    memcpy(to_remove, &randomizer.items.data[30], 10 * sizeof(*to_remove));
834
    randomizer_Remove(&randomizer, to_remove, 10);
835
836
    assert(randomizer.items.size == 70);
837
    assert(randomizer.history == 21);
838
839
    /* the other items in the history must be kept in order */
840
    for (int i = 0; i < 9; ++i)
841
        assert(history[i] == randomizer.items.data[21 + i]);
842
    for (int i = 0; i < 40; ++i)
843
        assert(history[i + 19] == randomizer.items.data[30 + i]);
844
845
    ArrayDestroy(items, SIZE);
846
    randomizer_Destroy(&randomizer);
847
    #undef SIZE
848
}
849
850
static void
851
test_force_select_new_item(void)
852
{
853
    struct randomizer randomizer;
854
    randomizer_Init(&randomizer);
855
856
    #define SIZE 100
857
    vlc_playlist_item_t *items[SIZE];
858
    ArrayInit(items, SIZE);
859
860
    bool ok = randomizer_Add(&randomizer, items, SIZE);
861
    assert(ok);
862
863
    bool selected[SIZE] = {0};
864
    for (int i = 0; i < SIZE; ++i)
865
    {
866
        vlc_playlist_item_t *item;
867
        if (i != 50)
868
        {
869
            assert(randomizer_HasNext(&randomizer));
870
            item = randomizer_Next(&randomizer);
871
        }
872
        else
873
        {
874
            /* force the selection of a new item not already selected */
875
            item = randomizer.items.data[62];
876
            randomizer_Select(&randomizer, item);
877
            /* the item should now be the last selected one */
878
            assert(randomizer.items.data[randomizer.next - 1] == item);
879
        }
880
        assert(item);
881
        assert(!selected[item->index]); /* never selected twice */
882
        selected[item->index] = true;
883
    }
884
885
    assert(!randomizer_HasNext(&randomizer)); /* no more items */
886
887
    for (int i = 0; i < SIZE; ++i)
888
        assert(selected[i]); /* all selected */
889
890
    ArrayDestroy(items, SIZE);
891
    randomizer_Destroy(&randomizer);
892
}
893
894
static void
895
test_force_select_item_already_selected(void)
896
{
897
    struct randomizer randomizer;
898
    randomizer_Init(&randomizer);
899
900
    #define SIZE 100
901
    vlc_playlist_item_t *items[SIZE];
902
    ArrayInit(items, SIZE);
903
904
    bool ok = randomizer_Add(&randomizer, items, SIZE);
905
    assert(ok);
906
907
    bool selected[SIZE] = {0};
908
    /* we need an additional loop cycle, since we select the same item twice */
909
    for (int i = 0; i < SIZE + 1; ++i)
910
    {
911
        vlc_playlist_item_t *item;
912
        if (i != 50)
913
        {
914
            assert(randomizer_HasNext(&randomizer));
915
            item = randomizer_Next(&randomizer);
916
        }
917
        else
918
        {
919
            /* force the selection of an item already selected */
920
            item = randomizer.items.data[42];
921
            randomizer_Select(&randomizer, item);
922
            /* the item should now be the last selected one */
923
            assert(randomizer.items.data[randomizer.next - 1] == item);
924
        }
925
        assert(item);
926
        /* never selected twice, except for item 50 */
927
        assert((i != 50) ^ selected[item->index]);
928
        selected[item->index] = true;
929
    }
930
931
    assert(!randomizer_HasNext(&randomizer)); /* no more items */
932
933
    for (int i = 0; i < SIZE; ++i)
934
        assert(selected[i]); /* all selected */
935
936
    ArrayDestroy(items, SIZE);
937
    randomizer_Destroy(&randomizer);
938
    #undef SIZE
939
}
940
941
static void
942
test_prev(void)
943
{
944
    struct randomizer randomizer;
945
    randomizer_Init(&randomizer);
946
947
    #define SIZE 10
948
    vlc_playlist_item_t *items[SIZE];
949
    ArrayInit(items, SIZE);
950
951
    bool ok = randomizer_Add(&randomizer, items, SIZE);
952
    assert(ok);
953
954
    assert(!randomizer_HasPrev(&randomizer));
955
956
    vlc_playlist_item_t *actual[SIZE];
957
    for (int i = 0; i < SIZE; ++i)
958
    {
959
        assert(randomizer_HasNext(&randomizer));
960
        actual[i] = randomizer_Next(&randomizer);
961
        assert(actual[i]);
962
    }
963
964
    assert(!randomizer_HasNext(&randomizer));
965
966
    for (int i = SIZE - 2; i >= 0; --i)
967
    {
968
        assert(randomizer_HasPrev(&randomizer));
969
        vlc_playlist_item_t *item = randomizer_Prev(&randomizer);
970
        assert(item == actual[i]);
971
    }
972
973
    assert(!randomizer_HasPrev(&randomizer));
974
975
    for (int i = 1; i < SIZE; ++i)
976
    {
977
        assert(randomizer_HasNext(&randomizer));
978
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
979
        assert(item == actual[i]);
980
    }
981
982
    ArrayDestroy(items, SIZE);
983
    randomizer_Destroy(&randomizer);
984
    #undef SIZE
985
}
986
987
static void
988
test_prev_with_select(void)
989
{
990
    struct randomizer randomizer;
991
    randomizer_Init(&randomizer);
992
993
    #define SIZE 10
994
    vlc_playlist_item_t *items[SIZE];
995
    ArrayInit(items, SIZE);
996
997
    bool ok = randomizer_Add(&randomizer, items, SIZE);
998
    assert(ok);
999
1000
    assert(!randomizer_HasPrev(&randomizer));
1001
1002
    vlc_playlist_item_t *actual[SIZE];
1003
    for (int i = 0; i < 5; ++i)
1004
    {
1005
        assert(randomizer_HasNext(&randomizer));
1006
        actual[i] = randomizer_Next(&randomizer);
1007
        assert(actual[i]);
1008
    }
1009
1010
    randomizer_Select(&randomizer, actual[2]);
1011
1012
    vlc_playlist_item_t *item;
1013
1014
    assert(randomizer_HasPrev(&randomizer));
1015
    item = randomizer_Prev(&randomizer);
1016
    assert(item == actual[4]);
1017
1018
    assert(randomizer_HasPrev(&randomizer));
1019
    item = randomizer_Prev(&randomizer);
1020
    assert(item == actual[3]);
1021
1022
    assert(randomizer_HasPrev(&randomizer));
1023
    item = randomizer_Prev(&randomizer);
1024
    assert(item == actual[1]);
1025
1026
    assert(randomizer_HasPrev(&randomizer));
1027
    item = randomizer_Prev(&randomizer);
1028
    assert(item == actual[0]);
1029
1030
    assert(!randomizer_HasPrev(&randomizer));
1031
1032
    ArrayDestroy(items, SIZE);
1033
    randomizer_Destroy(&randomizer);
1034
    #undef SIZE
1035
}
1036
1037
static void
1038
test_prev_across_reshuffle_loops(void)
1039
{
1040
    struct randomizer randomizer;
1041
    randomizer_Init(&randomizer);
1042
1043
    #define SIZE 10
1044
    vlc_playlist_item_t *items[SIZE];
1045
    ArrayInit(items, SIZE);
1046
1047
    bool ok = randomizer_Add(&randomizer, items, SIZE);
1048
    assert(ok);
1049
1050
    assert(!randomizer_HasPrev(&randomizer));
1051
1052
    vlc_playlist_item_t *actual[SIZE];
1053
    for (int i = 0; i < SIZE; ++i)
1054
    {
1055
        assert(randomizer_HasNext(&randomizer));
1056
        actual[i] = randomizer_Next(&randomizer);
1057
        assert(actual[i]);
1058
    }
1059
1060
    assert(!randomizer_HasNext(&randomizer));
1061
    randomizer_SetLoop(&randomizer, true);
1062
    assert(randomizer_HasNext(&randomizer));
1063
1064
    vlc_playlist_item_t *actualnew[4];
1065
    /* determine the 4 first items */
1066
    for (int i = 0; i < 4; ++i)
1067
    {
1068
        assert(randomizer_HasNext(&randomizer));
1069
        actualnew[i] = randomizer_Next(&randomizer);
1070
        assert(actualnew[i]);
1071
    }
1072
1073
    /* go back to the first */
1074
    for (int i = 2; i >= 0; --i)
1075
    {
1076
        assert(randomizer_HasPrev(&randomizer));
1077
        actualnew[i] = randomizer_Prev(&randomizer);
1078
        assert(actualnew[i]);
1079
    }
1080
1081
    assert(actualnew[0] == randomizer.items.data[0]);
1082
1083
    /* from now, any "prev" goes back to the history */
1084
1085
    int index_in_actual = 9;
1086
    for (int i = 0; i < 6; ++i)
1087
    {
1088
        assert(randomizer_HasPrev(&randomizer));
1089
        vlc_playlist_item_t *item = randomizer_Prev(&randomizer);
1090
1091
        int j;
1092
        for (j = 3; j >= 0; --j)
1093
            if (item == actualnew[j])
1094
                break;
1095
        bool in_actualnew = j != 0;
1096
1097
        if (in_actualnew)
1098
            /* the item has been selected for the new order, it is not in the
1099
             * history anymore */
1100
            index_in_actual--;
1101
        else
1102
            /* the remaining previous items are retrieved in reverse order in
1103
             * the history */
1104
            assert(item == actual[index_in_actual]);
1105
    }
1106
1107
    /* no more history: 4 in the current shuffle, 6 in the history */
1108
    assert(!randomizer_HasPrev(&randomizer));
1109
1110
    ArrayDestroy(items, SIZE);
1111
    randomizer_Destroy(&randomizer);
1112
    #undef SIZE
1113
}
1114
1115
/* when loop is enabled, we must take care that the last items of the
1116
 * previous order are not the same as the first items of the new order */
1117
static void
1118
test_loop_respect_not_same_before(void)
1119
{
1120
    struct randomizer randomizer;
1121
    randomizer_Init(&randomizer);
1122
    randomizer_SetLoop(&randomizer, true);
1123
1124
    #define SIZE (NOT_SAME_BEFORE + 2)
1125
    vlc_playlist_item_t *items[SIZE];
1126
    ArrayInit(items, SIZE);
1127
1128
    bool ok = randomizer_Add(&randomizer, items, SIZE);
1129
    assert(ok);
1130
1131
    vlc_playlist_item_t *actual[SIZE];
1132
    for (int i = 0; i < SIZE; ++i)
1133
    {
1134
        assert(randomizer_HasNext(&randomizer));
1135
        actual[i] = randomizer_Next(&randomizer);
1136
    }
1137
1138
    for (int cycle = 0; cycle < 20; cycle++)
1139
    {
1140
        /* check that the first items are not the same as the last ones of the
1141
         * previous order */
1142
        for (int i = 0; i < NOT_SAME_BEFORE; ++i)
1143
        {
1144
            assert(randomizer_HasNext(&randomizer));
1145
            actual[i] = randomizer_Next(&randomizer);
1146
            for (int j = (i + SIZE - NOT_SAME_BEFORE) % SIZE;
1147
                 j != i;
1148
                 j = (j + 1) % SIZE)
1149
            {
1150
                assert(actual[i] != actual[j]);
1151
            }
1152
        }
1153
        for (int i = NOT_SAME_BEFORE; i < SIZE; ++i)
1154
        {
1155
            assert(randomizer_HasNext(&randomizer));
1156
            actual[i] = randomizer_Next(&randomizer);
1157
        }
1158
    }
1159
1160
    ArrayDestroy(items, SIZE);
1161
    randomizer_Destroy(&randomizer);
1162
    #undef SIZE
1163
}
1164
1165
/* if there are less items than NOT_SAME_BEFORE, obviously we can't avoid
1166
 * repeating last items in the new order, but it must still work */
1167
static void
1168
test_loop_respect_not_same_before_impossible(void)
1169
{
1170
    struct randomizer randomizer;
1171
    randomizer_Init(&randomizer);
1172
    randomizer_SetLoop(&randomizer, true);
1173
1174
    #define SIZE NOT_SAME_BEFORE
1175
    vlc_playlist_item_t *items[SIZE];
1176
    ArrayInit(items, SIZE);
1177
1178
    bool ok = randomizer_Add(&randomizer, items, SIZE);
1179
    assert(ok);
1180
1181
    for (int i = 0; i < 10 * SIZE; ++i)
1182
    {
1183
        assert(randomizer_HasNext(&randomizer));
1184
        vlc_playlist_item_t *item = randomizer_Next(&randomizer);
1185
        assert(item);
1186
    }
1187
1188
    ArrayDestroy(items, SIZE);
1189
    randomizer_Destroy(&randomizer);
1190
    #undef SIZE
1191
}
1192
1193
static void
1194
test_has_prev_next_empty(void)
1195
{
1196
    struct randomizer randomizer;
1197
    randomizer_Init(&randomizer);
1198
1199
    assert(!randomizer_HasPrev(&randomizer));
1200
    assert(!randomizer_HasNext(&randomizer));
1201
1202
    randomizer_SetLoop(&randomizer, true);
1203
1204
    assert(!randomizer_HasPrev(&randomizer));
1205
1206
    /* there are always next items in loop mode */
1207
    assert(randomizer_HasNext(&randomizer));
1208
1209
    randomizer_Destroy(&randomizer);
1210
}
1211
1212
int main(void)
1213
{
1214
    test_all_items_selected_exactly_once();
1215
    test_all_items_selected_exactly_once_per_cycle();
1216
    test_all_items_selected_exactly_once_with_additions();
1217
    test_all_items_selected_exactly_once_with_removals();
1218
    test_cycle_after_manual_selection();
1219
    test_cycle_with_additions_and_removals();
1220
    test_force_select_new_item();
1221
    test_force_select_item_already_selected();
1222
    test_prev();
1223
    test_prev_with_select();
1224
    test_prev_across_reshuffle_loops();
1225
    test_loop_respect_not_same_before();
1226
    test_loop_respect_not_same_before_impossible();
1227
    test_has_prev_next_empty();
1228
}
1229
1230
#endif
1231
#endif