feat: add threshold support and tagmap support to variable system
Introduce a `threshold` column to tag modifiers in CSV declarations, allowing modifiers to activate only when a tag's count meets a minimum requirement. Implement support for "tagmap" variables (e.g., `#warrior:1;#druid:2`) which allow for complex, multi-tag state tracking. These variables can receive specialized tagmap modifications rather than simple numeric additions.
This commit is contained in:
@@ -5,11 +5,16 @@
|
||||
* Runs client-side (in the sender's tab, via command-dispatcher).
|
||||
* Uses a base/mod separation:
|
||||
* - baseValues: what /set writes (or declaration evaluation produces)
|
||||
* - activeMods: per-target list of {tag, value, source} from tag activations
|
||||
* - sourceActivations: per-source list of {tag, target, value} for deactivation
|
||||
* - numericMods: per-target list of {tag, value, source} from tag activations
|
||||
* - tagMapMods: per-target list of {tag, value, source} for tagmap targets
|
||||
* - sourceActivations: per-source list of {tag, target, value, threshold, kind}
|
||||
* for deactivation
|
||||
*
|
||||
* Combined value = base + sum(activeMods). Tag values (starting with #)
|
||||
* are not numeric and don't receive mods.
|
||||
* Variables have one of two types:
|
||||
* - numeric: base + sum(numericMods)
|
||||
* - tagmap (#warrior:1;#druid:2): the tagmap is used for threshold-gated
|
||||
* modifier activation; tagmap variables do not receive numeric mods but
|
||||
* can receive tagmap mods (adding/subtracting to specific tag counts).
|
||||
*
|
||||
* All functions that resolve variable values accept an explicit `fallback`
|
||||
* (the stream VariableStore) rather than relying on mutable module state.
|
||||
@@ -32,9 +37,54 @@ export type VariableStore = Record<string, string>;
|
||||
|
||||
/** A single modifier applied to a target variable. */
|
||||
export interface ActiveMod {
|
||||
tag: string; // "#warrior"
|
||||
value: number; // evaluated modifier expression result
|
||||
source: string; // "$class" — which variable activated this tag
|
||||
tag: string; // "#warrior"
|
||||
value: number; // evaluated modifier expression result
|
||||
source: string; // "$class" — which variable activated this tag
|
||||
threshold: number; // the threshold that triggered this activation
|
||||
kind: "numeric" | "tagmap";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tagmap helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Tagmap serialization format: "#warrior:1;#druid:2" */
|
||||
const TAGMAP_RE = /^#([a-zA-Z_][a-zA-Z0-9_]*):(\d+)$/;
|
||||
|
||||
/** Parse a tagmap string. Returns null if the value is not a valid tagmap. */
|
||||
function parseTagMap(value: string | undefined): Record<string, number> | null {
|
||||
if (!value) return null;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.startsWith("#")) return null;
|
||||
|
||||
const parts = trimmed.split(";");
|
||||
const map: Record<string, number> = {};
|
||||
|
||||
for (const part of parts) {
|
||||
const m = TAGMAP_RE.exec(part.trim());
|
||||
if (!m) return null; // invalid entry
|
||||
const tag = "#" + m[1];
|
||||
const count = parseInt(m[2], 10);
|
||||
if (count <= 0) continue; // skip zero/negative counts on parse
|
||||
map[tag] = (map[tag] ?? 0) + count;
|
||||
}
|
||||
|
||||
return Object.keys(map).length > 0 ? map : null;
|
||||
}
|
||||
|
||||
/** Serialize a tagmap back to string. Returns empty string if map is empty. */
|
||||
function tagMapToString(map: Record<string, number>): string {
|
||||
const entries = Object.entries(map).filter(([, c]) => c > 0);
|
||||
if (entries.length === 0) return "";
|
||||
return entries.map(([tag, count]) => `${tag}:${count}`).join(";");
|
||||
}
|
||||
|
||||
/** Check if a value is a tagmap string. */
|
||||
function isTagMapValue(value: string | undefined): boolean {
|
||||
if (!value) return false;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.startsWith("#")) return false;
|
||||
return parseTagMap(trimmed) !== null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -47,17 +97,26 @@ let depGraph: Map<string, Set<string>> | null = null;
|
||||
/** Reverse: $declaredVar → its expression */
|
||||
let declExprs: Map<string, string> | null = null;
|
||||
|
||||
/** Tag modifiers from declare blocks: #tag → [{target, expression}] */
|
||||
let tagModMap: Map<string, Array<{ target: string; expression: string }>> | null = null;
|
||||
/** Tag modifiers from declare blocks: #tag → [{target, expression, threshold}] */
|
||||
let tagModMap: Map<string, Array<{ target: string; expression: string; threshold: number }>> | null = null;
|
||||
|
||||
/** Base values set by /set or declaration evaluation */
|
||||
const baseValues = new Map<string, string>();
|
||||
|
||||
/** Active mods per target: $target → [{tag, value, source}] */
|
||||
const activeMods = new Map<string, ActiveMod[]>();
|
||||
/** Numeric mods per target: $target → [{tag, value, source, ...}] */
|
||||
const numericMods = new Map<string, ActiveMod[]>();
|
||||
|
||||
/** Activations per source: $source → [{tag, target, value}] */
|
||||
const sourceActivations = new Map<string, Array<{ tag: string; target: string; value: number }>>();
|
||||
/** Tagmap mods per target: $target → [{tag, value, source, ...}] */
|
||||
const tagMapMods = new Map<string, ActiveMod[]>();
|
||||
|
||||
/** Activations per source: $source → [{tag, target, value, threshold, kind}] */
|
||||
const sourceActivations = new Map<string, Array<{
|
||||
tag: string;
|
||||
target: string;
|
||||
value: number;
|
||||
threshold: number;
|
||||
kind: "numeric" | "tagmap";
|
||||
}>>();
|
||||
|
||||
/** Set of $vars currently being re-evaluated (cycle guard) */
|
||||
const inFlight = new Set<string>();
|
||||
@@ -77,7 +136,8 @@ export function initReactivity(state: VarReactivityState): void {
|
||||
declExprs = new Map();
|
||||
tagModMap = new Map();
|
||||
baseValues.clear();
|
||||
activeMods.clear();
|
||||
numericMods.clear();
|
||||
tagMapMods.clear();
|
||||
sourceActivations.clear();
|
||||
|
||||
// Index tag modifiers
|
||||
@@ -87,7 +147,7 @@ export function initReactivity(state: VarReactivityState): void {
|
||||
list = [];
|
||||
tagModMap.set(tm.tag, list);
|
||||
}
|
||||
list.push({ target: tm.target, expression: tm.expression });
|
||||
list.push({ target: tm.target, expression: tm.expression, threshold: tm.threshold });
|
||||
}
|
||||
|
||||
// Index declarations and build dependency graph
|
||||
@@ -120,17 +180,30 @@ export function extractDependencies(expr: string): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the combined value of a variable (base + sum of active mods).
|
||||
* Get the combined value of a variable.
|
||||
* - For tagmap variables: returns the serialized tagmap (base + tagmap mods).
|
||||
* - For numeric variables: returns base + sum(numericMods).
|
||||
* Falls back to the provided stream store for variables not tracked locally.
|
||||
*/
|
||||
export function getCombined(key: string, fallback?: VariableStore): string {
|
||||
const base = baseValues.get(key);
|
||||
if (base !== undefined && isTagValue(base)) {
|
||||
return base; // tag values don't receive numeric mods
|
||||
|
||||
if (base !== undefined && isTagMapValue(base)) {
|
||||
// Tagmap variable: apply tagmap mods, then serialize
|
||||
const baseMap = parseTagMap(base);
|
||||
const mods = tagMapMods.get(key) ?? [];
|
||||
if (baseMap && mods.length === 0) return base;
|
||||
const resultMap: Record<string, number> = { ...(baseMap ?? {}) };
|
||||
for (const m of mods) {
|
||||
resultMap[m.tag] = (resultMap[m.tag] ?? 0) + m.value;
|
||||
if (resultMap[m.tag] <= 0) delete resultMap[m.tag];
|
||||
}
|
||||
const serialized = tagMapToString(resultMap);
|
||||
return serialized || "0";
|
||||
}
|
||||
|
||||
const baseNum = base !== undefined ? parseFloat(base) : NaN;
|
||||
const mods = activeMods.get(key) ?? [];
|
||||
const mods = numericMods.get(key) ?? [];
|
||||
const modSum = mods.reduce((sum, m) => sum + m.value, 0);
|
||||
|
||||
if (!isNaN(baseNum)) {
|
||||
@@ -143,15 +216,35 @@ export function getCombined(key: string, fallback?: VariableStore): string {
|
||||
const fb = fallback?.[key];
|
||||
if (fb !== undefined) return fb;
|
||||
|
||||
// No base, but has mods
|
||||
// No base, but has numeric mods
|
||||
if (mods.length > 0) return String(modSum);
|
||||
|
||||
return "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective tagmap for a variable (base + tagmap mods).
|
||||
* Returns null if the variable is not a tagmap variable.
|
||||
*/
|
||||
export function getTagMap(key: string): Record<string, number> | null {
|
||||
const base = baseValues.get(key);
|
||||
if (base === undefined || !isTagMapValue(base)) return null;
|
||||
const baseMap = parseTagMap(base);
|
||||
if (!baseMap) return null;
|
||||
const mods = tagMapMods.get(key) ?? [];
|
||||
const result: Record<string, number> = { ...baseMap };
|
||||
for (const m of mods) {
|
||||
result[m.tag] = (result[m.tag] ?? 0) + m.value;
|
||||
if (result[m.tag] <= 0) delete result[m.tag];
|
||||
}
|
||||
return Object.keys(result).length > 0 ? result : null;
|
||||
}
|
||||
|
||||
/** Get the active mods for a variable (for UI hover display). */
|
||||
export function getMods(key: string): ActiveMod[] {
|
||||
return activeMods.get(key) ?? [];
|
||||
const nums = numericMods.get(key) ?? [];
|
||||
const tags = tagMapMods.get(key) ?? [];
|
||||
return [...nums, ...tags];
|
||||
}
|
||||
|
||||
/** Get the declaration expression for a variable, if any. */
|
||||
@@ -168,7 +261,7 @@ export function setBase(key: string, value: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild local reactivity state (baseValues, activeMods, sourceActivations)
|
||||
* Rebuild local reactivity state (baseValues, mods, sourceActivations)
|
||||
* from the hydrated stream variable store. Must be called after replayReducers
|
||||
* so that tag activations are correctly tracked for subsequent cascade
|
||||
* computations.
|
||||
@@ -181,9 +274,12 @@ export function rebuildReactivityFromStore(variables: VariableStore): void {
|
||||
baseValues.set(key, value);
|
||||
}
|
||||
|
||||
const tag = isTagValue(value);
|
||||
if (tag && !sourceActivations.has(key)) {
|
||||
activateTagFromSource(key, tag, variables);
|
||||
const tagMap = isTagMapValue(value) ? parseTagMap(value) : null;
|
||||
if (tagMap && !sourceActivations.has(key)) {
|
||||
// Activate all tags in the tagmap
|
||||
const added = applyTagMapActivations(key, {}, tagMap, variables);
|
||||
// We don't return the added entries here — they'll be applied
|
||||
// to the store separately
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,26 +305,12 @@ export function computeCascade(
|
||||
|
||||
// ---- Tag activation/deactivation ----
|
||||
const newValue = getCombined(changedVar, currentVars);
|
||||
const newTag = isTagValue(newValue);
|
||||
const oldTag = isTagValue(oldValue);
|
||||
const oldTagMap = isTagMapValue(oldValue) ? parseTagMap(oldValue) : {};
|
||||
const newTagMap = isTagMapValue(newValue) ? parseTagMap(newValue) : {};
|
||||
|
||||
if (newTag !== oldTag) {
|
||||
// Deactivate old tag
|
||||
if (oldTag) {
|
||||
const removed = deactivateTagFromSource(changedVar, oldTag, currentVars);
|
||||
for (const r of removed) {
|
||||
results.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
// Activate new tag
|
||||
if (newTag) {
|
||||
const added = activateTagFromSource(changedVar, newTag, currentVars);
|
||||
for (const r of added) {
|
||||
results.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Diff tagmaps and apply changes
|
||||
const tagResults = applyTagMapActivations(changedVar, oldTagMap ?? {}, newTagMap ?? {}, currentVars);
|
||||
results.push(...tagResults);
|
||||
|
||||
// ---- Declaration re-evaluation ----
|
||||
const reevaluated = reevaluateDependents(changedVar, currentVars);
|
||||
@@ -270,10 +352,10 @@ export function computeInitialValues(
|
||||
const rawValue = String(result.value);
|
||||
baseValues.set(key, rawValue);
|
||||
|
||||
// Check if this is a tag value — if so, activate it
|
||||
const tag = isTagValue(rawValue);
|
||||
if (tag) {
|
||||
const added = activateTagFromSource(key, tag, currentVars);
|
||||
// Check if this is a tagmap value — if so, activate matching modifiers
|
||||
const tagMap = isTagMapValue(rawValue) ? parseTagMap(rawValue) : null;
|
||||
if (tagMap) {
|
||||
const added = applyTagMapActivations(key, {}, tagMap, currentVars);
|
||||
for (const r of added) {
|
||||
if (!results.some((x) => x.key === r.key)) {
|
||||
results.push(r);
|
||||
@@ -295,112 +377,148 @@ export function computeInitialValues(
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tag activation / deactivation (source-based)
|
||||
// Tag activation / deactivation (threshold-based)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isTagValue(value: string | undefined): string | null {
|
||||
if (!value) return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.startsWith("#") ? trimmed : null;
|
||||
}
|
||||
|
||||
/** Activate a tag from a source variable. Evaluates modifiers once. */
|
||||
function activateTagFromSource(
|
||||
/**
|
||||
* Apply tagmap changes for a source variable. Compares old and new tagmaps,
|
||||
* activating/deactivating modifiers whose threshold crossing state changed.
|
||||
*/
|
||||
function applyTagMapActivations(
|
||||
source: string,
|
||||
tag: string,
|
||||
oldTagMap: Record<string, number>,
|
||||
newTagMap: Record<string, number>,
|
||||
fallback: VariableStore,
|
||||
): Array<{ key: string; value: string }> {
|
||||
if (!tagModMap) return [];
|
||||
|
||||
const mods = tagModMap.get(tag);
|
||||
if (!mods) return [];
|
||||
|
||||
const results: Array<{ key: string; value: string }> = [];
|
||||
const sourceEntries: Array<{ tag: string; target: string; value: number }> = [];
|
||||
const allTags = new Set([...Object.keys(oldTagMap), ...Object.keys(newTagMap)]);
|
||||
|
||||
for (const mod of mods) {
|
||||
try {
|
||||
// Snapshot the target's current combined value as its base before
|
||||
// adding the mod, so deactivation can restore it correctly.
|
||||
if (!baseValues.has(mod.target)) {
|
||||
baseValues.set(mod.target, getCombined(mod.target, fallback));
|
||||
for (const tag of allTags) {
|
||||
const mods = tagModMap.get(tag);
|
||||
if (!mods || mods.length === 0) continue;
|
||||
|
||||
const oldCount = oldTagMap[tag] ?? 0;
|
||||
const newCount = newTagMap[tag] ?? 0;
|
||||
|
||||
for (let modIdx = 0; modIdx < mods.length; modIdx++) {
|
||||
const mod = mods[modIdx];
|
||||
const wasActive = oldCount >= mod.threshold;
|
||||
const isActive = newCount >= mod.threshold;
|
||||
|
||||
if (!wasActive && isActive) {
|
||||
// Activate this modifier
|
||||
try {
|
||||
// Snapshot target's current value as base if needed
|
||||
if (!baseValues.has(mod.target)) {
|
||||
baseValues.set(mod.target, getCombined(mod.target, fallback));
|
||||
}
|
||||
|
||||
const evalResult = evaluateExpression(mod.expression, {
|
||||
lookup: (name: string) => {
|
||||
const k = "$" + name;
|
||||
return getCombined(k, fallback);
|
||||
},
|
||||
});
|
||||
|
||||
const value = evalResult.value;
|
||||
const targetIsTagMap = isTagMapValue(baseValues.get(mod.target));
|
||||
|
||||
if (targetIsTagMap) {
|
||||
// Apply as tagmap mod to the target's tag count
|
||||
const entry: ActiveMod = { tag, value, source, threshold: mod.threshold, kind: "tagmap" };
|
||||
let targetMods = tagMapMods.get(mod.target);
|
||||
if (!targetMods) {
|
||||
targetMods = [];
|
||||
tagMapMods.set(mod.target, targetMods);
|
||||
}
|
||||
targetMods.push(entry);
|
||||
} else {
|
||||
// Apply as numeric mod
|
||||
const entry: ActiveMod = { tag, value, source, threshold: mod.threshold, kind: "numeric" };
|
||||
let targetMods = numericMods.get(mod.target);
|
||||
if (!targetMods) {
|
||||
targetMods = [];
|
||||
numericMods.set(mod.target, targetMods);
|
||||
}
|
||||
targetMods.push(entry);
|
||||
}
|
||||
|
||||
// Track in source activations for deactivation
|
||||
let sourceEntries = sourceActivations.get(source);
|
||||
if (!sourceEntries) {
|
||||
sourceEntries = [];
|
||||
sourceActivations.set(source, sourceEntries);
|
||||
}
|
||||
sourceEntries.push({
|
||||
tag,
|
||||
target: mod.target,
|
||||
value,
|
||||
threshold: mod.threshold,
|
||||
kind: targetIsTagMap ? "tagmap" : "numeric",
|
||||
});
|
||||
|
||||
// Emit new combined value
|
||||
const combined = getCombined(mod.target, fallback);
|
||||
const existing = results.findIndex((r) => r.key === mod.target);
|
||||
if (existing >= 0) {
|
||||
results[existing] = { key: mod.target, value: combined };
|
||||
} else {
|
||||
results.push({ key: mod.target, value: combined });
|
||||
}
|
||||
} catch {
|
||||
// skip failed modifier
|
||||
}
|
||||
} else if (wasActive && !isActive) {
|
||||
// Deactivate this modifier
|
||||
const sourceEntries = sourceActivations.get(source);
|
||||
if (!sourceEntries) continue;
|
||||
|
||||
const idx = sourceEntries.findIndex(
|
||||
(e) => e.tag === tag && e.target === mod.target && e.threshold === mod.threshold,
|
||||
);
|
||||
if (idx < 0) continue;
|
||||
|
||||
const entry = sourceEntries[idx];
|
||||
sourceEntries.splice(idx, 1);
|
||||
|
||||
// Remove from the appropriate mod list
|
||||
if (entry.kind === "tagmap") {
|
||||
const targetMods = tagMapMods.get(entry.target);
|
||||
if (targetMods) {
|
||||
const modIdx = targetMods.findIndex(
|
||||
(m) => m.tag === tag && m.source === source && m.threshold === mod.threshold,
|
||||
);
|
||||
if (modIdx >= 0) targetMods.splice(modIdx, 1);
|
||||
if (targetMods.length === 0) tagMapMods.delete(entry.target);
|
||||
}
|
||||
} else {
|
||||
const targetMods = numericMods.get(entry.target);
|
||||
if (targetMods) {
|
||||
const modIdx = targetMods.findIndex(
|
||||
(m) => m.tag === tag && m.source === source && m.threshold === mod.threshold,
|
||||
);
|
||||
if (modIdx >= 0) targetMods.splice(modIdx, 1);
|
||||
if (targetMods.length === 0) numericMods.delete(entry.target);
|
||||
}
|
||||
}
|
||||
|
||||
// Emit updated combined value
|
||||
const combined = getCombined(entry.target, fallback);
|
||||
const existing = results.findIndex((r) => r.key === entry.target);
|
||||
if (existing >= 0) {
|
||||
results[existing] = { key: entry.target, value: combined };
|
||||
} else {
|
||||
results.push({ key: entry.target, value: combined });
|
||||
}
|
||||
}
|
||||
|
||||
const result = evaluateExpression(mod.expression, {
|
||||
lookup: (name: string) => {
|
||||
const k = "$" + name;
|
||||
return getCombined(k, fallback);
|
||||
},
|
||||
});
|
||||
|
||||
const entry: ActiveMod = { tag, value: result.value, source };
|
||||
|
||||
let targetMods = activeMods.get(mod.target);
|
||||
if (!targetMods) {
|
||||
targetMods = [];
|
||||
activeMods.set(mod.target, targetMods);
|
||||
}
|
||||
targetMods.push(entry);
|
||||
|
||||
sourceEntries.push({ tag, target: mod.target, value: result.value });
|
||||
|
||||
// Emit new combined value for the target
|
||||
const combined = getCombined(mod.target, fallback);
|
||||
const existing = results.findIndex((r) => r.key === mod.target);
|
||||
if (existing >= 0) {
|
||||
results[existing] = { key: mod.target, value: combined };
|
||||
} else {
|
||||
results.push({ key: mod.target, value: combined });
|
||||
}
|
||||
} catch {
|
||||
// skip failed modifier
|
||||
}
|
||||
}
|
||||
|
||||
sourceActivations.set(source, sourceEntries);
|
||||
return results;
|
||||
}
|
||||
|
||||
/** Deactivate a tag from a source variable. Removes exactly the mods it created. */
|
||||
function deactivateTagFromSource(
|
||||
source: string,
|
||||
_tag: string,
|
||||
fallback: VariableStore,
|
||||
): Array<{ key: string; value: string }> {
|
||||
const entries = sourceActivations.get(source);
|
||||
if (!entries) return [];
|
||||
|
||||
const results: Array<{ key: string; value: string }> = [];
|
||||
const affectedTargets = new Set<string>();
|
||||
|
||||
// Remove mods from activeMods
|
||||
for (const entry of entries) {
|
||||
const targetMods = activeMods.get(entry.target);
|
||||
if (!targetMods) continue;
|
||||
|
||||
const idx = targetMods.findIndex(
|
||||
(m) => m.tag === entry.tag && m.source === source,
|
||||
);
|
||||
if (idx >= 0) {
|
||||
targetMods.splice(idx, 1);
|
||||
affectedTargets.add(entry.target);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up empty arrays
|
||||
for (const target of affectedTargets) {
|
||||
const mods = activeMods.get(target);
|
||||
if (mods && mods.length === 0) {
|
||||
activeMods.delete(target);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove source activations
|
||||
sourceActivations.delete(source);
|
||||
|
||||
// Emit new combined values for affected targets
|
||||
for (const target of affectedTargets) {
|
||||
results.push({ key: target, value: getCombined(target, fallback) });
|
||||
// Clean up empty sourceActivations
|
||||
if (sourceActivations.get(source)?.length === 0) {
|
||||
sourceActivations.delete(source);
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -455,29 +573,16 @@ function reevaluateDependents(
|
||||
|
||||
const rawValue = String(result.value);
|
||||
|
||||
// Check for tag transition on this declared variable
|
||||
// Check for tagmap transition on this declared variable
|
||||
const oldCombined = getCombined(key, fallback);
|
||||
const oldTag = isTagValue(oldCombined);
|
||||
const newTag = isTagValue(rawValue);
|
||||
const oldTagMap = isTagMapValue(oldCombined) ? parseTagMap(oldCombined) : {};
|
||||
const newTagMap = isTagMapValue(rawValue) ? parseTagMap(rawValue) : {};
|
||||
|
||||
if (oldTag !== newTag) {
|
||||
// Deactivate old tag
|
||||
if (oldTag) {
|
||||
const removed = deactivateTagFromSource(key, oldTag, fallback);
|
||||
for (const r of removed) {
|
||||
if (!results.some((x) => x.key === r.key)) {
|
||||
results.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Activate new tag
|
||||
if (newTag) {
|
||||
baseValues.set(key, rawValue);
|
||||
const added = activateTagFromSource(key, newTag, fallback);
|
||||
for (const r of added) {
|
||||
if (!results.some((x) => x.key === r.key)) {
|
||||
results.push(r);
|
||||
}
|
||||
if (JSON.stringify(oldTagMap) !== JSON.stringify(newTagMap)) {
|
||||
const tagResults = applyTagMapActivations(key, oldTagMap ?? {}, newTagMap ?? {}, fallback);
|
||||
for (const r of tagResults) {
|
||||
if (!results.some((x) => x.key === r.key)) {
|
||||
results.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user