Browse Source

Replace sortDomains w/ trie dump sort

SukkaW 1 year ago
parent
commit
2bbc122b85
5 changed files with 99 additions and 46 deletions
  1. 2 2
      Build/lib/misc.ts
  2. 0 8
      Build/lib/rules/base.ts
  3. 14 7
      Build/lib/rules/domainset.ts
  4. 3 4
      Build/lib/rules/ruleset.ts
  5. 80 25
      Build/lib/trie.ts

+ 2 - 2
Build/lib/misc.ts

@@ -103,9 +103,9 @@ export function withBannerArray(title: string, description: string[] | readonly
   ];
 };
 
-export function mergeHeaders<T extends RequestInit['headers'] | HeadersInit>(headersA: T | undefined, headersB: T): T {
+export function mergeHeaders<T extends RequestInit['headers'] | HeadersInit>(headersA: T | undefined, headersB: T | undefined): T {
   if (headersA == null) {
-    return headersB;
+    return headersB!;
   }
 
   if (Array.isArray(headersB)) {

+ 0 - 8
Build/lib/rules/base.ts

@@ -88,14 +88,6 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
     return this;
   }
 
-  protected apexDomainMap: Map<string, string> | null = null;
-  protected subDomainMap: Map<string, string> | null = null;
-  withDomainMap(apexDomainMap: Map<string, string>, subDomainMap: Map<string, string>) {
-    this.apexDomainMap = apexDomainMap;
-    this.subDomainMap = subDomainMap;
-    return this;
-  }
-
   addDomain(domain: string) {
     this.domainTrie.add(domain);
     return this;

+ 14 - 7
Build/lib/rules/domainset.ts

@@ -20,13 +20,8 @@ export class DomainsetOutput extends RuleOutput<Preprocessed> {
         return;
       }
       results.push(domain);
-    });
+    }, true);
 
-    if (!this.apexDomainMap || !this.subDomainMap) {
-      const { domainMap, subdomainMap } = buildParseDomainMap(results);
-      this.apexDomainMap = domainMap;
-      this.subDomainMap = subdomainMap;
-    }
     const sorted = results;
     sorted.push('this_ruleset_is_made_by_sukkaw.ruleset.skk.moe');
 
@@ -63,9 +58,21 @@ export class DomainsetOutput extends RuleOutput<Preprocessed> {
     } satisfies SingboxSourceFormat);
   }
 
