Initial commit 🎉
fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2 Co-authored-by: Sebastian McKenzie <sebmck@fb.com> Co-authored-by: John Knox <jknox@fb.com> Co-authored-by: Emil Sjölander <emilsj@fb.com> Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
114
src/ui/styled/gc.js
Normal file
114
src/ui/styled/gc.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {Tracker, RulesToClass} from './index.js';
|
||||
import type {StyleSheet} from './sheet.js';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
export class GarbageCollector {
|
||||
constructor(sheet: StyleSheet, tracker: Tracker, rulesToClass: RulesToClass) {
|
||||
this.sheet = sheet;
|
||||
this.tracker = tracker;
|
||||
|
||||
// used to keep track of what classes are actively in use
|
||||
this.usedClasses = new Map();
|
||||
|
||||
// classes to be removed, we put this in a queue and perform it in bulk rather than straight away
|
||||
// since by the time the next tick happens this style could have been reinserted
|
||||
this.classRemovalQueue = new Set();
|
||||
|
||||
this.rulesToClass = rulesToClass;
|
||||
}
|
||||
|
||||
tracker: Tracker;
|
||||
sheet: StyleSheet;
|
||||
usedClasses: Map<string, number>;
|
||||
garbageTimer: ?TimeoutID;
|
||||
classRemovalQueue: Set<string>;
|
||||
rulesToClass: RulesToClass;
|
||||
|
||||
hasQueuedCollection(): boolean {
|
||||
return Boolean(this.garbageTimer);
|
||||
}
|
||||
|
||||
getReferenceCount(key: string): number {
|
||||
return this.usedClasses.get(key) || 0;
|
||||
}
|
||||
|
||||
// component has been mounted so make sure it's being depended on
|
||||
registerClassUse(name: string) {
|
||||
const count = this.usedClasses.get(name) || 0;
|
||||
this.usedClasses.set(name, count + 1);
|
||||
if (this.classRemovalQueue.has(name)) {
|
||||
this.classRemovalQueue.delete(name);
|
||||
|
||||
if (this.classRemovalQueue.size === 0) {
|
||||
this.haltGarbage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// component has been unmounted so remove it's dependencies
|
||||
deregisterClassUse(name: string) {
|
||||
let count = this.usedClasses.get(name);
|
||||
if (count == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
count--;
|
||||
this.usedClasses.set(name, count);
|
||||
|
||||
if (count === 0) {
|
||||
this.classRemovalQueue.add(name);
|
||||
this.scheduleGarbage();
|
||||
}
|
||||
}
|
||||
|
||||
scheduleGarbage() {
|
||||
if (this.garbageTimer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.garbageTimer = setTimeout(() => {
|
||||
this.collectGarbage();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
haltGarbage() {
|
||||
if (this.garbageTimer) {
|
||||
clearTimeout(this.garbageTimer);
|
||||
this.garbageTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
getCollectionQueue() {
|
||||
return Array.from(this.classRemovalQueue);
|
||||
}
|
||||
|
||||
collectGarbage() {
|
||||
this.haltGarbage();
|
||||
for (const name of this.classRemovalQueue) {
|
||||
const trackerInfo = this.tracker.get(name);
|
||||
invariant(trackerInfo != null, 'trying to remove unknown class');
|
||||
|
||||
const {rules} = trackerInfo;
|
||||
this.rulesToClass.delete(rules);
|
||||
|
||||
this.sheet.delete(name);
|
||||
this.tracker.delete(name);
|
||||
this.usedClasses.delete(name);
|
||||
}
|
||||
this.classRemovalQueue.clear();
|
||||
}
|
||||
|
||||
flush() {
|
||||
this.haltGarbage();
|
||||
this.classRemovalQueue.clear();
|
||||
this.usedClasses.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user