All files / src rewind.js

100% Statements 29/29
100% Branches 7/7
100% Functions 7/7
100% Lines 25/25

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                    28x 28x 28x 28x                     65x 65x 65x               18x 16x 16x 16x 16x 16x               5x 4x 4x             4x 4x 4x               17x 17x 17x 30x   17x               40x      
/**
 * Circular buffer of emulator state snapshots for rewind functionality.
 * Snapshots are stored directly without deep-copying, since
 * snapshotState() already clones all TypedArrays via .slice().
 */
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.
     * The caller must ensure the snapshot's typed arrays are already
     * independent copies (e.g. from snapshotState() which uses .slice()).
     * Overwrites the oldest snapshot when full.
     * @param {object} snapshot - emulator state snapshot (already cloned)
     */
    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;
    }
 
    /**
     * 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;
    }
}