+  protected apexDomainMap: Map<string, string> | null = null;
+  protected subDomainMap: Map<string, string> | null = null;
+  withDomainMap(apexDomainMap: Map<string, string>, subDomainMap: Map<string, string>) {
+    this.apexDomainMap = apexDomainMap;
+    this.subDomainMap = subDomainMap;
+    return this;
+  }
+
   getStatMap() {
     invariant(this.$preprocessed, 'Non dumped yet');
-    invariant(this.apexDomainMap, 'Missing apex domain map');
+
+    if (!this.apexDomainMap || !this.subDomainMap) {
+      const { domainMap } = buildParseDomainMap(this.$preprocessed);
+      this.apexDomainMap = domainMap;
+    }
 
     return Array.from(this.$preprocessed
       .reduce<Map<string, number>>(

+ 3 - 4
Build/lib/rules/ruleset.ts

@@ -4,7 +4,6 @@ import createKeywordFilter from '../aho-corasick';
 import { appendArrayInPlace } from '../append-array-in-place';
 import { appendArrayFromSet } from '../misc';
 import type { SingboxSourceFormat } from '../singbox';
-import { sortDomains } from '../stable-sort-domain';
 import { RuleOutput } from './base';
 import picocolors from 'picocolors';
 import { normalizeDomain } from '../normalize-domain';
@@ -24,9 +23,9 @@ export class RulesetOutput extends RuleOutput<Preprocessed> {
     const domainSuffixes: string[] = [];
     const sortedDomainRules: string[] = [];
 
-    for (const domain of sortDomains(this.domainTrie.dump(), this.apexDomainMap, this.subDomainMap)) {
+    this.domainTrie.dump((domain) => {
       if (kwfilter(domain)) {
-        continue;
+        return;
       }
       if (domain[0] === '.') {
         domainSuffixes.push(domain.slice(1));
@@ -35,7 +34,7 @@ export class RulesetOutput extends RuleOutput<Preprocessed> {
         domains.push(domain);
         sortedDomainRules.push(`DOMAIN,${domain}`);
       }
-    }
+    }, true);
 
     return [domains, domainSuffixes, sortedDomainRules] satisfies Preprocessed;
   }

+ 80 - 25
Build/lib/trie.ts

@@ -177,33 +177,76 @@ abstract class Triebase<Meta = any> {
     return true;
   };
 
+  private static bfsResults: [node: TrieNode | null, suffix: string[]] = [null, []];
+
+  private static bfs<Meta>(this: void, nodeStack: FIFO<TrieNode<Meta>>, suffixStack: FIFO<string[]>) {
+    const node = nodeStack.dequeue()!;
+    const suffix = suffixStack.dequeue()!;
+
+    node[3].forEach((childNode, k) => {
+      // Pushing the child node to the stack for next iteration of DFS
+      nodeStack.enqueue(childNode);
+
+      suffixStack.enqueue([k, ...suffix]);
+    });
+
+    Triebase.bfsResults[0] = node;
+    Triebase.bfsResults[1] = suffix;
+
+    return Triebase.bfsResults;
+  }
+
+  private static bfsWithSort<Meta>(this: void, nodeStack: FIFO<TrieNode<Meta>>, suffixStack: FIFO<string[]>) {
+    const node = nodeStack.dequeue()!;
+    const suffix = suffixStack.dequeue()!;
+
+    if (node[3].size) {
+      const keys = Array.from(node[3].keys()).sort(Triebase.compare);
+
+      for (let i = 0, l = keys.length; i < l; i++) {
+        const key = keys[i];
+        const childNode = node[3].get(key)!;
+
+        // Pushing the child node to the stack for next iteration of DFS
+        nodeStack.enqueue(childNode);
+        suffixStack.enqueue([key, ...suffix]);
+      }
+    }
+
+    Triebase.bfsResults[0] = node;
+    Triebase.bfsResults[1] = suffix;
+
+    return Triebase.bfsResults;
+  }
+
   private walk(
     onMatches: (suffix: string[], subdomain: boolean, meta: Meta) => void,
     initialNode = this.$root,
-    initialSuffix: string[] = []
+    initialSuffix: string[] = [],
+    withSort = false
   ) {
-    const nodeStack: Array<TrieNode<Meta>> = [initialNode];
+    const bfsImpl = withSort ? Triebase.bfsWithSort : Triebase.bfs;
+
+    const nodeStack = new FIFO<TrieNode<Meta>>();
+    nodeStack.enqueue(initialNode);
+
     // Resolving initial string (begin the start of the stack)
-    const suffixStack: string[][] = [initialSuffix];
+    const suffixStack = new FIFO<string[]>();
+    suffixStack.enqueue(initialSuffix);
 
     let node: TrieNode<Meta> = initialNode;
+    let r;
 
     do {
-      node = nodeStack.pop()!;
-      const suffix = suffixStack.pop()!;
-
-      node[3].forEach((childNode, k) => {
-        // Pushing the child node to the stack for next iteration of DFS
-        nodeStack.push(childNode);
-
-        suffixStack.push([k, ...suffix]);
-      });
+      r = bfsImpl(nodeStack, suffixStack);
+      node = r[0]!;
+      const suffix = r[1];
 
       // If the node is a sentinel, we push the suffix to the results
       if (node[0]) {
         onMatches(suffix, node[1], node[4]);
       }
-    } while (nodeStack.length);
+    } while (nodeStack.size);
   };
 
   static compare(this: void, a: string, b: string) {
@@ -360,9 +403,9 @@ abstract class Triebase<Meta = any> {
     return true;
   };
 
-  public dump(onSuffix: (suffix: string) => void): void;
-  public dump(): string[];
-  public dump(onSuffix?: (suffix: string) => void): string[] | void {
+  public dump(onSuffix: (suffix: string) => void, withSort?: boolean): void;
+  public dump(onSuffix?: null, withSort?: boolean): string[];
+  public dump(onSuffix?: ((suffix: string) => void) | null, withSort = false): string[] | void {
     const results: string[] = [];
 
     const handleSuffix = onSuffix
@@ -375,28 +418,36 @@ abstract class Triebase<Meta = any> {
         results.push(subdomain ? '.' + d : d);
       };
 
-    this.walkWithSort(handleSuffix);
+    if (withSort) {
+      this.walkWithSort(handleSuffix);
+    } else {
+      this.walk(handleSuffix);
+    }
 
     return results;
   };
 
-  public dumpMeta(onMeta: (meta: Meta) => void): void;
-  public dumpMeta(): Meta[];
-  public dumpMeta(onMeta?: (meta: Meta) => void): Meta[] | void {
+  public dumpMeta(onMeta: (meta: Meta) => void, withSort?: boolean): void;
+  public dumpMeta(onMeta?: null, withSort?: boolean): Meta[];
+  public dumpMeta(onMeta?: ((meta: Meta) => void) | null, withSort = false): Meta[] | void {
     const results: Meta[] = [];
 
     const handleMeta = onMeta
       ? (_suffix: string[], _subdomain: boolean, meta: Meta) => onMeta(meta)
       : (_suffix: string[], _subdomain: boolean, meta: Meta) => results.push(meta);
 
-    this.walk(handleMeta);
+    if (withSort) {
+      this.walkWithSort(handleMeta);
+    } else {
+      this.walk(handleMeta);
+    }
 
     return results;
   };
 
-  public dumpWithMeta(onSuffix: (suffix: string, meta: Meta | undefined) => void): void;
-  public dumpWithMeta(): Array<[string, Meta | undefined]>;
-  public dumpWithMeta(onSuffix?: (suffix: string, meta: Meta | undefined) => void): Array<[string, Meta | undefined]> | void {
+  public dumpWithMeta(onSuffix: (suffix: string, meta: Meta | undefined) => void, withSort?: boolean): void;
+  public dumpWithMeta(onMeta?: null, withSort?: boolean): Array<[string, Meta | undefined]>;
+  public dumpWithMeta(onSuffix?: ((suffix: string, meta: Meta | undefined) => void) | null, withSort = false): Array<[string, Meta | undefined]> | void {
     const results: Array<[string, Meta | undefined]> = [];
 
     const handleSuffix = onSuffix
@@ -409,7 +460,11 @@ abstract class Triebase<Meta = any> {
         results.push([subdomain ? '.' + d : d, meta]);
       };
 
-    this.walk(handleSuffix);
+    if (withSort) {
+      this.walkWithSort(handleSuffix);
+    } else {
+      this.walk(handleSuffix);
+    }
 
     return results;
   };