harmony/src/client/shard.ts

134 lines
4.0 KiB
TypeScript
Raw Normal View History

2020-12-16 10:00:13 +00:00
import { Collection } from '../utils/collection.ts'
2021-01-24 16:46:10 +00:00
import type { Client } from './client.ts'
2021-04-04 05:42:15 +00:00
import { RESTManager } from '../rest/mod.ts'
import { Gateway } from '../gateway/mod.ts'
2021-01-20 10:05:15 +00:00
import { HarmonyEventEmitter } from '../utils/events.ts'
import { GatewayEvents } from '../types/gateway.ts'
2021-01-24 16:46:10 +00:00
import { delay } from '../utils/delay.ts'
2020-12-16 10:00:13 +00:00
2021-01-20 10:05:15 +00:00
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type ShardManagerEvents = {
launch: [number]
shardReady: [number]
shardDisconnect: [number, number | undefined, string | undefined]
shardError: [number, Error, ErrorEvent]
shardResume: [number]
2020-12-16 10:00:13 +00:00
}
2021-01-20 10:05:15 +00:00
export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> {
list: Collection<string, Gateway> = new Collection()
client: Client
cachedShardCount?: number
2021-01-24 16:46:10 +00:00
queueProcessing: boolean = false
queue: CallableFunction[] = []
2020-12-16 10:00:13 +00:00
get rest(): RESTManager {
2021-01-20 10:05:15 +00:00
return this.client.rest
2020-12-16 10:00:13 +00:00
}
2021-01-20 10:05:15 +00:00
constructor(client: Client) {
2020-12-16 10:00:13 +00:00
super()
2021-01-20 10:05:15 +00:00
this.client = client
}
2020-12-16 10:00:13 +00:00
2021-01-24 16:46:10 +00:00
debug(msg: string): void {
this.client.debug('Shards', msg)
}
enqueueIdentify(fn: CallableFunction): ShardManager {
this.queue.push(fn)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
if (!this.queueProcessing) this.processQueue()
return this
}
private async processQueue(): Promise<void> {
if (this.queueProcessing || this.queue.length === 0) return
this.queueProcessing = true
const item = this.queue[0]
await item()
this.queue.shift()
await delay(5000)
this.queueProcessing = false
if (this.queue.length === 0) {
this.queueProcessing = false
} else {
await this.processQueue()
}
}
2021-01-20 10:05:15 +00:00
async getShardCount(): Promise<number> {
let shardCount: number
if (this.cachedShardCount !== undefined) shardCount = this.cachedShardCount
else {
2021-04-08 08:52:33 +00:00
if (
this.client.shardCount === 'auto' &&
this.client.fetchGatewayInfo !== false
) {
this.debug('Fetch /gateway/bot...')
2021-01-20 10:05:15 +00:00
const info = await this.client.rest.api.gateway.bot.get()
2021-04-08 08:52:33 +00:00
this.debug(`Recommended Shards: ${info.shards}`)
this.debug('=== Session Limit Info ===')
this.debug(
`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`
)
this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`)
2021-01-20 10:05:15 +00:00
shardCount = info.shards as number
2021-04-08 08:52:33 +00:00
} else
shardCount =
typeof this.client.shardCount === 'string'
? 1
: this.client.shardCount ?? 1
2021-01-20 10:05:15 +00:00
}
this.cachedShardCount = shardCount
return this.cachedShardCount
2020-12-16 10:00:13 +00:00
}
2021-01-20 10:05:15 +00:00
/** Launches a new Shard */
async launch(id: number): Promise<ShardManager> {
if (this.list.has(id.toString()) === true)
throw new Error(`Shard ${id} already launched`)
2020-12-16 10:00:13 +00:00
2021-01-24 16:46:10 +00:00
this.debug(`Launching Shard: ${id}`)
2021-01-20 10:05:15 +00:00
const shardCount = await this.getShardCount()
2020-12-16 10:00:13 +00:00
2021-01-20 10:05:15 +00:00
const gw = new Gateway(this.client, [Number(id), shardCount])
this.list.set(id.toString(), gw)
2021-01-24 16:46:10 +00:00
2021-01-20 10:05:15 +00:00
gw.initWebsocket()
this.emit('launch', id)
2020-12-16 10:00:13 +00:00
2021-01-20 10:05:15 +00:00
gw.on(GatewayEvents.Ready, () => this.emit('shardReady', id))
gw.on('error', (err: Error, evt: ErrorEvent) =>
this.emit('shardError', id, err, evt)
)
gw.on(GatewayEvents.Resumed, () => this.emit('shardResume', id))
gw.on('close', (code: number, reason: string) =>
this.emit('shardDisconnect', id, code, reason)
)
return gw.waitFor(GatewayEvents.Ready, () => true).then(() => this)
}
2021-01-24 16:46:10 +00:00
/** Launches all Shards */
async connect(): Promise<ShardManager> {
2021-01-20 10:05:15 +00:00
const shardCount = await this.getShardCount()
2021-01-24 16:46:10 +00:00
this.client.shardCount = shardCount
this.debug(`Launching ${shardCount} shard${shardCount === 1 ? '' : 's'}...`)
const startTime = Date.now()
for (let i = 0; i < shardCount; i++) {
2021-01-20 10:05:15 +00:00
await this.launch(i)
}
2021-01-24 16:46:10 +00:00
const endTime = Date.now()
const diff = endTime - startTime
this.debug(
`Launched ${shardCount} shards! Time taken: ${Math.floor(diff / 1000)}s`
)
2021-01-20 10:05:15 +00:00
return this
}
2021-01-24 16:46:10 +00:00
get(id: number): Gateway | undefined {
return this.list.get(id.toString())
}
2020-12-16 10:00:13 +00:00
}