speed update, new table for cached stats

This commit is contained in:
Michael Simard
2021-08-04 16:32:17 -05:00
parent c38b621bdd
commit bc0dc93e9e
11 changed files with 485 additions and 139 deletions

View File

@@ -17,7 +17,6 @@ struct MatchController: RouteCollection {
matchRoute.delete("delete", "id",":id", use: deleteMatch)
matchRoute.post("update", "id",":id", use: updateMatch)
matchRoute.post("add", use: logMatch)
}
@@ -28,20 +27,21 @@ struct MatchController: RouteCollection {
let newMatch = try req.content.decode(Match.self)
return Match.find(id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap {
$0.update(newMatch: newMatch)
return $0.save(on: req.db)
}
.map { .ok }
.map {
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
}
return .ok
}
}
func deleteMatch(req: Request) throws -> EventLoopFuture<HTTPStatus> {
@@ -51,14 +51,36 @@ struct MatchController: RouteCollection {
return Match.find(id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { $0.delete(on: req.db) }
.map { .ok }
.map {
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
}
return .ok
}
}
func logMatch(req: Request) throws -> EventLoopFuture<Match> {
let newMatch = try req.content.decode(Match.self)
return newMatch.save(on: req.db).map { newMatch}
return newMatch.save(on: req.db).map {
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
}
return newMatch
}
}

View File

