Update NO_REPEAT_RANDOM to use Sample Without Replacement (2nd Try)#853
Update NO_REPEAT_RANDOM to use Sample Without Replacement (2nd Try)#853pedker wants to merge 8 commits intoprofezzorn:masterfrom
Conversation
| #ifdef KILL_OLD_PLAYERS | ||
| killable_ = false; | ||
| #endif | ||
| #ifdef NO_REPEAT_RANDOM |
There was a problem hiding this comment.
If we're going to have this code (which I'm not entirely convinced it is a good idea.) then we should use a new define to enable it. Maybe the NO_REPEAT_RANDOM_BUFFER_SIZE_BITS define?
sound/effect.h
Outdated
|
|
||
| #ifdef NO_REPEAT_RANDOM | ||
| // Storage for random sampling without replacement. | ||
| bool available_[NO_REPEAT_RANDOM_BUFFER_SIZE_BITS]; |
There was a problem hiding this comment.
An array of bool does not take one bit per bool.
In most cases, a bool takes one byte.
There was a problem hiding this comment.
Ah, crap! I incorrectly assumed that a boolean took up one bit in memory. I will have to look into the feasibility of refactoring to instead use a single integer with bitmasking.
This means that available_ will change to be either of type uint16_t or uint32_t, and the size will no longer be configurable. The current version of the define macro will be renamed/reused as the enable define, mentioned above. 32 bits is close to too much memory, considering average file counts. 16 bits is way below the budget, but a max of 16 files per Effect may feel a bit constraining. I am leaning towards 16 bits and will procced that way unless told otherwise.
sound/effect.h
Outdated
| #ifdef NO_REPEAT_RANDOM | ||
| // Storage for random sampling without replacement. | ||
| bool available_[NO_REPEAT_RANDOM_BUFFER_SIZE_BITS]; | ||
| bool available_mark_ = false; |
There was a problem hiding this comment.
This needs a better name, or a comment that explains what it is for.
sound/effect.h
Outdated
| for (uint8_t i = 0; i < total_files; ++i) { | ||
| const int16_t file = sub_files_ ? i / sub_files_ : i; | ||
| const int16_t subfile = sub_files_ ? i % sub_files_ : 0; | ||
| n += available_[i] == available_mark_ && (last_ != file || (sub_files_ && last_subid_ != subfile)); |
There was a problem hiding this comment.
Use an if statement here, it will make the code easier to read.
sound/effect.h
Outdated
| n += available_[i] == available_mark_ && (last_ != file || (sub_files_ && last_subid_ != subfile)); | ||
| } | ||
| if (!n) { | ||
| available_mark_ = !available_mark_; |
There was a problem hiding this comment.
Using available_mark_ this way doesn't save anything if you have to iterate over the whole array to check how many elements are available anyways.
There was a problem hiding this comment.
It lowers the worst case from 3N to 2N, since the alternative is to iterate over the array one more time to mark everything as available. I agree though, it's not really much of a save since it's still all O(N). The refactor will scrap this anyway, since if available_ is becoming an integer I can just set it back to 0 or max
There was a problem hiding this comment.
Marking everything as available only has to be done every Nth time though, so the average time spent ends up being constant.
|
Successfully converted this to use a Something else to note is that I have not been able to test the |
| } | ||
| return ret; | ||
| } | ||
| size_t popcount_subset(size_t first, size_t last) const { |
There was a problem hiding this comment.
I'm wondering if it would be easier (and faster) to do all this subset stuff with ands and ors.
Basically something like:
Bitset subset = ~(Bitset::All() << last) & (Bitset::All() << first);
size_t popcount_subset = (subset & bitset).popcount();
sound/effect.h
Outdated
| uint8_t n = available_.popcount(); | ||
| if (!n) { | ||
| available_.fill(total_files); | ||
| n = available_.popcount(); |
There was a problem hiding this comment.
n = total_files; would be faster
common/arg_parser.h
Outdated
| bits_[i] = 0; | ||
| } | ||
| } | ||
| void fill(size_t bit = SIZE) { |
There was a problem hiding this comment.
This should be a static function that returns a bitset. It also needs a comment to explain what it does.
common/arg_parser.h
Outdated
| } | ||
| } | ||
| void fill(size_t bit = SIZE) { | ||
| for (size_t i = 0; i < NELEM(bits_); i++) { |
There was a problem hiding this comment.
The loop should only iterate over all bits_[] where it can assign 0xFFFFFFFF, then assign the final value after the loop.
|
Needed to add some bitwise operator overloads to support the proposed changes, figured I'd take a shot at making them all while I'm there (except XOR). I'm considering renaming some or all of the instances of |
| ret >>= bits; | ||
| return ret; | ||
| } | ||
| void operator<<=(int bits) { |
There was a problem hiding this comment.
We should probably improve these while we're at it, something like:
uint32_t get_word(int word) {
if (word < 0 || word >= (int) NELEM(bits_)) return 0;
return bits_[word];
}
uint32_t get32(int pos) {
uint64_t tmp = get_word(1 + (pos >> 5));
tmp <<= 32;
tmp |= get_word(pos >> 5);
return tmp >> (pos & 31);
}
BitSet<SIZE> operator>>(int bits) const {
BitSet<SIZE> ret;
for (int i = 0; i < SIZE; i++) ret.bits_[i]= get32(i*32 + bits);
return ret;
}<< would be basically the same, but with a - inside the get32() call.
|
I've implemented the suggestions for the shifters. It compiles, but I currently do not have time to thoroughly test it as I am out of town soon. I will do thorough testing when I return, but on paper this looks pretty good to me |
|
Identified a crashing issue and put up a fix. Unfortunately, before I could verify the fix and finish testing, I accidentally ripped the USB port right off of the ProffieBoard of my saber. So, for now, my ability to work on this has been crippled. I'm not sure how long it will take to remedy, as I may have to contact support, try to DIY fix/replace the board on my own, or wait until I can afford to pay for a replacement solution. |
|
I have replaced my soundboard! I've done more extensive testing now, everything is behaving as expected now. I was also able to test calling |
common/arg_parser.h
Outdated
| } | ||
| void operator<<=(int bits) { | ||
| if (!bits) return; | ||
| const size_t n = NELEM(bits_); |
There was a problem hiding this comment.
This looks like a complicated way to write:
for (int i = NELEM(bits_)-1; i >= 0; i--) {There was a problem hiding this comment.
Fair, I was going to but was afraid of moving away from using size_t
|
I also caught something I did wrong when attempting to address an edge case in |
This is a second try at this PR, attempting to address its raised issues.
The storage used to manage the Sample-Without-Replacement algorithm is now a one-dimensional boolean array on each Effect, created statically of size
NO_REPEAT_RANDOM_BUFFER_SIZE_BITS. Each bit of the array is used to manage an individual Sound File, and the array's size determines the maximum number of Sound Files each Effect can support using SWR. If an Effect has more Sound Files than available memory, then the Effect will default back to using the existingNO_REPEAT_RANDOMlogic.To meet the requirement of using less than one byte of memory per File, the value of
NO_REPEAT_RANDOM_BUFFER_SIZE_BITSshould be tuned relative to the ratio of Files to Effects used by Fonts on average. For example, setting it to 16 would only require the ratio to be 2:1, A value of 24 would require 3:1, and a value of 32 would require 4:1.