-
Notifications
You must be signed in to change notification settings - Fork 13
/
EventLoader.ts
156 lines (133 loc) · 4.06 KB
/
EventLoader.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import * as glob from 'glob';
import * as path from 'path';
import { logger, Logger } from '../util/logger/Logger';
import { Client } from '../client/Client';
import { Event } from './Event';
import { EventRegistry } from './EventRegistry';
import { Util } from '../util/Util';
/**
* Handles loading and registering Event class event handlers from
* registered source directories.
*/
export class EventLoader
{
@logger('EventLoader')
private readonly _logger!: Logger;
private readonly _client: Client;
private readonly _sources: string[];
private readonly _registry: EventRegistry;
public constructor(client: Client)
{
this._client = client;
this._sources = [];
this._registry = new EventRegistry(client);
}
/**
* Registers a source directory to load Event class event handlers from.
* Does nothing if the directory is already registered. Events will be loaded
* by the Client after the `continue` event is emitted at runtime
* @returns {void}
*/
public addSourceDir(dir: string): void
{
const resolvedDir: string = path.resolve(dir);
if (this._sources.includes(resolvedDir)) return;
this._sources.push(resolvedDir);
}
/**
* Returns whether or not the EventLoader has any registered source directories
* @returns {boolean}
*/
public hasSources(): boolean
{
return this._sources.length > 0;
}
/**
* Loads or reloads all Events from all registered sources. Allows for hot-reloading
* @returns {number} The total number of loaded or reloaded events
*/
public loadFromSources(): number
{
this._clearEvents();
let loadedEvents: number = 0;
for (const source of this._sources)
loadedEvents += this._loadEventsFrom(source);
return loadedEvents;
}
/**
* Unregisters and clears all loaded Event class event handlers
* @private
*/
private _clearEvents(): void
{
this._registry.clearRegisteredEvents();
}
/**
* Load events from the given directory
* @private
*/
private _loadEventsFrom(dir: string): number
{
// Glob all the javascript files in the directory
let eventFiles: string[] = glob.sync(`${dir}/**/*.js`);
// Glob typescript files if `tsNode` is enabled
if (this._client.tsNode)
{
eventFiles.push(...glob.sync(`${dir}/**/!(*.d).ts`));
const filteredEventFiles: string[] = eventFiles.filter(f =>
{
const file: string = f.match(/\/([^/]+?)\.[j|t]s$/)![1];
if (f.endsWith('.ts')) return true;
if (f.endsWith('.js'))
return !eventFiles.some(cf => cf.endsWith(`${file}.ts`));
return false;
});
eventFiles = filteredEventFiles;
}
const loadedEvents: Event[] = [];
this._logger.debug(`Loading events in: ${dir}`);
// Load and instantiate every event from the globbed files
for (const file of eventFiles)
{
// Delete the cached event file for hot-reloading
delete require.cache[require.resolve(file)];
const loadedFile: any = require(file);
const eventClasses: (new () => Event)[] = this._findEventClasses(loadedFile);
if (eventClasses.length === 0)
{
this._logger.warn(`Failed to find Event class in file: ${file}`);
continue;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
for (const EventClass of eventClasses)
{
const eventInstance: Event = new EventClass();
this._logger.info(`Loaded Event handler for event: ${eventInstance.name}`);
loadedEvents.push(eventInstance);
}
}
// Register all of the loaded events
for (const event of loadedEvents)
{
event.register(this._client);
this._registry.register(event);
}
return loadedEvents.length;
}
/**
* Recursively search for Event classes within the given object
* @private
*/
private _findEventClasses(obj: any): (new () => Event)[]
{
const foundClasses: ((new () => Event) | (new () => Event)[])[] = [];
const keys: string[] = Object.keys(obj);
if (Event.prototype.isPrototypeOf(obj.prototype))
foundClasses.push(obj);
else if (keys.length > 0)
for (const key of keys)
if (Event.prototype.isPrototypeOf(obj[key].prototype))
foundClasses.push(this._findEventClasses(obj[key]));
return Util.flattenArray(foundClasses);
}
}