Merge pull request #3797 from ornicar/throttle-debounce
split throttle and debounce, improve debouncerpull/3820/head
commit
30385b6c41
|
@ -241,7 +241,7 @@ export default class AnalyseCtrl {
|
|||
});
|
||||
}
|
||||
|
||||
getDests: () => void = throttle(800, false, () => {
|
||||
getDests: () => void = throttle(800, () => {
|
||||
if (!this.embed && !defined(this.node.dests)) this.socket.sendAnaDests({
|
||||
variant: this.data.game.variant.key,
|
||||
fen: this.node.fen,
|
||||
|
@ -286,25 +286,25 @@ export default class AnalyseCtrl {
|
|||
}
|
||||
|
||||
private sound = li.sound ? {
|
||||
move: throttle(50, false, li.sound.move),
|
||||
capture: throttle(50, false, li.sound.capture),
|
||||
check: throttle(50, false, li.sound.check)
|
||||
move: throttle(50, li.sound.move),
|
||||
capture: throttle(50, li.sound.capture),
|
||||
check: throttle(50, li.sound.check)
|
||||
} : {
|
||||
move: $.noop,
|
||||
capture: $.noop,
|
||||
check: $.noop
|
||||
};
|
||||
|
||||
private onChange: () => void = throttle(300, false, () => {
|
||||
private onChange: () => void = throttle(300, () => {
|
||||
if (this.opts.onChange) {
|
||||
const mainlinePly = this.onMainline ? this.node.ply : false;
|
||||
this.opts.onChange!(this.node.fen, this.path, mainlinePly);
|
||||
}
|
||||
});
|
||||
|
||||
private updateHref: () => void = throttle(750, false, () => {
|
||||
private updateHref: () => void = li.fp.debounce(() => {
|
||||
if (!this.opts.study) window.history.replaceState(null, '', '#' + this.node.ply);
|
||||
}, false);
|
||||
}, 750);
|
||||
|
||||
autoScroll(): void {
|
||||
this.autoScrollRequested = true;
|
||||
|
@ -602,7 +602,7 @@ export default class AnalyseCtrl {
|
|||
return !this.gameOver() && !this.node.threefold;
|
||||
}
|
||||
|
||||
startCeval = throttle(800, false, () => {
|
||||
startCeval = throttle(800, () => {
|
||||
if (this.ceval.enabled()) {
|
||||
if (this.canUseCeval()) {
|
||||
this.ceval.start(this.path, this.nodeList, this.threatMode(), false);
|
||||
|
|
|
@ -64,7 +64,7 @@ export function make(opts): EvalCache {
|
|||
return fenFetched.indexOf(node.fen) !== -1;
|
||||
};
|
||||
return {
|
||||
onCeval: throttle(500, false, function() {
|
||||
onCeval: throttle(500, function() {
|
||||
const node = opts.getNode(), ev = node.ceval;
|
||||
if (ev && !ev.cloud && hasFetched(node) && qualityCheck(ev) && opts.canPut(node)) {
|
||||
opts.send("evalPut", toPutData(opts.variant, ev));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { throttle, prop, storedProp } from 'common';
|
||||
import { prop, storedProp } from 'common';
|
||||
import { controller as configCtrl } from './explorerConfig';
|
||||
import xhr = require('./openingXhr');
|
||||
import { synthetic } from '../util';
|
||||
|
@ -38,7 +38,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
|||
|
||||
const config = configCtrl(root.data.game, onConfigClose, root.trans, root.redraw);
|
||||
|
||||
const fetch = throttle(250, function() {
|
||||
const fetch = window.lichess.fp.debounce(function() {
|
||||
const fen = root.node.fen;
|
||||
const request: JQueryPromise<ExplorerData> = (withGames && tablebaseRelevant(effectiveVariant, fen)) ?
|
||||
xhr.tablebase(opts.tablebaseEndpoint, effectiveVariant, fen) :
|
||||
|
@ -55,7 +55,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
|||
failing(true);
|
||||
root.redraw();
|
||||
});
|
||||
}, false);
|
||||
}, 250, true);
|
||||
|
||||
const empty = {
|
||||
opening: true,
|
||||
|
|
|
@ -35,7 +35,7 @@ export function ctrl(root: AnalyseCtrl): CommentForm {
|
|||
doSubmit(text);
|
||||
};
|
||||
|
||||
const doSubmit = throttle(500, false, (text: string) => {
|
||||
const doSubmit = throttle(500, (text: string) => {
|
||||
const cur = current();
|
||||
if (cur) root.study!.makeChange('setComment', {
|
||||
ch: cur.chapterId,
|
||||
|
|
|
@ -125,7 +125,7 @@ function renderHint(ctrl: AnalyseCtrl): VNode {
|
|||
]);
|
||||
}
|
||||
|
||||
const saveNode = throttle(500, false, (ctrl: AnalyseCtrl, gamebook: Tree.Gamebook) => {
|
||||
const saveNode = throttle(500, (ctrl: AnalyseCtrl, gamebook: Tree.Gamebook) => {
|
||||
ctrl.socket.send('setGamebook', {
|
||||
path: ctrl.path,
|
||||
ch: ctrl.study!.vm.chapterId,
|
||||
|
|
|
@ -207,7 +207,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
).then(onReload, li.reload);
|
||||
};
|
||||
|
||||
const onSetPath = throttle(300, false, (path: Tree.Path) => {
|
||||
const onSetPath = throttle(300, (path: Tree.Path) => {
|
||||
if (vm.mode.sticky && path !== data.position.path) makeChange("setPath", addChapterId({
|
||||
path
|
||||
}));
|
||||
|
|
|
@ -37,7 +37,7 @@ export function ctrl(root: AnalyseCtrl) {
|
|||
});
|
||||
};
|
||||
|
||||
const toggleGlyph = throttle(500, false, function(id) {
|
||||
const toggleGlyph = throttle(500, function(id) {
|
||||
root.study!.makeChange('toggleGlyph', root.study!.withPosition({
|
||||
id
|
||||
}));
|
||||
|
|
|
@ -86,7 +86,7 @@ function renderPgnTags(chapter: StudyChapter, submit, types: string[]): VNode {
|
|||
|
||||
export function ctrl(root: AnalyseCtrl, getChapter: () => StudyChapter, types) {
|
||||
|
||||
const submit = throttle(500, false, function(name, value) {
|
||||
const submit = throttle(500, function(name, value) {
|
||||
root.study!.makeChange('setTag', {
|
||||
chapterId: getChapter().id,
|
||||
name,
|
||||
|
|
|
@ -72,7 +72,7 @@ export default function(opts: CevalOpts): CevalCtrl {
|
|||
|
||||
let lastEmitFen: string | null = null;
|
||||
|
||||
const onEmit = throttle(500, false, (ev: Tree.ClientEval, work: Work) => {
|
||||
const onEmit = throttle(500, (ev: Tree.ClientEval, work: Work) => {
|
||||
sortPvsInPlace(ev.pvs, (work.ply % 2 === (work.threatMode ? 1 : 0)) ? 'white' : 'black');
|
||||
npsRecorder(ev);
|
||||
curEval = ev;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/// <reference types="types/lichess" />
|
||||
|
||||
import throttle from './throttle';
|
||||
|
||||
export function defined<A>(v: A | undefined): v is A {
|
||||
return typeof v !== 'undefined';
|
||||
}
|
||||
|
@ -87,7 +85,28 @@ export function sync<T>(promise: Promise<T>): Sync<T> {
|
|||
return sync;
|
||||
}
|
||||
|
||||
export { throttle };
|
||||
// Ensures calls to the wrapped function are spaced by the given delay.
|
||||
// Any extra calls are dropped, except the last one.
|
||||
export function throttle(delay: number, callback: (...args: any[]) => void): (...args: any[]) => void {
|
||||
let timer: number | undefined;
|
||||
let lastExec = 0;
|
||||
|
||||
return function(this: any, ...args: any[]): void {
|
||||
const self: any = this;
|
||||
const elapsed = Date.now() - lastExec;
|
||||
|
||||
function exec() {
|
||||
timer = undefined;
|
||||
lastExec = Date.now();
|
||||
callback.apply(self, args);
|
||||
}
|
||||
|
||||
if (timer) clearTimeout(timer);
|
||||
|
||||
if (elapsed > delay) exec();
|
||||
else timer = setTimeout(exec, delay - elapsed);
|
||||
};
|
||||
}
|
||||
|
||||
export type F = () => void;
|
||||
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* https://github.com/niksy/throttle-debounce/blob/master/throttle.js
|
||||
*
|
||||
* Throttle execution of a function. Especially useful for rate limiting
|
||||
* execution of handlers on events like resize and scroll.
|
||||
*
|
||||
* @param {Number} delay A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
|
||||
* @param {Boolean} noTrailing Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds while the
|
||||
* throttled-function is being called. If noTrailing is false or unspecified, callback will be executed one final time
|
||||
* after the last throttled-function call. (After the throttled-function has not been called for `delay` milliseconds,
|
||||
* the internal counter is reset)
|
||||
* @param {Function} callback A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,
|
||||
* to `callback` when the throttled-function is executed.
|
||||
* @param {Boolean} debounceMode If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is false (at end),
|
||||
* schedule `callback` to execute after `delay` ms.
|
||||
*
|
||||
* @return {Function} A new, throttled, function.
|
||||
*/
|
||||
export default function(delay: number, noTrailing: boolean, callback: (...args: any[]) => void, debounceMode?: boolean): (...args: any[]) => void;
|
||||
export default function(delay: number, callback: (...args: any[]) => void, debounceMode?: boolean): (...args: any[]) => void;
|
||||
export default function(delay: number, noTrailing: any, callback: any, debounceMode?: any): (...args: any[]) => void {
|
||||
|
||||
// After wrapper has stopped being called, this timeout ensures that
|
||||
// `callback` is executed at the proper times in `throttle` and `end`
|
||||
// debounce modes.
|
||||
let timeoutID: number | undefined;
|
||||
|
||||
// Keep track of the last time `callback` was executed.
|
||||
let lastExec = 0;
|
||||
|
||||
// `noTrailing` defaults to falsy.
|
||||
if (typeof(noTrailing) !== 'boolean') {
|
||||
debounceMode = callback;
|
||||
callback = noTrailing;
|
||||
noTrailing = undefined;
|
||||
}
|
||||
|
||||
// The `wrapper` function encapsulates all of the throttling / debouncing
|
||||
// functionality and when executed will limit the rate at which `callback`
|
||||
// is executed.
|
||||
return function(this: any, ...args: any[]): void {
|
||||
|
||||
const self: any = this;
|
||||
const elapsed = Date.now() - lastExec;
|
||||
|
||||
// Execute `callback` and update the `lastExec` timestamp.
|
||||
function exec() {
|
||||
lastExec = Date.now();
|
||||
callback.apply(self, args);
|
||||
}
|
||||
|
||||
// If `debounceMode` is true (at begin) this is used to clear the flag
|
||||
// to allow future `callback` executions.
|
||||
function clear() {
|
||||
timeoutID = undefined;
|
||||
}
|
||||
|
||||
if (debounceMode && !timeoutID) {
|
||||
// Since `wrapper` is being called for the first time and
|
||||
// `debounceMode` is true (at begin), execute `callback`.
|
||||
exec();
|
||||
}
|
||||
|
||||
// Clear any existing timeout.
|
||||
if (timeoutID) {
|
||||
clearTimeout(timeoutID);
|
||||
}
|
||||
|
||||
if (debounceMode === undefined && elapsed > delay) {
|
||||
// In throttle mode, if `delay` time has been exceeded, execute
|
||||
// `callback`.
|
||||
exec();
|
||||
|
||||
} else if (noTrailing !== true) {
|
||||
// In trailing throttle mode, since `delay` time has not been
|
||||
// exceeded, schedule `callback` to execute `delay` ms after most
|
||||
// recent execution.
|
||||
//
|
||||
// If `debounceMode` is true (at begin), schedule `clear` to execute
|
||||
// after `delay` ms.
|
||||
//
|
||||
// If `debounceMode` is false (at end), schedule `callback` to
|
||||
// execute after `delay` ms.
|
||||
timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
|
@ -46,7 +46,7 @@ module.exports = function(env, domElement) {
|
|||
this.vm.filters = {};
|
||||
}.bind(this);
|
||||
|
||||
var askQuestion = throttle(1000, false, function() {
|
||||
var askQuestion = throttle(1000, function() {
|
||||
if (!this.validCombinationCurrent()) reset();
|
||||
this.pushState();
|
||||
this.vm.loading = true;
|
||||
|
|
|
@ -139,7 +139,7 @@ export default function(opts, redraw: () => void): Controller {
|
|||
socket.sendAnaMove(move);
|
||||
};
|
||||
|
||||
var getDests = throttle(800, false, function() {
|
||||
var getDests = throttle(800, function() {
|
||||
if (!vm.node.dests && treePath.contains(vm.path, vm.initialPath))
|
||||
socket.sendAnaDests({
|
||||
fen: vm.node.fen,
|
||||
|
@ -304,7 +304,7 @@ export default function(opts, redraw: () => void): Controller {
|
|||
if (ceval.enabled() && canUseCeval()) doStartCeval();
|
||||
};
|
||||
|
||||
const doStartCeval = throttle(800, false, function() {
|
||||
const doStartCeval = throttle(800, function() {
|
||||
ceval.start(vm.path, vm.nodeList, threatMode());
|
||||
});
|
||||
|
||||
|
@ -419,7 +419,7 @@ export default function(opts, redraw: () => void): Controller {
|
|||
|
||||
const callToVote = () => parseInt(nbToVoteCall()) < 1;
|
||||
|
||||
const vote = throttle(1000, false, function(v) {
|
||||
const vote = throttle(1000, function(v) {
|
||||
if (callToVote()) thanksUntil = Date.now() + 2000;
|
||||
nbToVoteCall(5);
|
||||
vm.voted = v;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { throttle } from 'common';
|
|||
const sounds = window.lichess.sound;
|
||||
|
||||
export const sound = {
|
||||
move: throttle(50, false, sounds.move),
|
||||
capture: throttle(50, false, sounds.capture),
|
||||
check: throttle(50, false, sounds.check)
|
||||
move: throttle(50, sounds.move),
|
||||
capture: throttle(50, sounds.capture),
|
||||
check: throttle(50, sounds.check)
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import RoundController from './ctrl';
|
|||
|
||||
let element: HTMLElement;
|
||||
|
||||
export const reload = throttle(1000, false, (ctrl: RoundController) => {
|
||||
export const reload = throttle(1000, (ctrl: RoundController) => {
|
||||
$.ajax({
|
||||
url: (ctrl.data.player.spectator ?
|
||||
router.game(ctrl.data, ctrl.data.player.color) :
|
||||
|
|
|
@ -138,9 +138,9 @@ export function make(send: SocketSend, ctrl: RoundController): RoundSocket {
|
|||
return {
|
||||
send,
|
||||
handlers,
|
||||
moreTime: throttle(300, false, () => send('moretime')),
|
||||
outoftime: throttle(500, false, () => send('flag', ctrl.data.game.player)),
|
||||
berserk: throttle(200, false, () => send('berserk', null, { ackable: true })),
|
||||
moreTime: throttle(300, () => send('moretime')),
|
||||
outoftime: throttle(500, () => send('flag', ctrl.data.game.player)),
|
||||
berserk: throttle(200, () => send('berserk', null, { ackable: true })),
|
||||
sendLoading(typ: string, data?: any) {
|
||||
ctrl.setLoading(true);
|
||||
send(typ, data);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { throttle } from 'common';
|
||||
|
||||
function throttled(sound: string): () => void {
|
||||
return throttle(100, false, () => window.lichess.sound[sound]())
|
||||
return throttle(100, () => window.lichess.sound[sound]())
|
||||
}
|
||||
|
||||
export const move = throttled('move');
|
||||
|
|
|
@ -46,17 +46,19 @@ lichess.topMenuIntent = function() {
|
|||
};
|
||||
lichess.fp.debounce = function(func, wait, immediate) {
|
||||
var timeout;
|
||||
var lastBounce = 0;
|
||||
return function() {
|
||||
var context = this,
|
||||
args = arguments;
|
||||
args = arguments,
|
||||
elapsed = Date.now() - lastBounce;
|
||||
lastBounce = Date.now();
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
if (immediate && elapsed > wait) func.apply(context, args);
|
||||
else timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -64,9 +64,9 @@ function playerInfo(ctrl: TournamentController, userId: string) {
|
|||
}
|
||||
|
||||
export default {
|
||||
join: throttle(1000, false, join),
|
||||
withdraw: throttle(1000, false, withdraw),
|
||||
loadPage: throttle(1000, false, loadPage),
|
||||
reloadTournament: throttle(2000, false, reloadTournament),
|
||||
join: throttle(1000, join),
|
||||
withdraw: throttle(1000, withdraw),
|
||||
loadPage: throttle(1000, loadPage),
|
||||
reloadTournament: throttle(2000, reloadTournament),
|
||||
playerInfo
|
||||
};
|
||||
|
|
|
@ -21,7 +21,10 @@ interface Lichess {
|
|||
escapeHtml(html: string): string
|
||||
toYouTubeEmbedUrl(url: string): string
|
||||
|
||||
fp: any
|
||||
fp: {
|
||||
debounce(func: (...args: any[]) => void, wait: number, immediate?: boolean): (...args: any[]) => void;
|
||||
contains<T>(list: T[], needle: T): boolean;
|
||||
}
|
||||
sound: any
|
||||
powertip: any
|
||||
userAutocomplete: any
|
||||
|
|
Loading…
Reference in New Issue