@@ -24,9 +24,140 @@ struct StatsController: RouteCollection {
statsRoute.get("history","page",":page", use: history)
statsRoute.get("history", use: history)
statsRoute.get("maps", use: mapRecords)
statsRoute.get("dashboard", use: dashboard)
}
func dashboard(db: Database) throws -> EventLoopFuture<DashboardStats> {
let statistics = WinLossRecords.query(on: db).all()
let adamAffectedMatches = Match.query(on: db).filter(\.$finalKillRuinedPlayerId == 6).count()
let totalMWGames = Match.query(on: db).filter(\.$codGame == "mw").count()
return (statistics.and(adamAffectedMatches).and(totalMWGames)).map { arg -> (DashboardStats) in
let (((statistics, adamAffectedMatches), totalMWGames)) = arg
// return statistics.map { statistics in
return DashboardStats(dashboardItems: statistics.map({ statisticItem in
let title = statisticItem.title
var title2:String? = ""
var content1 = ""
var content2 = ""
var sortOrder = 0
if statisticItem.codTrackerId == "no_hyder_overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 10
}
else if statisticItem.codTrackerId == "mw_overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 2
}
else if statisticItem.codTrackerId == "mw_six_players"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 6
}
else if statisticItem.codTrackerId == "2021_overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 5
}
// else if statisticItem.codTrackerId == "best_map_overall"{
// title2 = "Ratio"
// content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
// content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
//
//
// }
else if statisticItem.codTrackerId == "bocw_overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 3
}
// else if statisticItem.codTrackerId == "worst_map_overall"{
// title2 = "Ratio"
// content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
// content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
//
//
// }
else if statisticItem.codTrackerId == "with_hyder_overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 11
}
else if statisticItem.codTrackerId == "mw_five_players"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 7
}
else if statisticItem.codTrackerId == "casual_overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 4
}
else if statisticItem.codTrackerId == "overall_four_players"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 8
}
else if statisticItem.codTrackerId == "overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 1
}
else if statisticItem.codTrackerId == "2020_overall"{
title2 = "Ratio"
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
sortOrder = 9
}
else {
}
return DashboardItem(codTrackerId:statisticItem.codTrackerId, title: title, content: content1, title2: title2, content2: content2, sortOrder: sortOrder)
} ) +
[
DashboardItem(codTrackerId: "adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(adamAffectedMatches + 7)", title2: "", content2: "",sortOrder: 100),
DashboardItem(codTrackerId:"total_mw_games", title: "Total MW Games", content: "\(totalMWGames)", title2: "", content2: "", sortOrder: 0)
]
)
}
}
func dashboard(req: Request) throws -> EventLoopFuture<DashboardStats> {
return try dashboard(db: req.db)
}
func history(req: Request) throws -> EventLoopFuture<MatchHistory> {
@@ -85,9 +216,7 @@ struct StatsController: RouteCollection {
}
func getAllMatches(matches:[Match]) -> Stats{
let totals = matches.reduce([0,0]) { (totals, match) -> [Int] in
if match.win == true {
return [totals[0] + 1, totals[1]]
@@ -184,12 +313,10 @@ struct StatsController: RouteCollection {
}
}
}
func getStatsForYear(year:Int, req: Request) -> EventLoopFuture<Stats>{
func getStatsForYear(year:Int, db: Database) -> EventLoopFuture<Stats>{
// Specify date components
var dateComponents = DateComponents()
@@ -218,7 +345,7 @@ struct StatsController: RouteCollection {
let endDate = userCalendar.date(from: endDateComponenents)
return Match.query(on: req.db).filter(\.$date > startDate!).filter(\.$date < endDate!).all().map { (matches) -> (Stats) in
return Match.query(on: db).filter(\.$date > startDate!).filter(\.$date < endDate!).all().map { (matches) -> (Stats) in
return self.getCountedMatches(matches: matches)
}
}
@@ -237,10 +364,6 @@ struct StatsController: RouteCollection {
let dates = sortedMatches.suffix(30).map { (match) -> CODDate in
return CODDate(month: match.date.month, year: match.date.year, day: match.date.day, hour: match.date.hour, minute: match.date.minute)
}
//print ("MDP to dates \(Date().timeIntervalSince(startTime))")
return dates.reduce([CODDate]()) { (datesPlayed, codDate) -> [CODDate] in
if datesPlayed.contains(where: { (existingDate) -> Bool in
@@ -257,8 +380,6 @@ struct StatsController: RouteCollection {
}
}
func getCumulativeWinLossRatios(matches:[Match]) -> [DataPoint] {
let daysPlayed = getDaysPlayed(sortedMatches: matches)
@@ -483,34 +604,29 @@ struct StatsController: RouteCollection {
}
}
func overall(req: Request) throws -> EventLoopFuture<OverallStats> {
let startTime = Date()
let statsWithHyder = statsWithPlayer(req: req, playerId: 5)
let statsWithoutHyder = statsWithoutPlayer(req: req, playerId: 5)
let statsFor2020 = getStatsForYear(year: 2020, req: req)
let statsFor2021 = getStatsForYear(year: 2021, req: req)
// func cachedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
//
// }
func forceCalculatedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
let statsWithHyder = statsWithPlayer(db: db, playerId: 5)
let statsWithoutHyder = statsWithoutPlayer(db: db, playerId: 5)
let statsFor2020 = getStatsForYear(year: 2020, db: db)
let statsFor2021 = getStatsForYear(year: 2021, db: db)
let hyderFuture = statsWithHyder.and(statsWithoutHyder)
let hyderStats = hyderFuture.map { (withHyder, withoutHyder) -> [Stats] in
return [withHyder, withoutHyder]
//print ("Hyder done \(Date().timeIntervalSince(startTime))")
}
let matches = Match.query(on: req.db).sort( \.$date).all()
let matches = Match.query(on: db).sort( \.$date).all()
return matches.and(hyderStats).and(statsFor2020).and(statsFor2021).map { arg -> (OverallStats) in
return matches.and(hyderStats).and(statsFor2020).and(statsFor2021).map { arg -> ([String:Stats]) in
let (((matches, hyderStats), statsFor2020), statsFor2021) = arg
@@ -527,11 +643,11 @@ struct StatsController: RouteCollection {
var mwSixPlayers:Stats?
var mwFivePlayers:Stats?
var overallFourPlayers:Stats?
var mapStats:[Int:Stats]?
var worstMap:Int?
var bestMap:Int?
//
// var mapStats:[Int:Stats]?
// var worstMap:Int?
// var bestMap:Int?
//
group.enter()
queue.async {
overallStats = self.getStatsWithMostRecentDailyRecord(sortedMatches: matches)
@@ -559,42 +675,13 @@ struct StatsController: RouteCollection {
return match.codGame == "bocw" && self.shouldCountMatch(match: match )
}))
//print ("cw done \(Date().timeIntervalSince(startTime))")
group.leave()
}
group.enter()
queue.async {
mapStats = self.getMapStats(matches: matches)
//print ("maps done \(Date().timeIntervalSince(startTime))")
group.leave()
}
//
group.enter()
queue.async {
let mapStats = self.getMapStats(matches: matches)
bestMap = self.getBestMap(records: mapStats)
//print ("best done \(Date().timeIntervalSince(startTime))")
group.leave()
}
group.enter()
queue.async {
let mapStats = self.getMapStats(matches: matches)
worstMap = self.getWorstMap(records: mapStats)
//print ("worst done \(Date().timeIntervalSince(startTime))")
group.leave()
}
group.enter()
queue.async {
overallFourPlayers = self.getAllMatchesByPlayerCount(matches: matches, playerCount: 4) //Feb 1 21
overallFourPlayers = self.getAllMatchesByPlayerCount(matches: matches, playerCount: 4) //Feb
group.leave()
}
@@ -615,27 +702,111 @@ struct StatsController: RouteCollection {
group.wait()
let dashboardItems = [
// let dashboardItems:[DashboardItem] = [
//
// // DashboardItem(codTrackerId:"total_mw_games", title: "Total MW Games", content: "\(mwStats!.totalWins + mwStats!.totalLosses)" , title2:"", content2:""),
// DashboardItem(codTrackerId:"mw_overall",title: "MW Overall", content: mwStats!.record, title2: "Ratio", content2: mwStats!.winLossRatio),
// DashboardItem(codTrackerId:"mw_six_players", title: "MW 6 Players ", content: mwSixPlayers!.record, title2: "Ratio", content2: mwSixPlayers!.winLossRatio),
// DashboardItem(codTrackerId:"mw_five_players", title: "MW 5 Players ", content: mwFivePlayers!.record, title2: "Ratio", content2: mwFivePlayers!.winLossRatio),
// DashboardItem(codTrackerId:"overall_four_players", title: "4 Player Crew", content: overallFourPlayers!.record, title2: "Ratio", content2: overallFourPlayers!.winLossRatio),
// DashboardItem(codTrackerId:"overall", title: "Overall", content: overallStats!.record, title2: "Ratio", content2: overallStats!.winLossRatio),
// DashboardItem(codTrackerId:"2021_overall", title: "2021 Overall", content: statsFor2021.record, title2: "Ratio", content2: statsFor2021.winLossRatio),
// DashboardItem(codTrackerId:"2020_overall", title: "2020 Overall", content: statsFor2020.record, title2: "Ratio", content2: statsFor2020.winLossRatio),
//// DashboardItem(codTrackerId:"win_rate_overall", title: "Win Rate", content: "\(overallStats!.winRate)", title2: "", content2: ""),
// DashboardItem(codTrackerId:"bocw_overall", title: "Cold War Overall", content: bocwStats!.record, title2: "Ratio", content2: bocwStats!.winLossRatio),
// DashboardItem(codTrackerId:"with_hyder_overall", title: "With Hyder", content: hyderStats[0].record, title2: "Ratio", content2: hyderStats[0].winLossRatio),
// DashboardItem(codTrackerId:"no_hyder_overall", title: "No Hyder", content: hyderStats[1].record, title2: "Ratio", content2: hyderStats[1].winLossRatio),
//// DashboardItem(codTrackerId:"best_map_overall", title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)"),
//// DashboardItem(codTrackerId:"worst_map_overall", title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)"),
//// DashboardItem(codTrackerId:"adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: ""),
////
// ]
//
return [
"mw_overall":Stats(totalWins: mwStats!.totalWins, totalLosses: mwStats!.totalLosses),
"no_hyder_overall":Stats(totalWins: hyderStats[1].totalWins, totalLosses: hyderStats[1].totalLosses),
"with_hyder_overall":Stats(totalWins:hyderStats[0].totalWins, totalLosses: hyderStats[0].totalWins),
"bocw_overall":Stats(totalWins: bocwStats!.totalWins, totalLosses: bocwStats!.totalLosses),
"2020_overall":Stats(totalWins: statsFor2020.totalWins, totalLosses: statsFor2020.totalLosses),
"2021_overall":Stats(totalWins: statsFor2021.totalWins, totalLosses: statsFor2021.totalLosses),
"mw_six_players":Stats(totalWins: mwSixPlayers!.totalWins, totalLosses: mwSixPlayers!.totalLosses),
"overall_four_players":Stats(totalWins: overallFourPlayers!.totalWins, totalLosses: overallFourPlayers!.totalLosses),
"mw_five_players":Stats(totalWins: mwFivePlayers!.totalWins, totalLosses: mwFivePlayers!.totalLosses),
"overall":Stats(totalWins: overallStats!.totalWins, totalLosses: overallStats!.totalLosses),
DashboardItem(title: "Total MW Games", content: "\(mwStats!.totalWins + mwStats!.totalLosses)" , title2:"", content2:""),
DashboardItem(title: "MW Overall", content: mwStats!.record, title2: "Ratio", content2: mwStats!.winLossRatio),
DashboardItem(title: "MW 6 Players ", content: mwSixPlayers!.record, title2: "Ratio", content2: mwSixPlayers!.winLossRatio),
DashboardItem(title: "MW 5 Players ", content: mwFivePlayers!.record, title2: "Ratio", content2: mwFivePlayers!.winLossRatio),
DashboardItem(title: "4 Player Crew", content: overallFourPlayers!.record, title2: "Ratio", content2: overallFourPlayers!.winLossRatio),
DashboardItem(title: "Overall", content: overallStats!.record, title2: "Ratio", content2: overallStats!.winLossRatio),
DashboardItem(title: "2021 Overall", content: statsFor2021.record, title2: "Ratio", content2: statsFor2021.winLossRatio),
DashboardItem(title: "2020 Overall", content: statsFor2020.record, title2: "Ratio", content2: statsFor2020.winLossRatio),
DashboardItem(title: "Win Rate", content: "\(overallStats!.winRate)", title2: "", content2: ""),
DashboardItem(title: "Cold War Overall", content: bocwStats!.record, title2: "Ratio", content2: bocwStats!.winLossRatio),
DashboardItem(title: "With Hyder", content: hyderStats[0].record, title2: "Ratio", content2: hyderStats[0].winLossRatio),
DashboardItem(title: "No Hyder", content: hyderStats[1].record, title2: "Ratio", content2: hyderStats[1].winLossRatio),
DashboardItem(title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)"),
DashboardItem(title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)"),
DashboardItem(title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: ""),
]
return OverallStats(overall: overallStats!, mwStats: mwStats!, bocwStats: bocwStats!, mostRecentRecord: "Temporarily Unavailable", statsWithHyder:hyderStats[0], statsWithoutHyder: hyderStats[1], dashboardItems:dashboardItems)
}
}
func overall(db: Database) throws -> EventLoopFuture<OverallStats> {
let dashboardStats = try dashboard(db:db)
let matches = Match.query(on: db).sort( \.$date).all()
return matches.and(dashboardStats).map { arg -> (OverallStats) in
let (matches, dashboardStats) = arg
let queue = DispatchQueue(label: "com.sledsoft.cod-tracker.queue", attributes: .concurrent)
let group = DispatchGroup()
var mapStats:[Int:Stats]?
var worstMap:Int?
var bestMap:Int?
//
group.enter()
queue.async {
mapStats = self.getMapStats(matches: matches)
//print ("maps done \(Date().timeIntervalSince(startTime))")
group.leave()
}
//
group.enter()
queue.async {
let mapStats = self.getMapStats(matches: matches)
bestMap = self.getBestMap(records: mapStats)
group.leave()
}
group.enter()
queue.async {
let mapStats = self.getMapStats(matches: matches)
worstMap = self.getWorstMap(records: mapStats)
group.leave()
}
group.wait()
let dashboardItems:[DashboardItem] =
(dashboardStats.dashboardItems +
[
DashboardItem(codTrackerId:"best_map_overall", title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)", sortOrder: 12),
DashboardItem(codTrackerId:"worst_map_overall", title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)",sortOrder: 13),
DashboardItem(codTrackerId:"adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: "",sortOrder: 14),
]).sorted{
$0.sortOrder < $1.sortOrder
}
return OverallStats(dashboardItems: dashboardItems)
}
}
func overall(req: Request) throws -> EventLoopFuture<OverallStats> {
return try overall(db: req.db)
}
@@ -722,8 +893,8 @@ struct StatsController: RouteCollection {
}
func statsWithPlayer(req: Request, playerId:Int) -> EventLoopFuture<Stats> {
return Match.query(on: req.db)
func statsWithPlayer(db: Database, playerId:Int) -> EventLoopFuture<Stats> {
return Match.query(on: db)
.filter(\.$players ~~ "\(playerId)")
.all().map { (matches) -> (Stats) in
return self.getCountedMatches(matches: matches)
@@ -731,8 +902,8 @@ struct StatsController: RouteCollection {
}
func statsWithoutPlayer (req: Request, playerId:Int) -> EventLoopFuture<Stats> {
return Match.query(on: req.db)
func statsWithoutPlayer (db: Database, playerId:Int) -> EventLoopFuture<Stats> {
return Match.query(on: db)
.filter(\.$players !~ "\(playerId)")
.all().map { (matches) -> (Stats) in
return self.getCountedMatches(matches: matches)