seyfert
Version:
The most advanced framework for discord bots
235 lines (234 loc) • 9.06 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShardManager = void 0;
const common_1 = require("../../common");
const types_1 = require("../../types");
const constants_1 = require("../constants");
const structures_1 = require("../structures");
const timeout_1 = require("../structures/timeout");
const shard_1 = require("./shard");
let parentPort;
let workerData;
class ShardManager extends Map {
connectQueue;
options;
debugger;
constructor(options) {
super();
this.options = (0, common_1.MergeOptions)(constants_1.ShardManagerDefaults, {
totalShards: options.info.shards,
}, options);
this.connectQueue = new timeout_1.ConnectQueue(5.5e3, this.concurrency);
if (this.options.debug) {
this.debugger = new common_1.Logger({
name: '[ShardManager]',
logLevel: common_1.LogLevels.Debug,
});
}
const worker_threads = (0, common_1.lazyLoadPackage)('node:worker_threads');
if (worker_threads) {
workerData = worker_threads.workerData;
if (worker_threads.parentPort)
parentPort = worker_threads.parentPort;
}
}
get totalShards() {
return this.options.totalShards ?? this.options.info.shards;
}
get shardStart() {
return this.options.shardStart ?? 0;
}
get shardEnd() {
return this.options.shardEnd ?? this.totalShards;
}
get remaining() {
return this.options.info.session_start_limit.remaining;
}
get concurrency() {
return this.options.info.session_start_limit.max_concurrency;
}
get latency() {
let acc = 0;
this.forEach(s => (acc += s.latency));
return acc / this.size;
}
calculateShardId(guildId) {
return (0, common_1.calculateShardId)(guildId, this.totalShards);
}
create(shardId) {
this.debugger?.info(`Creating shard ${shardId}`);
let shard = this.get(shardId);
shard ??= new shard_1.Shard(shardId, {
token: this.options.token,
intents: this.options.intents,
info: { ...this.options.info, shards: this.totalShards },
handlePayload: this.options.handlePayload,
properties: this.options.properties,
debugger: this.debugger,
compress: this.options.compress ?? false,
presence: this.options.presence?.(shardId, -1),
});
this.set(shardId, shard);
return shard;
}
async spawnShards() {
const buckets = this.spawnBuckets();
this.debugger?.info('Spawning shards');
for (const bucket of buckets) {
for (const shard of bucket) {
if (!shard) {
break;
}
this.debugger?.info(`${shard.id} add to connect queue`);
this.connectQueue.push(shard.connect.bind(shard));
}
}
await this.startResharder();
}
async startResharder() {
if (this.options.resharding.interval <= 0)
return;
if (this.shardStart !== 0 || this.shardEnd !== this.totalShards)
return this.debugger?.debug('Cannot start resharder');
this.debugger?.debug('Resharder enabled');
setInterval(async () => {
this.debugger?.debug('Checking if reshard is needed');
const info = await this.options.resharding.getInfo();
if (info.shards <= this.totalShards)
return this.debugger?.debug('Resharding not needed');
//https://github.com/discordeno/discordeno/blob/6a5f446c0651b9fad9f1550ff1857fe7a026426b/packages/gateway/src/manager.ts#L106C8-L106C94
const percentage = (info.shards / ((this.totalShards * 2500) / 1000)) * 100;
if (percentage < this.options.resharding.percentage)
return this.debugger?.debug(`Percentage is not enough to reshard ${percentage}/${this.options.resharding.percentage}`);
this.debugger?.info('Starting resharding process');
this.connectQueue.concurrency = info.session_start_limit.max_concurrency;
this.options.info.session_start_limit.max_concurrency = info.session_start_limit.max_concurrency;
//waiting for all shards to connect
let shardsConnected = 0;
let handlePayload = (sharder, _, packet) => {
if (packet.t !== 'GUILDS_READY')
return;
if (++shardsConnected !== info.shards)
return;
cleanProcess(sharder);
// dont listen more events when all shards are ready
};
const cleanProcess = (sharder) => {
handlePayload = () => {
//
};
this.disconnectAll();
this.clear();
this.options.totalShards = this.options.shardEnd = this.options.info.shards = info.shards;
for (const [id, shard] of sharder) {
shard.options.handlePayload = (shardId, packet) => {
return this.options.handlePayload(shardId, packet);
};
this.set(id, shard);
}
sharder.clear();
};
const options = (0, common_1.MergeOptions)(this.options, {
totalShards: info.shards,
shardEnd: info.shards,
});
const resharder = new ShardManager({
...options,
resharding: {
// getInfo mock, we don't need it
getInfo: () => ({}),
interval: 0,
percentage: 0,
},
handlePayload: (shardId, packet) => {
return handlePayload(resharder, shardId, packet);
},
});
// share ratelimit
resharder.connectQueue = this.connectQueue;
await resharder.spawnShards();
}, this.options.resharding.interval);
}
/*
* spawns buckets in order
* https://discord.com/developers/docs/topics/gateway#sharding-max-concurrency
*/
spawnBuckets() {
this.debugger?.info('#0 Preparing buckets');
const chunks = structures_1.DynamicBucket.chunk(new Array(this.shardEnd - this.shardStart), this.concurrency);
chunks.forEach((arr, index) => {
for (let i = 0; i < arr.length; i++) {
const id = i + (index > 0 ? index * this.concurrency : 0) + this.shardStart;
chunks[index][i] = this.create(id);
}
});
this.debugger?.info(`${chunks.length} buckets created`);
return chunks;
}
forceIdentify(shardId) {
this.debugger?.info(`Shard #${shardId} force identify`);
return this.create(shardId).identify();
}
disconnect(shardId) {
this.debugger?.info(`Shard #${shardId} force disconnect`);
return this.get(shardId)?.disconnect();
}
disconnectAll() {
this.debugger?.info('Disconnect all shards');
this.forEach(shard => shard.disconnect());
}
setShardPresence(shardId, payload) {
this.debugger?.info(`Shard #${shardId} update presence`);
return this.send(shardId, {
op: types_1.GatewayOpcodes.PresenceUpdate,
d: payload,
});
}
setPresence(payload) {
this.forEach(shard => {
this.setShardPresence(shard.id, payload);
}, this);
}
joinVoice(guild_id, channel_id, options) {
const shardId = this.calculateShardId(guild_id);
this.debugger?.info(`Shard #${shardId} join voice ${channel_id} in ${guild_id}`);
return this.send(shardId, {
op: types_1.GatewayOpcodes.VoiceStateUpdate,
d: {
guild_id,
channel_id,
...options,
},
});
}
leaveVoice(guild_id) {
const shardId = this.calculateShardId(guild_id);
return this.send(shardId, {
op: types_1.GatewayOpcodes.VoiceStateUpdate,
d: {
guild_id,
channel_id: null,
self_mute: false,
self_deaf: false,
},
});
}
send(shardId, payload) {
if (workerData?.__USING_WATCHER__) {
return parentPort?.postMessage({
type: 'SEND_TO_SHARD',
shardId,
payload,
});
}
this.get(shardId)?.send(false, payload);
}
resume(shardId, shardData) {
if (this.has(shardId))
throw new Error('Cannot override existing shard');
const shard = this.create(shardId);
shard.data = shardData;
return this.connectQueue.push(shard.connect.bind(shard));
}
}
exports.ShardManager = ShardManager;