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

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

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

1.37
/src/engine/Director/DefaultLoader.ts
1
import { WebAudio } from '../Util/WebAudio';
2
import { Engine } from '../Engine';
3
import { Loadable } from '../Interfaces/Loadable';
4
import { Canvas } from '../Graphics/Canvas';
5
import { ImageFiltering } from '../Graphics/Filtering';
6
import { clamp } from '../Math/util';
7
import { Sound } from '../Resources/Sound/Sound';
8
import { Future } from '../Util/Future';
9
import { EventEmitter, EventKey, Handler, Subscription } from '../EventEmitter';
10
import { Color } from '../Color';
11
import { delay } from '../Util/Util';
12

13
export interface DefaultLoaderOptions {
14
  /**
15
   * List of loadables
16
   */
17
  loadables?: Loadable<any>[];
18
}
19

20
export type LoaderEvents = {
21
  // Add event types here
22
  beforeload: void;
23
  afterload: void;
24
  useraction: void;
25
  loadresourcestart: Loadable<any>;
26
  loadresourceend: Loadable<any>;
27
};
28

29
export const LoaderEvents = {
1✔
30
  // Add event types here
31
  BeforeLoad: 'beforeload',
32
  AfterLoad: 'afterload',
33
  UserAction: 'useraction',
34
  LoadResourceStart: 'loadresourcestart',
35
  LoadResourceEnd: 'loadresourceend'
36
};
37

38
export type LoaderConstructor = new (...args: any[]) => DefaultLoader;
39
/**
40
 * Returns true if the constructor is for an Excalibur Loader
41
 */
42
export function isLoaderConstructor(x: any): x is LoaderConstructor {
UNCOV
43
  return !!x?.prototype && !!x?.prototype?.constructor?.name;
×
44
}
45

