All files / src rewind.js

100% Statements 30/30
100% Branches 9/9
100% Functions 8/8
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                  81x 81x 81x 81x               384x 384x 384x               22x 20x 20x 20x 20x 20x               343x               180x 179x 179x             10x 10x 10x               44x 44x 44x 221x   44x               195x      
/**
 * Circular buffer of emulator state snapshots, for rewind and the frame ring that feeds it.
 * Snapshots are stored directly, so the caller hands over ones nothing else holds.
 */
export class RewindBuffer {
    /**
     * @param {number} maxSnapshots - maximum number of snapshots to retain
     */
    constructor(maxSnapshots = 30) {
        this.maxSnapshots = maxSnapshots;
        this.snapshots = new Array(maxSnapshots);
        this.count = 0;
        this.writeIndex = 0;
    }
 
    /**
     * Push a snapshot into the buffer, overwriting the oldest when full.
     * @param {object} snapshot - emulator state snapshot that nothing else holds
     */
    push(snapshot) {
        this.snapshots[this.writeIndex] = snapshot;
        this.writeIndex = (this.writeIndex + 1) % this.maxSnapshots;
        if (this.count < this.maxSnapshots) this.count++;
    }
 
    /**
     * Pop the most recent snapshot from the buffer.
     * @returns {object|null} the most recent snapshot, or null if empty
     */
    pop() {
        if (this.count === 0) return null;
        this.writeIndex = (this.writeIndex - 1 + this.maxSnapshots) % this.maxSnapshots;
        this.count--;
        const snapshot = this.snapshots[this.writeIndex];
        this.snapshots[this.writeIndex] = null;
        return snapshot;
    }
 
    /**
     * The snapshot the next push overwrites, whose storage that push may reuse.
     * @returns {object|null} the oldest snapshot when full, or null
     */
    nextToReplace() {
        return this.count === this.maxSnapshots ? this.snapshots[this.writeIndex] : null;
    }
 
    /**
     * Peek at the most recent snapshot without removing it.
     * @returns {object|null} the most recent snapshot, or null if empty
     */
    peek() {
        if (this.count === 0) return null;
        const index = (this.writeIndex - 1 + this.maxSnapshots) % this.maxSnapshots;
        return this.snapshots[index];
    }
 
    /**
     * Clear all snapshots from the buffer.
     */
    clear() {
        this.snapshots.fill(null);
        this.count = 0;
        this.writeIndex = 0;
    }
 
    /**
     * Return all snapshots in order from oldest to newest.
     * @returns {object[]} array of snapshots
     */
    getAll() {
        const result = new Array(this.count);
        const start = (this.writeIndex - this.count + this.maxSnapshots) % this.maxSnapshots;
        for (let i = 0; i < this.count; i++) {
            result[i] = this.snapshots[(start + i) % this.maxSnapshots];
        }
        return result;
    }
 
    /**
     * Number of snapshots currently stored.
     * @returns {number}
     */
    get length() {
        return this.count;
    }
}