ag-grid
Version:
Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components
128 lines (105 loc) • 4.15 kB
text/typescript
import {Autowired, Bean, PostConstruct} from "../context/context";
import {GridPanel} from "../gridPanel/gridPanel";
import {LinkedList} from "./linkedList";
import {GridOptionsWrapper} from "../gridOptionsWrapper";
import {AnimationQueueEmptyEvent} from "../events";
import {Events} from "../eventKeys";
import {EventService} from "../eventService";
export class AnimationFrameService {
private gridOptionsWrapper: GridOptionsWrapper;
private eventService: EventService;
private gridPanel: GridPanel;
private p1Tasks = new LinkedList<()=>void>();
private p2Tasks = new LinkedList<()=>void>();
private ticking = false;
private useAnimationFrame: boolean;
public registerGridComp(gridPanel: GridPanel): void {
this.gridPanel = gridPanel;
}
private init(): void {
this.useAnimationFrame = !this.gridOptionsWrapper.isSuppressAnimationFrame();
}
// this method is for our ag-Grid sanity only - if animation frames are turned off,
// then no place in the code should be looking to add any work to be done in animation
// frames. this stops bugs - where some code is asking for a frame to be executed
// when it should not.
private verifyAnimationFrameOn(methodName: string): void {
if (this.useAnimationFrame===false) {
console.warn(`ag-Grid: AnimationFrameService.${methodName} called but animation frames are off`);
}
}
public addP1Task(task: ()=>void): void {
this.verifyAnimationFrameOn('addP1Task');
this.p1Tasks.add(task);
this.schedule();
}
public addP2Task(task: ()=>void): void {
this.verifyAnimationFrameOn('addP2Task');
this.p2Tasks.add(task);
this.schedule();
}
private executeFrame(millis: number): void {
this.verifyAnimationFrameOn('executeFrame');
let frameStart = new Date().getTime();
let duration = (new Date().getTime()) - frameStart;
let gridPanelNeedsAFrame = true;
// 16ms is 60 fps
let noMaxMillis = millis <= 0;
while (noMaxMillis || duration < millis) {
if (gridPanelNeedsAFrame) {
gridPanelNeedsAFrame = this.gridPanel.executeFrame();
} else if (!this.p1Tasks.isEmpty()) {
let task = this.p1Tasks.remove();
task();
} else if (!this.p2Tasks.isEmpty()) {
let task = this.p2Tasks.remove();
task();
} else {
break;
}
duration = (new Date().getTime()) - frameStart;
}
if (gridPanelNeedsAFrame || !this.p1Tasks.isEmpty() || !this.p2Tasks.isEmpty()) {
this.requestFrame();
} else {
this.stopTicking();
}
}
private stopTicking(): void {
this.ticking = false;
let event: AnimationQueueEmptyEvent = {
type: Events.EVENT_ANIMATION_QUEUE_EMPTY,
columnApi: this.gridOptionsWrapper.getColumnApi(),
api: this.gridOptionsWrapper.getApi()
};
this.eventService.dispatchEvent(event);
}
public flushAllFrames(): void {
if (!this.useAnimationFrame) { return; }
this.executeFrame(-1);
}
public schedule(): void {
if (!this.useAnimationFrame) { return; }
if (!this.ticking) {
this.ticking = true;
this.requestFrame();
}
}
private requestFrame(): void {
// check for the existence of requestAnimationFrame, and if
// it's missing, then we polyfill it with setTimeout()
let callback = this.executeFrame.bind(this, 60);
if (window.requestAnimationFrame) {
window.requestAnimationFrame(callback);
} else if (window.webkitRequestAnimationFrame) {
window.webkitRequestAnimationFrame(callback);
} else {
setTimeout(callback, 0);
}
}
public isQueueEmpty(): boolean {
return this.ticking;
}
}