From 1997f706994461104b9a447d155d1aa7245df605 Mon Sep 17 00:00:00 2001
From: thepaperpilot <thepaperpilot@gmail.com>
Date: Tue, 26 Jul 2022 15:25:31 -0500
Subject: [PATCH] Made modifiers lazy

---
 src/data/common.tsx    | 106 +++++++++++----------
 src/game/modifiers.tsx | 206 +++++++++++++++++++++++++----------------
 2 files changed, 184 insertions(+), 128 deletions(-)

diff --git a/src/data/common.tsx b/src/data/common.tsx
index f392741..b421460 100644
--- a/src/data/common.tsx
+++ b/src/data/common.tsx
@@ -20,6 +20,7 @@ import type {
     ProcessedComputable
 } from "util/computed";
 import { convertComputable, processComputable } from "util/computed";
+import { createLazyProxy } from "util/proxies";
 import { renderJSX } from "util/vue";
 import type { Ref } from "vue";
 import { computed, unref } from "vue";
@@ -33,7 +34,7 @@ export interface ResetButtonOptions extends ClickableOptions {
     tree: GenericTree;
     /** The specific tree node associated with this reset button */
     treeNode: GenericTreeNode;
-    /** 
+    /**
      * Text to display on low conversion amounts, describing what "resetting" is in this context.
      * Defaults to "Reset for ".
      */
@@ -253,62 +254,67 @@ export interface Section {
 /**
  * Takes an array of modifier "sections", and creates a JSXFunction that can render all those sections, and allow each section to be collapsed.
  * Also returns a list of persistent refs that are used to control which sections are currently collapsed.
+ * @param sectionsFunc A function that returns the sections to display.
  */
 export function createCollapsibleModifierSections(
-    sections: Section[]
+    sectionsFunc: () => Section[]
 ): [JSXFunction, Persistent<boolean>[]] {
-    const processedBase = sections.map(s => convertComputable(s.base));
-    const processedBaseText = sections.map(s => convertComputable(s.baseText));
-    const processedVisible = sections.map(s => convertComputable(s.visible));
-    const collapsed = sections.map(() => persistent<boolean>(false));
-    const jsxFunc = jsx(() => {
-        const sectionJSX = sections.map((s, i) => {
-            if (unref(processedVisible[i]) === false) return null;
-            const header = (
-                <h3
-                    onClick={() => (collapsed[i].value = !collapsed[i].value)}
-                    style="cursor: pointer"
-                >
-                    <span class={"modifier-toggle" + (unref(collapsed[i]) ? " collapsed" : "")}>
-                        ▼
-                    </span>
-                    {s.title}
-                    {s.subtitle ? <span class="subtitle"> ({s.subtitle})</span> : null}
-                </h3>
-            );
+    return createLazyProxy(() => {
+        const sections = sectionsFunc();
 
-            const modifiers = unref(collapsed[i]) ? null : (
-                <>
-                    <div class="modifier-container">
-                        <span class="modifier-amount">
-                            {format(unref(processedBase[i]) ?? 1)}
+        const processedBase = sections.map(s => convertComputable(s.base));
+        const processedBaseText = sections.map(s => convertComputable(s.baseText));
+        const processedVisible = sections.map(s => convertComputable(s.visible));
+        const collapsed = sections.map(() => persistent<boolean>(false));
+        const jsxFunc = jsx(() => {
+            const sectionJSX = sections.map((s, i) => {
+                if (unref(processedVisible[i]) === false) return null;
+                const header = (
+                    <h3
+                        onClick={() => (collapsed[i].value = !collapsed[i].value)}
+                        style="cursor: pointer"
+                    >
+                        <span class={"modifier-toggle" + (unref(collapsed[i]) ? " collapsed" : "")}>
+                            ▼
+                        </span>
+                        {s.title}
+                        {s.subtitle ? <span class="subtitle"> ({s.subtitle})</span> : null}
+                    </h3>
+                );
+
+                const modifiers = unref(collapsed[i]) ? null : (
+                    <>
+                        <div class="modifier-container">
+                            <span class="modifier-amount">
+                                {format(unref(processedBase[i]) ?? 1)}
+                                {s.unit}
+                            </span>
+                            <span class="modifier-description">
+                                {renderJSX(unref(processedBaseText[i]) ?? "Base")}
+                            </span>
+                        </div>
+                        {renderJSX(unref(s.modifier.description))}
+                    </>
+                );
+
+                return (
+                    <>
+                        {i === 0 ? null : <br />}
+                        <div>
+                            {header}
+                            <br />
+                            {modifiers}
+                            <hr />
+                            Total: {format(s.modifier.apply(unref(processedBase[i]) ?? 1))}
                             {s.unit}
-                        </span>
-                        <span class="modifier-description">
-                            {renderJSX(unref(processedBaseText[i]) ?? "Base")}
-                        </span>
-                    </div>
-                    {renderJSX(unref(s.modifier.description))}
-                </>
-            );
-
-            return (
-                <>
-                    {i === 0 ? null : <br />}
-                    <div>
-                        {header}
-                        <br />
-                        {modifiers}
-                        <hr />
-                        Total: {format(s.modifier.apply(unref(processedBase[i]) ?? 1))}
-                        {s.unit}
-                    </div>
-                </>
-            );
+                        </div>
+                    </>
+                );
+            });
+            return <>{sectionJSX}</>;
         });
-        return <>{sectionJSX}</>;
+        return [jsxFunc, collapsed];
     });
-    return [jsxFunc, collapsed];
 }
 
 /**
diff --git a/src/game/modifiers.tsx b/src/game/modifiers.tsx
index 6f090c7..b476027 100644
--- a/src/game/modifiers.tsx
+++ b/src/game/modifiers.tsx
@@ -6,6 +6,7 @@ import Decimal, { format } from "util/bignum";
 import type { WithRequired } from "util/common";
 import type { Computable, ProcessedComputable } from "util/computed";
 import { convertComputable } from "util/computed";
+import { createLazyProxy } from "util/proxies";
 import { renderJSX } from "util/vue";
 import { computed, unref } from "vue";
 
@@ -44,112 +45,161 @@ export type ModifierFromOptionalParams<T, S> = T extends undefined
     ? Omit<WithRequired<Modifier, "revert" | "description">, "enabled">
     : WithRequired<Modifier, "revert" | "enabled" | "description">;
 
+/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
+export interface AdditiveModifierOptions<
+    T extends Computable<CoercableComponent> | undefined,
+    S extends Computable<boolean> | undefined
+> {
+    /** The amount to add to the input value. */
+    addend: Computable<DecimalSource>;
+    /** Description of what this modifier is doing. */
+    description?: T;
+    /** A computable that will be processed and passed directly into the returned modifier. */
+    enabled?: S;
+}
+
 /**
  * Create a modifier that adds some value to the input value.
- * @param addend The amount to add to the input value.
- * @param description Description of what this modifier is doing.
- * @param enabled A computable that will be processed and passed directly into the returned modifier.
+ * @param optionsFunc Additive modifier options.
  */
 export function createAdditiveModifier<
     T extends Computable<CoercableComponent> | undefined,
     S extends Computable<boolean> | undefined,
     R = ModifierFromOptionalParams<T, S>
->(addend: Computable<DecimalSource>, description?: T, enabled?: S): R {
-    const processedAddend = convertComputable(addend);
-    const processedDescription = convertComputable(description);
-    const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
-    return {
-        apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
-        revert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)),
-        enabled: processedEnabled,
-        description:
-            description == null
-                ? undefined
-                : jsx(() => (
-                      <div class="modifier-container">
-                          <span class="modifier-amount">
-                              {Decimal.gte(unref(processedAddend), 0) ? "+" : ""}
-                              {format(unref(processedAddend))}
-                          </span>
-                          {unref(processedDescription) ? (
-                              <span class="modifier-description">
-                                  {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
-                                  {renderJSX(unref(processedDescription)!)}
+>(optionsFunc: () => AdditiveModifierOptions<T, S>): R {
+    return createLazyProxy(() => {
+        const { addend, description, enabled } = optionsFunc();
+
+        const processedAddend = convertComputable(addend);
+        const processedDescription = convertComputable(description);
+        const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
+        return {
+            apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
+            revert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)),
+            enabled: processedEnabled,
+            description:
+                description == null
+                    ? undefined
+                    : jsx(() => (
+                          <div class="modifier-container">
+                              <span class="modifier-amount">
+                                  {Decimal.gte(unref(processedAddend), 0) ? "+" : ""}
+                                  {format(unref(processedAddend))}
                               </span>
-                          ) : null}
-                      </div>
-                  ))
-    } as unknown as R;
+                              {unref(processedDescription) ? (
+                                  <span class="modifier-description">
+                                      {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
+                                      {renderJSX(unref(processedDescription)!)}
+                                  </span>
+                              ) : null}
+                          </div>
+                      ))
+        };
+    }) as unknown as R;
+}
+
+/** An object that configures an multiplicative modifier via {@link createMultiplicativeModifier}. */
+export interface MultiplicativeModifierOptions<
+    T extends Computable<CoercableComponent> | undefined,
+    S extends Computable<boolean> | undefined
+> {
+    /** The amount to multiply the input value by. */
+    multiplier: Computable<DecimalSource>;
+    /** Description of what this modifier is doing. */
+    description?: T;
+    /** A computable that will be processed and passed directly into the returned modifier. */
+    enabled?: S;
 }
 
 /**
  * Create a modifier that multiplies the input value by some value.
- * @param multiplier The value to multiply the input value by.
- * @param description Description of what this modifier is doing.
- * @param enabled A computable that will be processed and passed directly into the returned modifier.
+ * @param optionsFunc Multiplicative modifier options.
  */
 export function createMultiplicativeModifier<
     T extends Computable<CoercableComponent> | undefined,
     S extends Computable<boolean> | undefined,
     R = ModifierFromOptionalParams<T, S>
->(multiplier: Computable<DecimalSource>, description?: T, enabled?: S): R {
-    const processedMultiplier = convertComputable(multiplier);
-    const processedDescription = convertComputable(description);
-    const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
-    return {
-        apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
-        revert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)),
-        enabled: processedEnabled,
-        description:
-            description == null
-                ? undefined
-                : jsx(() => (
-                      <div class="modifier-container">
-                          <span class="modifier-amount">x{format(unref(processedMultiplier))}</span>
-                          {unref(processedDescription) ? (
-                              <span class="modifier-description">
-                                  {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
-                                  {renderJSX(unref(processedDescription)!)}
+>(optionsFunc: () => MultiplicativeModifierOptions<T, S>): R {
+    return createLazyProxy(() => {
+        const { multiplier, description, enabled } = optionsFunc();
+
+        const processedMultiplier = convertComputable(multiplier);
+        const processedDescription = convertComputable(description);
+        const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
+        return {
+            apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
+            revert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)),
+            enabled: processedEnabled,
+            description:
+                description == null
+                    ? undefined
+                    : jsx(() => (
+                          <div class="modifier-container">
+                              <span class="modifier-amount">
+                                  x{format(unref(processedMultiplier))}
                               </span>
-                          ) : null}
-                      </div>
-                  ))
-    } as unknown as R;
+                              {unref(processedDescription) ? (
+                                  <span class="modifier-description">
+                                      {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
+                                      {renderJSX(unref(processedDescription)!)}
+                                  </span>
+                              ) : null}
+                          </div>
+                      ))
+        };
+    }) as unknown as R;
+}
+
+/** An object that configures an exponential modifier via {@link createExponentialModifier}. */
+export interface ExponentialModifierOptions<
+    T extends Computable<CoercableComponent> | undefined,
+    S extends Computable<boolean> | undefined
+> {
+    /** The amount to raise the input value to the power of. */
+    exponent: Computable<DecimalSource>;
+    /** Description of what this modifier is doing. */
+    description?: T;
+    /** A computable that will be processed and passed directly into the returned modifier. */
+    enabled?: S;
 }
 
 /**
  * Create a modifier that raises the input value to the power of some value.
- * @param exponent The value to raise the input value to the power of.
- * @param description Description of what this modifier is doing.
- * @param enabled A computable that will be processed and passed directly into the returned modifier.
+ * @param optionsFunc Exponential modifier options.
  */
 export function createExponentialModifier<
     T extends Computable<CoercableComponent> | undefined,
     S extends Computable<boolean> | undefined,
     R = ModifierFromOptionalParams<T, S>
->(exponent: Computable<DecimalSource>, description?: T, enabled?: S): R {
-    const processedExponent = convertComputable(exponent);
-    const processedDescription = convertComputable(description);
-    const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
-    return {
-        apply: (gain: DecimalSource) => Decimal.pow(gain, unref(processedExponent)),
-        revert: (gain: DecimalSource) => Decimal.root(gain, unref(processedExponent)),
-        enabled: processedEnabled,
-        description:
-            description == null
-                ? undefined
-                : jsx(() => (
-                      <div class="modifier-container">
-                          <span class="modifier-amount">^{format(unref(processedExponent))}</span>
-                          {unref(processedDescription) ? (
-                              <span class="modifier-description">
-                                  {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
-                                  {renderJSX(unref(processedDescription)!)}
+>(optionsFunc: () => ExponentialModifierOptions<T, S>): R {
+    return createLazyProxy(() => {
+        const { exponent, description, enabled } = optionsFunc();
+
+        const processedExponent = convertComputable(exponent);
+        const processedDescription = convertComputable(description);
+        const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
+        return {
+            apply: (gain: DecimalSource) => Decimal.pow(gain, unref(processedExponent)),
+            revert: (gain: DecimalSource) => Decimal.root(gain, unref(processedExponent)),
+            enabled: processedEnabled,
+            description:
+                description == null
+                    ? undefined
+                    : jsx(() => (
+                          <div class="modifier-container">
+                              <span class="modifier-amount">
+                                  ^{format(unref(processedExponent))}
                               </span>
-                          ) : null}
-                      </div>
-                  ))
-    } as unknown as R;
+                              {unref(processedDescription) ? (
+                                  <span class="modifier-description">
+                                      {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
+                                      {renderJSX(unref(processedDescription)!)}
+                                  </span>
+                              ) : null}
+                          </div>
+                      ))
+        };
+    }) as unknown as R;
 }
 
 /**