UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

201 lines (193 loc) • 9.01 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var DEFAULT_INITIAL_BUCKETS = 50; var DEFAULT_MAX_BUCKET_CAPACITY = 50; var DEFAULT_SCALE_RATIO = 0.5; /** * Creates an empty bucket object with a specified capacity. Each bucket is designed * to hold a certain number of React portals and has an associated updater function * which can be null initially. * * @function createEmptyBucket * @param {number} capacity - The maximum capacity of the bucket. * @returns {PortalBucketType} An object representing an empty bucket with the specified capacity. */ function createEmptyBucket(capacity) { return { portals: {}, capacity: capacity, updater: null }; } /** * A utility class to manage and dynamically scale React portals across multiple buckets. * It allows for efficient rendering of large numbers of React portals by distributing them * across "buckets" and updating these buckets as necessary to balance load and performance. * * @class PortalManager * @typedef {Object} PortalManager * * @property {number} maxBucketCapacity - The maximum capacity of each bucket before a new bucket is created. * @property {number} scaleRatio - The ratio to determine the number of new buckets to add when scaling up. * @property {Array<PortalBucketType>} buckets - An array of bucket objects where each bucket holds a record of React portals. * @property {Set<number>} availableBuckets - A set of indices representing buckets that have available capacity. * @property {Map<React.Key, number>} portalToBucketMap - A map of React portal keys to their corresponding bucket indices. * @property {PortalRendererUpdater|null} portalRendererUpdater - A function to trigger updates to the rendering of portals. * @property {number} scaleCapacityThreshold - The threshold at which the buckets are scaled up to accommodate more portals. * * @param {number} [initialBuckets=DEFAULT_INITIAL_BUCKETS] - The initial number of buckets to create. * @param {number} [maxBucketCapacity=DEFAULT_MAX_BUCKET_CAPACITY] - The maximum number of portals a single bucket can hold. * @param {number} [scaleRatio=DEFAULT_SCALE_RATIO] - The ratio used to calculate the number of new buckets to add when scaling. */ export var PortalManager = /*#__PURE__*/function () { function PortalManager() { var initialBuckets = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_INITIAL_BUCKETS; var maxBucketCapacity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_MAX_BUCKET_CAPACITY; var scaleRatio = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_SCALE_RATIO; _classCallCheck(this, PortalManager); this.maxBucketCapacity = maxBucketCapacity; this.scaleRatio = scaleRatio; // Initialise buckets array by creating an array of length `initialBuckets` containing empty buckets this.buckets = Array.from({ length: initialBuckets }, function () { return createEmptyBucket(maxBucketCapacity); }); this.portalToBucketMap = new Map(); this.availableBuckets = new Set(Array.from({ length: initialBuckets }, function (_, i) { return i; })); this.portalRendererUpdater = null; this.scaleCapacityThreshold = maxBucketCapacity / 2; } return _createClass(PortalManager, [{ key: "getCurrentBucket", value: function getCurrentBucket() { return this.availableBuckets.values().next().value; } }, { key: "createBucket", value: function createBucket() { var _this$portalRendererU; var currentBucket = this.getCurrentBucket(); //If the current bucket has capacity, skip this logic if (this.buckets[currentBucket].capacity > 0) { return; } else { // The current bucket is full, delete the bucket from the list of available buckets this.availableBuckets.delete(currentBucket); } // Skip creating new bucket if there are buckets still available if (this.availableBuckets.size > 0) { return; } // Scale the buckets up only if there are no available buckets left // Calculate how many new buckets need to be added var numBucketsToAdd = Math.floor(this.buckets.length * this.scaleRatio); this.buckets = _toConsumableArray(this.buckets); for (var i = 0; i < numBucketsToAdd; i++) { this.buckets.push(createEmptyBucket(this.maxBucketCapacity)); this.availableBuckets.add(this.buckets.length - 1); } (_this$portalRendererU = this.portalRendererUpdater) === null || _this$portalRendererU === void 0 || _this$portalRendererU.call(this, this.buckets); } }, { key: "getBuckets", value: function getBuckets() { return this.buckets; } }, { key: "registerBucket", value: function registerBucket(id, updater) { var _this$buckets$id$upda, _this$buckets$id, _this = this; this.buckets[id].updater = updater; (_this$buckets$id$upda = (_this$buckets$id = this.buckets[id]).updater) === null || _this$buckets$id$upda === void 0 || _this$buckets$id$upda.call(_this$buckets$id, function () { return _objectSpread({}, _this.buckets[id].portals); }); } }, { key: "unregisterBucket", value: function unregisterBucket(id) { this.buckets[id].updater = null; } }, { key: "updateBuckets", value: function updateBuckets(id) { var _this$buckets$id$upda2, _this$buckets$id2, _this2 = this; (_this$buckets$id$upda2 = (_this$buckets$id2 = this.buckets[id]).updater) === null || _this$buckets$id$upda2 === void 0 || _this$buckets$id$upda2.call(_this$buckets$id2, function () { // new object is required to trigger react updates return _objectSpread({}, _this2.buckets[id].portals); }); } }, { key: "registerPortal", value: function registerPortal(key, portal) { var _this$portalToBucketM, _this3 = this; this.createBucket(); this.buckets[this.getCurrentBucket()].capacity -= 1; var id = (_this$portalToBucketM = this.portalToBucketMap.get(key)) !== null && _this$portalToBucketM !== void 0 ? _this$portalToBucketM : this.getCurrentBucket(); this.portalToBucketMap.set(key, id); if (this.buckets[id].portals[key] !== portal) { this.buckets[id].portals[key] = portal; this.updateBuckets(id); } //returns a function to unregister the portal return function () { delete _this3.buckets[id].portals[key]; _this3.portalToBucketMap.delete(key); _this3.buckets[id].capacity += 1; if (_this3.buckets[id].capacity > _this3.scaleCapacityThreshold) { _this3.availableBuckets.add(id); } _this3.updateBuckets(id); }; } }, { key: "registerPortalRenderer", value: function registerPortalRenderer(updater) { var _this4 = this; if (!this.portalRendererUpdater) { updater(function () { return _this4.buckets; }); } this.portalRendererUpdater = updater; } }, { key: "unregisterPortalRenderer", value: function unregisterPortalRenderer() { this.portalRendererUpdater = null; } /** * Cleans up resources used by the PortalManager. This includes clearing all portals, * unregistering all buckets, and resetting internal state. */ }, { key: "destroy", value: function destroy() { var _this5 = this; // Iterate through each bucket and clear its portals and unset the updater function this.buckets.forEach(function (bucket, id) { bucket.portals = {}; // Clearing all portals from the bucket bucket.updater = null; // Unsetting the bucket's updater function _this5.availableBuckets.add(id); // Mark all buckets as available }); this.portalToBucketMap.clear(); this.portalRendererUpdater = null; this.availableBuckets = new Set(this.buckets.map(function (_, index) { return index; })); } }]); }();