46
export class DefaultLoader implements Loadable<Loadable<any>[]> {
47
  public data!: Loadable<any>[];
UNCOV
48
  public events = new EventEmitter<LoaderEvents>();
×
UNCOV
49
  public canvas: Canvas = new Canvas({
×
50
    filtering: ImageFiltering.Blended,
51
    smoothing: true,
52
    cache: false,
53
    draw: this.onDraw.bind(this)
54
  });
UNCOV
55
  private _resources: Loadable<any>[] = [];
×
56
  public get resources(): readonly Loadable<any>[] {
UNCOV
57
    return this._resources;
×
58
  }
UNCOV
59
  private _numLoaded: number = 0;
×
60
  public engine!: Engine;
61

62
  /**
63
   * @param options Optionally provide the list of resources you want to load at constructor time
64
   */
65
  constructor(options?: DefaultLoaderOptions) {
UNCOV
66
    if (options && options.loadables?.length) {
×
UNCOV
67
      this.addResources(options.loadables);
×
68
    }
69
  }
70

71
  /**
72
   * Called by the engine before loading
73
   * @param engine
74
   */
75
  public onInitialize(engine: Engine) {
UNCOV
76
    this.engine = engine;
×
UNCOV
77
    this.canvas.width = this.engine.screen.resolution.width;
×
UNCOV
78
    this.canvas.height = this.engine.screen.resolution.height;
×
79
  }
80

81
  /**
82
   * Return a promise that resolves when the user interacts with the loading screen in some way, usually a click.
83
   *
84
   * It's important to implement this in order to unlock the audio context in the browser. Browsers automatically prevent
85
   * audio from playing until the user performs an action.
86
   *
87
   */
88
  public async onUserAction(): Promise<void> {
89
    return await Promise.resolve();
×
90
  }
91

92
  /**
93
   * Overridable lifecycle method, called directly before loading starts
94
   */
95
  public async onBeforeLoad() {
96
    // override me
97
  }
98

99
  /**
100
   * Overridable lifecycle method, called after loading has completed
101
   */
102
  public async onAfterLoad() {
103
    // override me
104
    await delay(500, this.engine.clock); // avoid a flicker
×
105
  }
106

107
  /**
108
   * Add a resource to the loader to load
109
   * @param loadable  Resource to add
110
   */
111
  public addResource(loadable: Loadable<any>) {
UNCOV
112
    this._resources.push(loadable);
×
113
  }
114

115
  /**
116
   * Add a list of resources to the loader to load
117
   * @param loadables  The list of resources to load
118
   */
119
  public addResources(loadables: Loadable<any>[]) {
UNCOV
120
    let i = 0;
×
UNCOV
121
    const len = loadables.length;
×
122

UNCOV
123
    for (i; i < len; i++) {
×
UNCOV
124
      this.addResource(loadables[i]);
×
125
    }
126
  }
127

128
  public markResourceComplete(): void {
UNCOV
129
    this._numLoaded++;
×
130
  }
131

132
  /**
133
   * Returns the progress of the loader as a number between [0, 1] inclusive.
134
   */
135
  public get progress(): number {
UNCOV
136
    const total = this._resources.length;
×
UNCOV
137
    return total > 0 ? clamp(this._numLoaded, 0, total) / total : 1;
×
138
  }
139

140
  /**
141
   * Returns true if the loader has completely loaded all resources
142
   */
143
  public isLoaded() {
UNCOV
144
    return this._numLoaded === this._resources.length;
×
145
  }
146

UNCOV
147
  private _totalTimeMs = 0;
×
148

149
  /**
150
   * Optionally override the onUpdate
151
   * @param engine
152
   * @param elapsed
153
   */
154
  onUpdate(engine: Engine, elapsed: number): void {
UNCOV
155
    this._totalTimeMs += elapsed;
×
156
    // override me
157
  }
158

159
  /**
160
   * Optionally override the onDraw
161
   */
162
  onDraw(ctx: CanvasRenderingContext2D) {
UNCOV
163
    const seconds = this._totalTimeMs / 1000;
×
164

UNCOV
165
    ctx.fillStyle = Color.Black.toRGBA();
×
UNCOV
166
    ctx.fillRect(0, 0, this.engine.screen.resolution.width, this.engine.screen.resolution.height);
×
167

UNCOV
168
    ctx.save();
×
UNCOV
169
    ctx.translate(this.engine.screen.resolution.width / 2, this.engine.screen.resolution.height / 2);
×
UNCOV
170
    const speed = seconds * 10;
×
UNCOV
171
    ctx.strokeStyle = 'white';
×
UNCOV
172
    ctx.lineWidth = 10;
×
UNCOV
173
    ctx.lineCap = 'round';
×
UNCOV
174
    ctx.arc(0, 0, 40, speed, speed + (Math.PI * 3) / 2);
×
UNCOV
175
    ctx.stroke();
×
176

UNCOV
177
    ctx.fillStyle = 'white';
×
UNCOV
178
    ctx.font = '16px sans-serif';
×
UNCOV
179
    const text = (this.progress * 100).toFixed(0) + '%';
×
UNCOV
180
    const textbox = ctx.measureText(text);
×
UNCOV
181
    const width = Math.abs(textbox.actualBoundingBoxLeft) + Math.abs(textbox.actualBoundingBoxRight);
×
UNCOV
182
    const height = Math.abs(textbox.actualBoundingBoxAscent) + Math.abs(textbox.actualBoundingBoxDescent);
×
UNCOV
183
    ctx.fillText(text, -width / 2, height / 2); // center
×
UNCOV
184
    ctx.restore();
×
185
  }
186

UNCOV
187
  private _loadingFuture = new Future<void>();
×
188
  public areResourcesLoaded() {
UNCOV
189
    if (this._resources.length === 0) {
×
190
      // special case no resources mean loaded;
UNCOV
191
      return Promise.resolve();
×
192
    }
UNCOV
193
    return this._loadingFuture.promise;
×
194
  }
195

196
  /**
197
   * Not meant to be overridden
198
   *
199
   * Begin loading all of the supplied resources, returning a promise
200
   * that resolves when loading of all is complete AND the user has interacted with the loading screen
201
   */
202
  public async load(): Promise<Loadable<any>[]> {
UNCOV
203
    await this.onBeforeLoad();
×
UNCOV
204
    this.events.emit('beforeload');
×
UNCOV
205
    this.canvas.flagDirty();
×
206

UNCOV
207
    await Promise.all(
×
UNCOV
208
      this._resources.map(async (r) => {
×
UNCOV
209
        this.events.emit('loadresourcestart', r);
×
UNCOV
210
        await r.load().finally(() => {
×
211
          // capture progress
UNCOV
212
          this._numLoaded++;
×
UNCOV
213
          this.canvas.flagDirty();
×
UNCOV
214
          this.events.emit('loadresourceend', r);
×
215
        });
216
      })
217
    );
218

219
    // Wire all sound to the engine
UNCOV
220
    for (const resource of this._resources) {
×
UNCOV
221
      if (resource instanceof Sound) {
×
222
        resource.wireEngine(this.engine);
×
223
      }
224
    }
225

UNCOV
226
    this._loadingFuture.resolve();
×
UNCOV
227
    this.canvas.flagDirty();
×
228
    // Unlock browser AudioContext in after user gesture
229
    // See: https://github.com/excaliburjs/Excalibur/issues/262
230
    // See: https://github.com/excaliburjs/Excalibur/issues/1031
UNCOV
231
    await this.onUserAction();
×
UNCOV
232
    this.events.emit('useraction');
×
UNCOV
233
    await WebAudio.unlock();
×
234

UNCOV
235
    await this.onAfterLoad();
×
UNCOV
236
    this.events.emit('afterload');
×
UNCOV
237
    return (this.data = this._resources);
×
238
  }
239

240
  public emit<TEventName extends EventKey<LoaderEvents>>(eventName: TEventName, event: LoaderEvents[TEventName]): void;
241
  public emit(eventName: string, event?: any): void;
242
  public emit<TEventName extends EventKey<LoaderEvents> | string>(eventName: TEventName, event?: any): void {
243
    this.events.emit(eventName, event);
×
244
  }
245

246
  public on<TEventName extends EventKey<LoaderEvents>>(eventName: TEventName, handler: Handler<LoaderEvents[TEventName]>): Subscription;
247
  public on(eventName: string, handler: Handler<unknown>): Subscription;
248
  public on<TEventName extends EventKey<LoaderEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
UNCOV
249
    return this.events.on(eventName, handler);
×
250
  }
251

252
  public once<TEventName extends EventKey<LoaderEvents>>(eventName: TEventName, handler: Handler<LoaderEvents[TEventName]>): Subscription;
253
  public once(eventName: string, handler: Handler<unknown>): Subscription;
254
  public once<TEventName extends EventKey<LoaderEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
255
    return this.events.once(eventName, handler);
×
256
  }
257

258
  public off<TEventName extends EventKey<LoaderEvents>>(eventName: TEventName, handler: Handler<LoaderEvents[TEventName]>): void;
259
  public off(eventName: string, handler: Handler<unknown>): void;
260
  public off(eventName: string): void;
261
  public off<TEventName extends EventKey<LoaderEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
262
    (this.events as any).off(eventName, handler);
×
263
  }
264
}
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