refactor: move recursion guard to actor instance and implement feature caching for ikonis virtual items

This commit is contained in:
CPTN Cosmo 2026-04-26 18:34:45 +02:00
parent fec1a6c1ef
commit d7fd36d24e

View file

@ -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. * Internal helper to generate and cache virtual features for an actor.
* Safe to call multiple times; uses a cache to keep it fast. * Safe to call multiple times; uses a cache to keep it fast.
*/ */
function _injectIkonisFeatures(actor) { 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(); if (!actor._ikonisCache) actor._ikonisCache = new Map();
const ikonisFeatures = []; const ikonisFeatures = [];
_isInjecting = true; actor._isIkonisInjecting = true;
try { try {
const weapons = actor.items.filter(i => i.type === 'weapon'); const weapons = actor.items.filter(i => i.type === 'weapon');
const allAugs = getAugments();
for (const item of weapons) { for (const item of weapons) {
const isEquipped = item.system.equipped; const isEquipped = item.system.equipped;
const installedIds = item.getFlag('dh-ikonis', 'installedAugments') || []; const installedIds = item.getFlag('dh-ikonis', 'installedAugments') || [];
const bondedUuid = item.getFlag('dh-ikonis', 'bondedFeatureUuid'); const bondedUuid = item.getFlag('dh-ikonis', 'bondedFeatureUuid');
const processFeature = (uuid, type) => { 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); const feature = _featureCache.get(uuid) || fromUuidSync(uuid);
if (feature) { if (feature) {
const featureClone = feature.clone({ parent: actor }, { keepId: true }); const featureClone = feature.clone({ parent: actor }, { keepId: true });
featureClone.system.type = "ikonis"; featureClone.system.type = "ikonis";
const virtualId = `ikonis-${item.id}-${feature.id}`;
Object.defineProperty(featureClone, "id", { value: virtualId, enumerable: true }); Object.defineProperty(featureClone, "id", { value: virtualId, enumerable: true });
actor._ikonisCache.set(virtualId, featureClone); actor._ikonisCache.set(virtualId, featureClone);
@ -165,7 +175,6 @@ function _injectIkonisFeatures(actor) {
}; };
if (bondedUuid) processFeature(bondedUuid, "bonded"); if (bondedUuid) processFeature(bondedUuid, "bonded");
const allAugs = getAugments();
for (const id of installedIds) { for (const id of installedIds) {
const aug = allAugs.find(a => String(a.id) === String(id)); const aug = allAugs.find(a => String(a.id) === String(id));
if (aug?.featureUuid) processFeature(aug.featureUuid, "augment"); if (aug?.featureUuid) processFeature(aug.featureUuid, "augment");
@ -174,10 +183,10 @@ function _injectIkonisFeatures(actor) {
} catch (err) { } catch (err) {
console.error("DH-Ikonis | Error during feature injection:", err); console.error("DH-Ikonis | Error during feature injection:", err);
} finally { } 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; const originalGetEmbedded = Actor.prototype.getEmbeddedDocument;
Actor.prototype.getEmbeddedDocument = function(embeddedName, id, options) { Actor.prototype.getEmbeddedDocument = function(embeddedName, id, options) {
if (embeddedName === "Item" && typeof id === "string" && id.startsWith("ikonis-")) { 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 this._ikonisCache?.get(id);
} }
return originalGetEmbedded.call(this, embeddedName, id, options); return originalGetEmbedded.call(this, embeddedName, id, options);