mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 08:03:30 +01:00
feat(Shard): shard-specific broadcastEval/fetchClientValues + shard Id util (#4991)
This commit is contained in:
committed by
GitHub
parent
643f96c79b
commit
2a6c363a8a
@@ -2,10 +2,10 @@
|
|||||||
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
||||||
"plugins": ["import"],
|
"plugins": ["import"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2019
|
"ecmaVersion": 2020
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es2020": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"overrides": [{ "files": ["*.browser.js"], "env": { "browser": true } }],
|
"overrides": [{ "files": ["*.browser.js"], "env": { "browser": true } }],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const Messages = {
|
|||||||
DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.',
|
DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.',
|
||||||
SHARDING_NO_SHARDS: 'No shards have been spawned.',
|
SHARDING_NO_SHARDS: 'No shards have been spawned.',
|
||||||
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
|
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
|
||||||
|
SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`,
|
||||||
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,
|
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,
|
||||||
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`,
|
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`,
|
||||||
SHARDING_WORKER_EXISTS: id => `Shard ${id} already has an active worker.`,
|
SHARDING_WORKER_EXISTS: id => `Shard ${id} already has an active worker.`,
|
||||||
@@ -28,6 +29,8 @@ const Messages = {
|
|||||||
SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`,
|
SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`,
|
||||||
SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`,
|
SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`,
|
||||||
SHARDING_NO_CHILD_EXISTS: id => `Shard ${id} has no active process or worker.`,
|
SHARDING_NO_CHILD_EXISTS: id => `Shard ${id} has no active process or worker.`,
|
||||||
|
SHARDING_SHARD_MISCALCULATION: (shard, guild, count) =>
|
||||||
|
`Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`,
|
||||||
|
|
||||||
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
|
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
|
||||||
COLOR_CONVERT: 'Unable to convert color to a number.',
|
COLOR_CONVERT: 'Unable to convert color to a number.',
|
||||||
|
|||||||
@@ -334,18 +334,20 @@ class Shard extends EventEmitter {
|
|||||||
|
|
||||||
// Shard is requesting a property fetch
|
// Shard is requesting a property fetch
|
||||||
if (message._sFetchProp) {
|
if (message._sFetchProp) {
|
||||||
this.manager.fetchClientValues(message._sFetchProp).then(
|
const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard };
|
||||||
results => this.send({ _sFetchProp: message._sFetchProp, _result: results }),
|
this.manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then(
|
||||||
err => this.send({ _sFetchProp: message._sFetchProp, _error: Util.makePlainError(err) }),
|
results => this.send({ ...resp, _result: results }),
|
||||||
|
err => this.send({ ...resp, _error: Util.makePlainError(err) }),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shard is requesting an eval broadcast
|
// Shard is requesting an eval broadcast
|
||||||
if (message._sEval) {
|
if (message._sEval) {
|
||||||
this.manager.broadcastEval(message._sEval).then(
|
const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
|
||||||
results => this.send({ _sEval: message._sEval, _result: results }),
|
this.manager.broadcastEval(message._sEval, message._sEvalShard).then(
|
||||||
err => this.send({ _sEval: message._sEval, _error: Util.makePlainError(err) }),
|
results => this.send({ ...resp, _result: results }),
|
||||||
|
err => this.send({ ...resp, _error: Util.makePlainError(err) }),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,28 +96,29 @@ class ShardClientUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a client property value of each shard.
|
* Fetches a client property value of each shard, or a given shard.
|
||||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||||
* @returns {Promise<Array<*>>}
|
* @param {number} [shard] Shard to fetch property from, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>}
|
||||||
* @example
|
* @example
|
||||||
* client.shard.fetchClientValues('guilds.cache.size')
|
* client.shard.fetchClientValues('guilds.cache.size')
|
||||||
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
* @see {@link ShardingManager#fetchClientValues}
|
* @see {@link ShardingManager#fetchClientValues}
|
||||||
*/
|
*/
|
||||||
fetchClientValues(prop) {
|
fetchClientValues(prop, shard) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const parent = this.parentPort || process;
|
const parent = this.parentPort || process;
|
||||||
|
|
||||||
const listener = message => {
|
const listener = message => {
|
||||||
if (!message || message._sFetchProp !== prop) return;
|
if (!message || message._sFetchProp !== prop || message._sFetchPropShard !== shard) return;
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
if (!message._error) resolve(message._result);
|
if (!message._error) resolve(message._result);
|
||||||
else reject(Util.makeError(message._error));
|
else reject(Util.makeError(message._error));
|
||||||
};
|
};
|
||||||
parent.on('message', listener);
|
parent.on('message', listener);
|
||||||
|
|
||||||
this.send({ _sFetchProp: prop }).catch(err => {
|
this.send({ _sFetchProp: prop, _sFetchPropShard: shard }).catch(err => {
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
@@ -125,29 +126,30 @@ class ShardClientUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a script or function on all shards, in the context of the {@link Client}s.
|
* Evaluates a script or function on all shards, or a given shard, in the context of the {@link Client}s.
|
||||||
* @param {string|Function} script JavaScript to run on each shard
|
* @param {string|Function} script JavaScript to run on each shard
|
||||||
* @returns {Promise<Array<*>>} Results of the script execution
|
* @param {number} [shard] Shard to run script on, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
|
||||||
* @example
|
* @example
|
||||||
* client.shard.broadcastEval('this.guilds.cache.size')
|
* client.shard.broadcastEval('this.guilds.cache.size')
|
||||||
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
* @see {@link ShardingManager#broadcastEval}
|
* @see {@link ShardingManager#broadcastEval}
|
||||||
*/
|
*/
|
||||||
broadcastEval(script) {
|
broadcastEval(script, shard) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const parent = this.parentPort || process;
|
const parent = this.parentPort || process;
|
||||||
script = typeof script === 'function' ? `(${script})(this)` : script;
|
script = typeof script === 'function' ? `(${script})(this)` : script;
|
||||||
|
|
||||||
const listener = message => {
|
const listener = message => {
|
||||||
if (!message || message._sEval !== script) return;
|
if (!message || message._sEval !== script || message._sEvalShard !== shard) return;
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
if (!message._error) resolve(message._result);
|
if (!message._error) resolve(message._result);
|
||||||
else reject(Util.makeError(message._error));
|
else reject(Util.makeError(message._error));
|
||||||
};
|
};
|
||||||
parent.on('message', listener);
|
parent.on('message', listener);
|
||||||
|
|
||||||
this.send({ _sEval: script }).catch(err => {
|
this.send({ _sEval: script, _sEvalShard: shard }).catch(err => {
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
@@ -224,6 +226,18 @@ class ShardClientUtil {
|
|||||||
}
|
}
|
||||||
return this._singleton;
|
return this._singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the shard ID for a given guild ID.
|
||||||
|
* @param {Snowflake} guildID Snowflake guild ID to get shard ID for
|
||||||
|
* @param {number} shardCount Number of shards
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static shardIDForGuildID(guildID, shardCount) {
|
||||||
|
const shard = Number(BigInt(guildID) >> 22n) % shardCount;
|
||||||
|
if (shard < 0) throw new Error('SHARDING_SHARD_MISCALCULATION', shard, guildID, shardCount);
|
||||||
|
return shard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ShardClientUtil;
|
module.exports = ShardClientUtil;
|
||||||
|
|||||||
@@ -222,30 +222,48 @@ class ShardingManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a script on all shards, in the context of the {@link Client}s.
|
* Evaluates a script on all shards, or a given shard, in the context of the {@link Client}s.
|
||||||
* @param {string} script JavaScript to run on each shard
|
* @param {string} script JavaScript to run on each shard
|
||||||
* @returns {Promise<Array<*>>} Results of the script execution
|
* @param {number} [shard] Shard to run on, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
|
||||||
*/
|
*/
|
||||||
broadcastEval(script) {
|
broadcastEval(script, shard) {
|
||||||
const promises = [];
|
return this._performOnShards('eval', [script], shard);
|
||||||
for (const shard of this.shards.values()) promises.push(shard.eval(script));
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a client property value of each shard.
|
* Fetches a client property value of each shard, or a given shard.
|
||||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||||
* @returns {Promise<Array<*>>}
|
* @param {number} [shard] Shard to fetch property from, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>}
|
||||||
* @example
|
* @example
|
||||||
* manager.fetchClientValues('guilds.cache.size')
|
* manager.fetchClientValues('guilds.cache.size')
|
||||||
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
fetchClientValues(prop) {
|
fetchClientValues(prop, shard) {
|
||||||
|
return this._performOnShards('fetchClientValue', [prop], shard);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a method with given arguments on all shards, or a given shard.
|
||||||
|
* @param {string} method Method name to run on each shard
|
||||||
|
* @param {Array<*>} args Arguments to pass through to the method call
|
||||||
|
* @param {number} [shard] Shard to run on, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>} Results of the method execution
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_performOnShards(method, args, shard) {
|
||||||
if (this.shards.size === 0) return Promise.reject(new Error('SHARDING_NO_SHARDS'));
|
if (this.shards.size === 0) return Promise.reject(new Error('SHARDING_NO_SHARDS'));
|
||||||
if (this.shards.size !== this.shardList.length) return Promise.reject(new Error('SHARDING_IN_PROCESS'));
|
if (this.shards.size !== this.shardList.length) return Promise.reject(new Error('SHARDING_IN_PROCESS'));
|
||||||
|
|
||||||
|
if (typeof shard === 'number') {
|
||||||
|
if (this.shards.has(shard)) return this.shards.get(shard)[method](...args);
|
||||||
|
return Promise.reject(new Error('SHARDING_SHARD_NOT_FOUND', shard));
|
||||||
|
}
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (const shard of this.shards.values()) promises.push(shard.fetchClientValue(prop));
|
for (const sh of this.shards.values()) promises.push(sh[method](...args));
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,15 @@ class SnowflakeUtil {
|
|||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discord's epoch value (2015-01-01T00:00:00.000Z).
|
||||||
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
static get EPOCH() {
|
||||||
|
return EPOCH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SnowflakeUtil;
|
module.exports = SnowflakeUtil;
|
||||||
|
|||||||
9
typings/index.d.ts
vendored
9
typings/index.d.ts
vendored
@@ -1352,12 +1352,16 @@ declare module 'discord.js' {
|
|||||||
public mode: ShardingManagerMode;
|
public mode: ShardingManagerMode;
|
||||||
public parentPort: any | null;
|
public parentPort: any | null;
|
||||||
public broadcastEval(script: string): Promise<any[]>;
|
public broadcastEval(script: string): Promise<any[]>;
|
||||||
|
public broadcastEval(script: string, shard: number): Promise<any>;
|
||||||
public broadcastEval<T>(fn: (client: Client) => T): Promise<T[]>;
|
public broadcastEval<T>(fn: (client: Client) => T): Promise<T[]>;
|
||||||
|
public broadcastEval<T>(fn: (client: Client) => T, shard: number): Promise<T>;
|
||||||
public fetchClientValues(prop: string): Promise<any[]>;
|
public fetchClientValues(prop: string): Promise<any[]>;
|
||||||
|
public fetchClientValues(prop: string, shard: number): Promise<any>;
|
||||||
public respawnAll(shardDelay?: number, respawnDelay?: number, spawnTimeout?: number): Promise<void>;
|
public respawnAll(shardDelay?: number, respawnDelay?: number, spawnTimeout?: number): Promise<void>;
|
||||||
public send(message: any): Promise<void>;
|
public send(message: any): Promise<void>;
|
||||||
|
|
||||||
public static singleton(client: Client, mode: ShardingManagerMode): ShardClientUtil;
|
public static singleton(client: Client, mode: ShardingManagerMode): ShardClientUtil;
|
||||||
|
public static shardIDForGuildID(guildID: Snowflake, shardCount: number): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ShardingManager extends EventEmitter {
|
export class ShardingManager extends EventEmitter {
|
||||||
@@ -1373,6 +1377,8 @@ declare module 'discord.js' {
|
|||||||
execArgv?: string[];
|
execArgv?: string[];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
private _performOnShards(method: string, args: any[]): Promise<any[]>;
|
||||||
|
private _performOnShards(method: string, args: any[], shard: number): Promise<any>;
|
||||||
|
|
||||||
public file: string;
|
public file: string;
|
||||||
public respawn: boolean;
|
public respawn: boolean;
|
||||||
@@ -1382,8 +1388,10 @@ declare module 'discord.js' {
|
|||||||
public totalShards: number | 'auto';
|
public totalShards: number | 'auto';
|
||||||
public broadcast(message: any): Promise<Shard[]>;
|
public broadcast(message: any): Promise<Shard[]>;
|
||||||
public broadcastEval(script: string): Promise<any[]>;
|
public broadcastEval(script: string): Promise<any[]>;
|
||||||
|
public broadcastEval(script: string, shard: number): Promise<any>;
|
||||||
public createShard(id: number): Shard;
|
public createShard(id: number): Shard;
|
||||||
public fetchClientValues(prop: string): Promise<any[]>;
|
public fetchClientValues(prop: string): Promise<any[]>;
|
||||||
|
public fetchClientValues(prop: string, shard: number): Promise<any>;
|
||||||
public respawnAll(
|
public respawnAll(
|
||||||
shardDelay?: number,
|
shardDelay?: number,
|
||||||
respawnDelay?: number,
|
respawnDelay?: number,
|
||||||
@@ -1399,6 +1407,7 @@ declare module 'discord.js' {
|
|||||||
export class SnowflakeUtil {
|
export class SnowflakeUtil {
|
||||||
public static deconstruct(snowflake: Snowflake): DeconstructedSnowflake;
|
public static deconstruct(snowflake: Snowflake): DeconstructedSnowflake;
|
||||||
public static generate(timestamp?: number | Date): Snowflake;
|
public static generate(timestamp?: number | Date): Snowflake;
|
||||||
|
public static readonly EPOCH: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Speaking extends BitField<SpeakingString> {
|
export class Speaking extends BitField<SpeakingString> {
|
||||||
|
|||||||
Reference in New Issue
Block a user