« get me outta code hell

html: experimentally optimize isBlank... with generators - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/util
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-01-09 16:39:59 -0400
committer(quasar) nebula <qznebula@protonmail.com>2024-03-16 13:30:35 -0300
commitdc6080a7543ea2fd39a28b4bbefa6b9b1c7139dd (patch)
treed1dc39e8453e4ac69ddc4acf81429bbf83523650 /src/util
parent76373411f96f793edfbfe857b22298849131e762 (diff)
html: experimentally optimize isBlank... with generators
Diffstat (limited to 'src/util')
-rw-r--r--src/util/html.js120
1 files changed, 119 insertions, 1 deletions
diff --git a/src/util/html.js b/src/util/html.js
index 499e372..d269b6e 100644
--- a/src/util/html.js
+++ b/src/util/html.js
@@ -82,6 +82,117 @@ export const noEdgeWhitespace = Symbol();
 // character).
 export const blockwrap = Symbol();
 
+function* isBlankArrayHelper(content) {
+  // First look for string items. These are the easiest to
+  // test blankness.
+
+  const nonStringContent = [];
+
+  for (const item of content) {
+    if (typeof item === 'string') {
+      if (item.length > 0) {
+        return false;
+      }
+    } else {
+      nonStringContent.push(item);
+    }
+  }
+
+  // Analyze the content more closely. Put arrays (and
+  // content of tags marked onlyIfContent) into one array,
+  // and templates into another. And if there's anything
+  // else, that's a non-blank condition we'll detect now.
+
+  const arrayContent = [];
+  const templateContent = [];
+
+  for (const item of nonStringContent) {
+    if (item instanceof Tag) {
+      if (item.onlyIfContent || item.contentOnly) {
+        arrayContent.push(item.content);
+      } else {
+        return false;
+      }
+    } else if (Array.isArray(item)) {
+      arrayContent.push(item);
+    } else if (item instanceof Template) {
+      templateContent.push(item);
+    } else {
+      return false;
+    }
+  }
+
+  // Iterate over arrays and tag content recursively.
+  // But only perform a basic test, first.
+
+  const recursiveGenerators = [];
+
+  for (const item of arrayContent) {
+    const generator = isBlankArrayHelper(item);
+    const firstResult = generator.next();
+    if (firstResult.done) {
+      if (firstResult.value === false) {
+        return false;
+      }
+    } else {
+      recursiveGenerators.push(generator);
+    }
+  }
+
+  // If none of the arrays/tag content ended up including
+  // templates, *and* this call's content doesn't have any
+  // directly descendant templates, then we've iterated
+  // over everything and didn't find anything non-blank.
+
+  if (empty(recursiveGenerators) && empty(templateContent)) {
+    return true;
+  }
+
+  // We'll continue the recursive generators to test for
+  // template content only if nothing else passes first,
+  // and only after a yield, so that the calling function
+  // can refer back to this generator only if needed.
+
+  yield;
+
+  // First check directly descendant templates. We don't
+  // actually have any way to rank one template as "easier"
+  // to test for blankness tha nanother, so the order is
+  // basically arbitrary, but this probably avoids some
+  // overhead of resuming generators (which would perform
+  // equivalent template-resolving work anyway).
+
+  for (const template of templateContent) {
+    // Note that we're using the atomic isBlank function
+    // to test templates, instead of recursing into
+    // another generator. At this point we've established
+    // that there's no hope but to test the templates,
+    // and we're already performing the worst operation
+    // available - if we decided to defer recursively
+    // evaluating one template's own descendant templates,
+    // what would we do *instead?* Well, just evaluate
+    // some other template... which is already the worst-
+    // priority action. So there's nothing lost by just
+    // checking each remaining template atomically.
+    if (!isBlank(template.content)) {
+      return false;
+    }
+  }
+
+  // Test recursive generators last.
+
+  for (const generator of recursiveGenerators) {
+    if (generator.next().value === false) {
+      return false;
+    }
+  }
+
+  // If absolutely nothing has been found, we're golden!
+  // This content is definitely blank.
+
+  return true;
+}
+
 // Checks if the content provided would be represented as nothing if included
 // on a page. This can be used on its own, and is the underlying "interface"
 // layer for specific classes' `blank` getters, so its definition and usage
@@ -95,7 +206,14 @@ export function isBlank(content) {
   }
 
   if (Array.isArray(content)) {
-    return content.every(isBlank);
+    const generator = isBlankArrayHelper(content);
+
+    let result;
+    do {
+      result = generator.next();
+    } while (!result.done);
+
+    return result.value;
   }
 
   if (typeof content === 'string') {