• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 14840900291

05 May 2025 04:07PM UTC coverage: 87.864% (-1.5%) from 89.319%
14840900291

push

github

web-flow
chore: migrate tests to vitest (#3381)

managed to migrate the engine leak & memory reporters, although it took a bit of rigamarole. Mainly because, as opposed to karma, the reporter runs in node and not the browser. So I have to track the data needed separately in some global hooks _within_ the browser environment, which the reporter then reads and creates the logs if needed.

However, it seems the memory tracking is regularly reporting >1 mb, so I'm not sure if it's tracking properly or if things have changed with the new browsers. My only theory is that because the timing where memory is read is during an `afterEach` _before_ the test's `afterEach`, it's running before any potential cleanup. The timing of this is not something that's easy to control, unfortunately. I did try to prove this theory by doing the memory analysis on the next test's `beforeEach`, but it didnt seem to change the results, so it may not be an issue.

Implementation is done in `src/spec/vitest/__reporters__/memory.ts` and `src/spec/vitest/__reporters/memory.setup.ts`

---

I noticed CouroutineSpec was an offender for >10mb memory, which spins up additional engines. I added a short 100ms wait after the engine.dispose and the memory usage did drop, so it does seem like this is prone to scheduled garbage collecting.

---

I was able to run the garbage collector (if exposed, currently only on chrome) and this makes the reports more accurate

4998 of 6942 branches covered (72.0%)

13655 of 15541 relevant lines covered (87.86%)

25166.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.86
/src/engine/Graphics/FontCache.ts
1
import type { BoundingBox } from '../Collision/BoundingBox';
2
import type { Color } from '../Color';
3
import { Logger } from '../Util/Log';
4
import type { Font } from './Font';
5
import { FontTextInstance } from './FontTextInstance';
6

7
export class FontCache {
117✔
8
  public static FONT_TIMEOUT = 500;
9
  private static _LOGGER = Logger.getInstance();
10
  private static _TEXT_USAGE = new Map<FontTextInstance, number>();
11
  private static _TEXT_CACHE = new Map<string, FontTextInstance>();
12
  private static _MEASURE_CACHE = new Map<string, BoundingBox>();
13

14
  static measureText(text: string, font: Font, maxWidth?: number): BoundingBox {
15
    const hash = FontTextInstance.getHashCode(font, text);
18✔
16
    if (FontCache._MEASURE_CACHE.has(hash)) {
18✔
17
      return FontCache._MEASURE_CACHE.get(hash)!;
15✔
18
    }
19
    FontCache._LOGGER.debug('Font text measurement cache miss');
3✔
20
    const measurement = font.measureTextWithoutCache(text, maxWidth);
3✔
21
    FontCache._MEASURE_CACHE.set(hash, measurement);
3✔
22
    return measurement;
3✔
23
  }
24

25
  static getTextInstance(text: string, font: Font, color: Color): FontTextInstance {
26
    const hash = FontTextInstance.getHashCode(font, text, color);
×
27
    let textInstance = FontCache._TEXT_CACHE.get(hash);
×
28
    if (!textInstance) {
×
29
      textInstance = new FontTextInstance(font, text, color);
×
30
      FontCache._TEXT_CACHE.set(hash, textInstance);
×
31
      FontCache._LOGGER.debug('Font text instance cache miss');
×
32
    }
33

34
    // Cache the bitmap for certain amount of time
35
    FontCache._TEXT_USAGE.set(textInstance, performance.now());
×
36

37
    return textInstance;
×
38
  }
39

40
  static checkAndClearCache() {
41
    const deferred: FontTextInstance[] = [];
942✔
42
    const currentHashCodes = new Set<string>();
942✔
43
    for (const [textInstance, time] of FontCache._TEXT_USAGE.entries()) {
942✔
44
      // if bitmap hasn't been used in 100 ms clear it
45
      if (time + FontCache.FONT_TIMEOUT < performance.now()) {
×
46
        FontCache._LOGGER.debug(`Text cache entry timed out ${textInstance.text}`);
×
47
        deferred.push(textInstance);
×
48
        textInstance.dispose();
×
49
      } else {
50
        const hash = textInstance.getHashCode(false);
×
51
        currentHashCodes.add(hash);
×
52
      }
53
    }
54
    // Deferred removal of text instances
55
    deferred.forEach((t) => {
942✔
56
      FontCache._TEXT_USAGE.delete(t);
×
57
    });
58

59
    // Regenerate text instance cache
60
    this._TEXT_CACHE.clear();
942✔
61
    for (const [textInstance] of this._TEXT_USAGE.entries()) {
942✔
62
      this._TEXT_CACHE.set(textInstance.getHashCode(), textInstance);
×
63
    }
64

65
    // Regenerated measurement cache
66
    const newTextMeasurementCache = new Map<string, BoundingBox>();
942✔
67
    for (const current of currentHashCodes) {
942✔
68
      if (FontCache._MEASURE_CACHE.has(current)) {
×
69
        newTextMeasurementCache.set(current, FontCache._MEASURE_CACHE.get(current)!);
×
70
      }
71
    }
72
    this._MEASURE_CACHE.clear();
942✔
73
    this._MEASURE_CACHE = newTextMeasurementCache;
942✔
74
  }
75

76
  public static get cacheSize() {
77
    return FontCache._TEXT_USAGE.size;
×
78
  }
79

80
  /**
81
   * Force clear all cached text bitmaps
82
   */
83
  public static clearCache() {
84
    for (const [textInstance] of FontCache._TEXT_USAGE.entries()) {
×
85
      textInstance.dispose();
×
86
    }
87
    FontCache._TEXT_USAGE.clear();
×
88
    FontCache._TEXT_CACHE.clear();
×
89
    FontCache._MEASURE_CACHE.clear();
×
90
  }
91
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc