<commit_before>// Copyright © 2017 <NAME>.
// This file is part of Attabench: https://github.com/lorentey/Attabench
// For licensing information, see the file LICENSE.md in the Git repository above.

import Foundation

class RateLimiter: NSObject {
    var maxDelay: TimeInterval
    let action: () -> Void
    private var scheduled = false
    private var next = Date.distantPast

    init(maxDelay: TimeInterval, action: @escaping () -> Void) {
        self.maxDelay = maxDelay
        self.action = action
    }

    private func cancel() {
        NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(RateLimiter.now), object: nil)
        scheduled = false
    }

    @objc func now() {
        cancel()
        action()
        next = Date(timeIntervalSinceNow: maxDelay)
    }

    func later() {
        if scheduled { return }
        let now = Date()
        if next < now {
            self.now()
        }
        else {
            self.perform(#selector(RateLimiter.now), with: nil, afterDelay: next.timeIntervalSince(now))
            scheduled = true
        }
    }

    func nowIfNeeded() {
        if scheduled { now() }
    }

}
<commit_msg>Don’t keep regenerating chart when loading a document<commit_after>// Copyright © 2017 <NAME>.
// This file is part of Attabench: https://github.com/lorentey/Attabench
// For licensing information, see the file LICENSE.md in the Git repository above.

import Foundation

class RateLimiter: NSObject {
    var maxDelay: TimeInterval
    let action: () -> Void
    private var scheduled = false
    private var performing = false
    private var next = Date.distantPast

    init(maxDelay: TimeInterval, action: @escaping () -> Void) {
        self.maxDelay = maxDelay
        self.action = action
    }

    private func cancel() {
        NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(RateLimiter.now), object: nil)
        scheduled = false
    }

    @objc func now() {
        if performing { return }
        cancel()
        performing = true
        DispatchQueue.main.async {
            self.performing = false
            self.action()
            self.next = Date(timeIntervalSinceNow: self.maxDelay)
        }
    }

    func later() {
        if scheduled { return }
        if performing { return }
        let now = Date()
        if next < now {
            self.now()
        }
        else {
            self.perform(#selector(RateLimiter.now), with: nil, afterDelay: next.timeIntervalSince(now))
            scheduled = true
        }
    }

    func nowIfNeeded() {
        if scheduled { now() }
    }
}

{'fraction_non_alphanumeric': 0.09772727272727273, 'fraction_numerical': 0.0030303030303030303, 'mean_word_length': 2.15531660692951, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6171202', 'n_tokens_mistral': 751, 'n_tokens_neox': 726, 'n_words': 214}