<commit_before>/**
 * Matchmaker
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * This keeps track of challenges to battle made between users, setting up
 * matches between users looking for a battle, and starting new battles.
 *
 * @License MIT License
 */

'use strict';

const PERIODIC_MATCH_INTERVAL = 60 * 1000;

class Search {
	constructor(userid, team, rating = 1000) {
		this.userid = userid;
		this.team = team;
		this.rating = rating;
		this.time = 0;
	}

	setRating(rating) {
		this.rating = rating;
	}

	setStart() {
		this.time = Date.now();
	}
}

class Matchmaker {
	constructor() {
		this.searches = new Map();
		this.periodicMatchInterval = setInterval(
			() => this.periodicMatch(),
			PERIODIC_MATCH_INTERVAL
		);
	}

	cancelSearch(user, format) {
		if (format && !user.searching[format]) return false;
		let searchedFormats = Object.keys(user.searching);
		if (!searchedFormats.length) return false;

		for (let searchedFormat of searchedFormats) {
			if (format && searchedFormat !== format) continue;
			let formatSearches = this.searches.get(searchedFormat);
			for (let search of formatSearches) {
				if (search.userid !== user.userid) continue;
				formatSearches.delete(search);
				delete user.searching[searchedFormat];
				break;
			}
		}

		user.updateSearch();
		return true;
	}

	searchBattle(user, formatid) {
		if (!user.connected) return;
		formatid = Dex.getFormat(formatid).id;
		return user.prepBattle(formatid, 'search', null)
			.then(validTeam => this.finishSearchBattle(user, formatid, validTeam));
	}

	finishSearchBattle(user, formatid, validTeam) {
		if (validTeam === false) return;

		const search = new Search(user.userid, validTeam);
		// Get the user's rating before actually starting to search.
		Ladders(formatid).getRating(user.userid).then(rating => {
			search.setRating(rating);
			search.setStart();
			this.addSearch(search, user, formatid);
		}, error => {
			// Rejects if we retrieved the rating but the user had changed their name;
			// the search simply doesn't happen in this case.
		});
	}

	matchmakingOK(search1, search2, user1, user2, formatid) {
		// This should never happen.
		if (!user1 || !user2) {
			return void require('./crashlogger')(new Error(`Matched user ${user1 ? search2.userid : search1.userid} not found`), "The main process");
		}

		// users must be different
		if (user1 === user2) return false;

		// users must have different IPs
		if (user1.latestIp === user2.latestIp) return false;

		// users must not have been matched immediately previously
		if (user1.lastMatch === user2.userid || user2.lastMatch === user1.userid) return false;

		// search must be within range
		let searchRange = 100, elapsed = Date.now() - Math.min(search1.time, search2.time);
		if (formatid === 'gen7ou' || formatid === 'gen7oucurrent' ||
				formatid === 'gen7oususpecttest' || formatid === 'gen7randombattle') {
			searchRange = 50;
		}

		searchRange += elapsed / 300; // +1 every .3 seconds
		if (searchRange > 300) searchRange = 300 + (searchRange - 300) / 10; // +1 every 3 sec after 300
		if (searchRange > 600) searchRange = 600;<commit_msg>Matchmaker: Parallelize rating retrieval and team validation<commit_after>/**
 * Matchmaker
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * This keeps track of challenges to battle made between users, setting up
 * matches between users looking for a battle, and starting new battles.
 *
 * @License MIT License
 */

'use strict';

const PERIODIC_MATCH_INTERVAL = 60 * 1000;

class Search {
	constructor(userid, team, rating = 1000) {
		this.userid = userid;
		this.team = team;
		this.rating = rating;
		this.time = Date.now();
	}

	setRating(rating) {
		this.rating = rating;
	}

	setStart() {
		this.time = Date.now();
	}
}

class Matchmaker {
	constructor() {
		this.searches = new Map();
		this.periodicMatchInterval = setInterval(
			() => this.periodicMatch(),
			PERIODIC_MATCH_INTERVAL
		);
	}

	cancelSearch(user, format) {
		if (format && !user.searching[format]) return false;
		let searchedFormats = Object.keys(user.searching);
		if (!searchedFormats.length) return false;

		for (let searchedFormat of searchedFormats) {
			if (format && searchedFormat !== format) continue;
			let formatSearches = this.searches.get(searchedFormat);
			for (let search of formatSearches) {
				if (search.userid !== user.userid) continue;
				formatSearches.delete(search);
				delete user.searching[searchedFormat];
				break;
			}
		}

		user.updateSearch();
		return true;
	}

	searchBattle(user, formatid) {
		if (!user.connected) return;
		formatid = Dex.getFormat(formatid).id;

		return Promise.all([
			user.prepBattle(formatid, 'search', null),
			Ladders(formatid).getRating(user.userid),
		]).then(([validTeam, rating]) => {
			return this.finishSearchBattle(user, formatid, validTeam, rating);
		}, err => {
			// Rejects iff we retrieved the rating but the user had changed their name;
			// the search simply doesn't happen in this case.
		});
	}

	finishSearchBattle(user, formatid, validTeam, rating) {
		if (validTeam === false) return;

		const search = new Search(user.userid, validTeam, rating);
		this.addSearch(search, user, formatid);
	}

	matchmakingOK(search1, search2, user1, user2, formatid) {
		// This should never happen.
		if (!user1 || !user2) {
			return void require('./crashlogger')(new Error(`Matched user ${user1 ? search2.userid : search1.userid} not found`), "The main process");
		}

		// users must be different
		if (user1 === user2) return false;

		// users must have different IPs
		if (user1.latestIp === user2.latestIp) return false;

		// users must not have been matched immediately previously
		if (user1.lastMatch === user2.userid || user2.lastMatch === user1.userid) return false;

		// search must be within range
		let searchRange = 100, elapsed = Date.now() - Math.min(search1.time, search2.time);
		if (formatid === 'gen7ou' || formatid === 'gen7oucurrent' ||
				formatid === 'gen7oususpecttest' || formatid === 'gen7randombattle') {
			searchRange = 50;
		}

		searchRange += elapsed / 300; // +1 every .3 seconds
		if (searchRange > 300) searchRange = 300 + (searchRange - 300) / 10; // +1 every 3 sec after 300
		if (searchRange > 600) searchRange = 600;
{'fraction_non_alphanumeric': 0.13056, 'fraction_numerical': 0.02096, 'mean_word_length': 3.8084615384615383, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4187856', 'n_tokens_mistral': 2290, 'n_tokens_neox': 1994, 'n_words': 564}