From d7fd36d24e067a0e3fb209e406f20d5c5c77c49f Mon Sep 17 00:00:00 2001 From: cosmo Date: Sun, 26 Apr 2026 18:34:45 +0200 Subject: [PATCH] refactor: move recursion guard to actor instance and implement feature caching for ikonis virtual items --- scripts/ikonis-data.js | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/ikonis-data.js b/scripts/ikonis-data.js index a76ee4f..030f91f 100644 --- a/scripts/ikonis-data.js +++ b/scripts/ikonis-data.js @@ -127,36 +127,46 @@ export function patchIkonisLogic() { }; } -// Global recursion guard for feature injection -let _isInjecting = false; - /** * Internal helper to generate and cache virtual features for an actor. * Safe to call multiple times; uses a cache to keep it fast. */ function _injectIkonisFeatures(actor) { - if (!actor || _isInjecting) return []; + if (!actor || actor._isIkonisInjecting) return []; - // Use a short-lived cache for the current render cycle + // Initialize the cache if it doesn't exist if (!actor._ikonisCache) actor._ikonisCache = new Map(); const ikonisFeatures = []; - _isInjecting = true; + actor._isIkonisInjecting = true; try { const weapons = actor.items.filter(i => i.type === 'weapon'); + const allAugs = getAugments(); + for (const item of weapons) { const isEquipped = item.system.equipped; const installedIds = item.getFlag('dh-ikonis', 'installedAugments') || []; const bondedUuid = item.getFlag('dh-ikonis', 'bondedFeatureUuid'); const processFeature = (uuid, type) => { + if (!uuid) return; + + // Extract original feature ID from UUID to build our virtual ID + const featureId = uuid.split('.').pop(); + const virtualId = `ikonis-${item.id}-${featureId}`; + + // Check cache first + if (actor._ikonisCache.has(virtualId)) { + if (isEquipped) ikonisFeatures.push(actor._ikonisCache.get(virtualId)); + return; + } + + // Resolve original and clone const feature = _featureCache.get(uuid) || fromUuidSync(uuid); if (feature) { const featureClone = feature.clone({ parent: actor }, { keepId: true }); featureClone.system.type = "ikonis"; - - const virtualId = `ikonis-${item.id}-${feature.id}`; Object.defineProperty(featureClone, "id", { value: virtualId, enumerable: true }); actor._ikonisCache.set(virtualId, featureClone); @@ -165,7 +175,6 @@ function _injectIkonisFeatures(actor) { }; if (bondedUuid) processFeature(bondedUuid, "bonded"); - const allAugs = getAugments(); for (const id of installedIds) { const aug = allAugs.find(a => String(a.id) === String(id)); if (aug?.featureUuid) processFeature(aug.featureUuid, "augment"); @@ -174,10 +183,10 @@ function _injectIkonisFeatures(actor) { } catch (err) { console.error("DH-Ikonis | Error during feature injection:", err); } finally { - _isInjecting = false; + actor._isIkonisInjecting = false; } - return Array.from(new Set(ikonisFeatures)); + return ikonisFeatures; } /** @@ -218,7 +227,9 @@ export function patchDhCharacter(DhCharacter) { const originalGetEmbedded = Actor.prototype.getEmbeddedDocument; Actor.prototype.getEmbeddedDocument = function(embeddedName, id, options) { if (embeddedName === "Item" && typeof id === "string" && id.startsWith("ikonis-")) { - if (!this._ikonisCache?.has(id)) _injectIkonisFeatures(this); + if (!this._ikonisCache?.has(id) && !this._isIkonisInjecting) { + _injectIkonisFeatures(this); + } return this._ikonisCache?.get(id); } return originalGetEmbedded.call(this, embeddedName, id, options);