-
-
Notifications
You must be signed in to change notification settings - Fork 189
/
Resource.ts
140 lines (122 loc) · 3.71 KB
/
Resource.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import { Loadable } from '../Interfaces/Loadable';
import { Class } from '../Class';
import { Engine } from '../Engine';
import { Promise } from '../Promises';
import { Logger } from '../Util/Log';
/**
* The [[Resource]] type allows games built in Excalibur to load generic resources.
* For any type of remote resource it is recommended to use [[Resource]] for preloading.
*
* [[include:Resources.md]]
*/
export class Resource<T> extends Class implements Loadable {
public data: T = null;
public logger: Logger = Logger.getInstance();
public arrayBuffer: ArrayBuffer = null;
/**
* @param path Path to the remote resource
* @param responseType The type to expect as a response: "" | "arraybuffer" | "blob" | "document" | "json" | "text";
* @param bustCache Whether or not to cache-bust requests
*/
constructor(
public path: string,
public responseType: '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text',
public bustCache: boolean = true
) {
super();
}
/**
* Returns true if the Resource is completely loaded and is ready
* to be drawn.
*/
public isLoaded(): boolean {
return this.data !== null;
}
public wireEngine(_engine: Engine) {
// override me
}
private _cacheBust(uri: string): string {
const query: RegExp = /\?\w*=\w*/;
if (query.test(uri)) {
uri += '&__=' + Date.now();
} else {
uri += '?__=' + Date.now();
}
return uri;
}
private _start() {
this.logger.debug('Started loading resource ' + this.path);
}
/**
* Begin loading the resource and returns a promise to be resolved on completion
*/
public load(): Promise<T> {
const complete = new Promise<T>();
// Exit early if we already have data
if (this.data !== null) {
this.logger.debug('Already have data for resource', this.path);
complete.resolve(this.data);
this.oncomplete();
return complete;
}
const request = new XMLHttpRequest();
request.open('GET', this.bustCache ? this._cacheBust(this.path) : this.path, true);
request.responseType = this.responseType;
request.onloadstart = () => {
this._start();
};
request.onprogress = this.onprogress;
request.onerror = this.onerror;
request.onload = () => {
// XHR on file:// success status is 0, such as with PhantomJS
if (request.status !== 0 && request.status !== 200) {
this.logger.error('Failed to load resource ', this.path, ' server responded with error code', request.status);
this.onerror(request.response);
complete.resolve(request.response);
return;
}
this.data = this.processData(request.response);
this.oncomplete();
this.logger.debug('Completed loading resource', this.path);
complete.resolve(this.data);
};
request.send();
return complete;
}
/**
* Returns the loaded data once the resource is loaded
*/
public getData(): any {
return this.data;
}
public getArrayData(): any {
return this.arrayBuffer;
}
/**
* Sets the data for this resource directly
*/
public setData(data: any) {
this.data = this.processData(data);
}
/**
* This method is meant to be overridden to handle any additional
* processing. Such as decoding downloaded audio bits.
*/
public processData(data: T): any {
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
// Blob requires an object url
if (this.responseType === 'blob') {
return URL.createObjectURL(data);
}
return data;
}
public onprogress: (e: any) => void = () => {
return;
};
public oncomplete: () => void = () => {
return;
};
public onerror: (e: any) => void = () => {
return;
};
}