From 32ff0f6f36b03829e33967cea18e960ef16a6d5b Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sat, 20 Apr 2024 22:58:10 +0800 Subject: [PATCH 01/29] Add visible ranks --- .../Storyboards/Base.lproj/Main.storyboard | 77 ++++++++++++++++++- .../Metrics/Ranking/RankingEngine.swift | 11 +++ .../PlayerStatsViewController.swift | 68 +++++++++++++--- 3 files changed, 145 insertions(+), 11 deletions(-) diff --git a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard index df5beba4..e2dca698 100644 --- a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard +++ b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard @@ -91,6 +91,7 @@ + @@ -121,7 +122,6 @@ - @@ -336,6 +336,57 @@ + + + + + + + + @@ -343,28 +394,42 @@ + + + + + + + + + + + + + + @@ -377,16 +442,26 @@ + + + + + + + + + + diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift index f24c8d2c..90dc6bb2 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -55,4 +55,15 @@ class RankingEngine: InferenceEngine, InferenceDataDelegate { func getPermanentValueFor(_ stat: T.Type) -> Double { statisticsDatabase.getStatistic(for: stat.asType)?.permanentValue ?? .zero } + + func percentageToNextRank() -> Double { + let minScore = currentRank.valueRange.lowerBound + let currentScore = Int(currentExp) + let maxScore = currentRank.valueRange.upperBound + let range = maxScore - minScore + let adjustedScore = currentScore - minScore + + return Double(adjustedScore / range) + + } } diff --git a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift index b5967fdf..6515dc24 100644 --- a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift @@ -22,6 +22,15 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl @IBOutlet private var kdRatio: UILabel! @IBOutlet private var totalGames: UILabel! + @IBOutlet private var pte: UILabel! + @IBOutlet private var cpl: UILabel! + @IBOutlet private var sgt: UILabel! + @IBOutlet private var lta: UILabel! + @IBOutlet private var cpt: UILabel! + @IBOutlet private var maj: UILabel! + @IBOutlet private var col: UILabel! + @IBOutlet private var gen: UILabel! + var statsEngine = StatisticsEngine() var achievements: AchievementsDatabase = getAchievements() @@ -70,15 +79,15 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl achievementsView.allowsSelection = false missionsView.allowsSelection = false - // rankImageView.image = UIImage(named: currentRank.imageIdentifer) - rankNameLabel.text = String("--- Rank: \(rank.rawValue) ---") - characterImage.image = rank.isOfficer() ? UIImage(named: "Shooter-1") : UIImage(named: "melee-1") - currentExp.text = String("XP: \(exp)") - totalKills.text = String("Kills: \(kills)") - totalDeaths.text = String("Deaths: \(deaths)") - totalGames.text = String("Games: \(games)") - kdRatio.text = String("K/D Ratio: ") + String(format: "%.2f", kd) + // initializeBackground() + + initializePlayerStats() + initializeRanks() + highlightCurrentRank() + reloadAchievements() + } + func initializeBackground() { /* TODO: Add background image let backgroundImage = UIImageView(frame: UIScreen.main.bounds) backgroundImage.image = UIImage(named: "stone-tile") @@ -86,11 +95,50 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl view.addSubview(backgroundImage) // Add the image view to the view hierarchy view.sendSubviewToBack(backgroundImage) // Send the image to the background */ + } - reloadAchievements() + func initializePlayerStats() { + // rankImageView.image = UIImage(named: currentRank.imageIdentifer) + rankNameLabel.text = String("--- Rank: \(rank.rawValue) ---") + characterImage.image = rank.isOfficer() ? UIImage(named: "Shooter-1") : UIImage(named: "melee-1") + currentExp.text = String("XP: \(exp)") + totalKills.text = String("Kills: \(kills)") + totalDeaths.text = String("Deaths: \(deaths)") + totalGames.text = String("Games: \(games)") + kdRatio.text = String("K/D Ratio: ") + String(format: "%.2f", kd) + } + + func initializeRanks() { + pte.text = "PTE" + cpl.text = "CPL" + sgt.text = "SGT" + lta.text = "LTA" + cpt.text = "CPT" + maj.text = "MAJ" + col.text = "COL" + gen.text = "GEN" } - // MARK: - Table view data source + func highlightCurrentRank() { + switch rank { + case .PRIVATE: + pte.textColor = .red + case .CORPORAL: + cpl.textColor = .red + case .SERGEANT: + sgt.textColor = .red + case .LIEUTENANT: + lta.textColor = .red + case .CAPTAIN: + cpt.textColor = .red + case .MAJOR: + maj.textColor = .red + case .COLONEL: + col.textColor = .red + case .GENERAL: + gen.textColor = .red + } + } func numberOfSections(in tableView: UITableView) -> Int { 1 From 19df9a8c575da51b2818187cd4f75a7771cf1036 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sat, 20 Apr 2024 23:05:36 +0800 Subject: [PATCH 02/29] Add progress view for ranks --- .../AppMain/Storyboards/Base.lproj/Main.storyboard | 7 +++++++ .../ViewControllers/PlayerStatsViewController.swift | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard index e2dca698..d8f26c83 100644 --- a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard +++ b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard @@ -387,6 +387,9 @@ + + + @@ -402,6 +405,7 @@ + @@ -414,6 +418,7 @@ + @@ -430,6 +435,7 @@ + @@ -461,6 +467,7 @@ + diff --git a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift index 6515dc24..4825a00d 100644 --- a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift @@ -15,6 +15,7 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl @IBOutlet private var rankNameLabel: UILabel! @IBOutlet private var characterImage: UIImageView! + @IBOutlet private var rankProgress: UIProgressView! @IBOutlet private var currentExp: UILabel! @IBOutlet private var totalKills: UILabel! @@ -106,6 +107,9 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl totalDeaths.text = String("Deaths: \(deaths)") totalGames.text = String("Games: \(games)") kdRatio.text = String("K/D Ratio: ") + String(format: "%.2f", kd) + + rankProgress.progress = Float(rankingEngine.percentageToNextRank()) + Logger.log("Current progress is \(rankProgress.progress)", self) } func initializeRanks() { From ccaa4087cfa3d2d5ad12fa25b57feb5756b0d6df Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sat, 20 Apr 2024 23:14:38 +0800 Subject: [PATCH 03/29] Add rank and rank progressUI --- .../AppMain/Storyboards/Base.lproj/Main.storyboard | 7 +++++++ TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard index d8f26c83..cb0228d0 100644 --- a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard +++ b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard @@ -399,10 +399,13 @@ + + + @@ -426,6 +429,7 @@ + @@ -434,6 +438,7 @@ + @@ -441,9 +446,11 @@ + + diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift index 90dc6bb2..b40be392 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -60,10 +60,11 @@ class RankingEngine: InferenceEngine, InferenceDataDelegate { let minScore = currentRank.valueRange.lowerBound let currentScore = Int(currentExp) let maxScore = currentRank.valueRange.upperBound - let range = maxScore - minScore - let adjustedScore = currentScore - minScore + let range = Double(maxScore - minScore) + let adjustedScore = Double(currentScore - minScore) + let percentageToNextRank = adjustedScore / range - return Double(adjustedScore / range) + return percentageToNextRank } } From 536e0bea9ddf1a5736e5afab8a4b2e32005c5f6d Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sat, 20 Apr 2024 23:18:15 +0800 Subject: [PATCH 04/29] Add minor fixes to extensions --- .../Commons/Extensions/Double+Extensions.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TowerForge/TowerForge/Commons/Extensions/Double+Extensions.swift b/TowerForge/TowerForge/Commons/Extensions/Double+Extensions.swift index 2e4ff8b2..50f41344 100644 --- a/TowerForge/TowerForge/Commons/Extensions/Double+Extensions.swift +++ b/TowerForge/TowerForge/Commons/Extensions/Double+Extensions.swift @@ -8,7 +8,7 @@ import Foundation extension Double { - static var unit: Double { 1.0 } + static let unit: Double = 1.0 var half: Double { self * 0.5 } var twice: Double { self * 2.0 } var oneHalf: Double { self * 1.5 } @@ -18,13 +18,13 @@ extension Double { } extension Int { - static var unit: Int { 1 } - static var zero: Int { 0 } - static var negativeUnit: Int { -1 } + static let unit: Int = 1 + static let zero: Int = 0 + static let negativeUnit: Int = -1 } extension CGFloat { - static var unit: Double { Double.unit } + static let unit = CGFloat(Double.unit) var half: Double { Double(self).half } var twice: Double { Double(self).twice } var square: Double { Double(self).square } From da5b210e39058bc576655d6cf2abaf79836b079f Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sat, 20 Apr 2024 23:41:42 +0800 Subject: [PATCH 05/29] Add mass effect mission --- .../TowerForge.xcodeproj/project.pbxproj | 4 +++ .../Implemented/MassEffectMission.swift | 27 +++++++++++++++++++ .../Metrics/Missions/MissionsFactory.swift | 3 ++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 TowerForge/TowerForge/Metrics/Missions/Implemented/MassEffectMission.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 30da90a2..bca88c36 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -200,6 +200,7 @@ BA436ADF2BD3CE9600BE3E4F /* CustomMissionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436ADE2BD3CE9600BE3E4F /* CustomMissionCell.swift */; }; BA436AE12BD3D66800BE3E4F /* MassKillMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AE02BD3D66800BE3E4F /* MassKillMission.swift */; }; BA436AE32BD3D6FF00BE3E4F /* MassDeathMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AE22BD3D6FF00BE3E4F /* MassDeathMission.swift */; }; + BA436AE72BD4180400BE3E4F /* MassEffectMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AE62BD4180400BE3E4F /* MassEffectMission.swift */; }; BA443D3D2BAD9557009F0FFB /* RemoveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */; }; BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */; }; BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D412BAD9885009F0FFB /* DamageEventTests.swift */; }; @@ -476,6 +477,7 @@ BA436ADE2BD3CE9600BE3E4F /* CustomMissionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMissionCell.swift; sourceTree = ""; }; BA436AE02BD3D66800BE3E4F /* MassKillMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MassKillMission.swift; sourceTree = ""; }; BA436AE22BD3D6FF00BE3E4F /* MassDeathMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MassDeathMission.swift; sourceTree = ""; }; + BA436AE62BD4180400BE3E4F /* MassEffectMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MassEffectMission.swift; sourceTree = ""; }; BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSystem.swift; sourceTree = ""; }; BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEvent.swift; sourceTree = ""; }; BA443D412BAD9885009F0FFB /* DamageEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageEventTests.swift; sourceTree = ""; }; @@ -1138,6 +1140,7 @@ BA82C78D2BCD2D2B000515A0 /* MassDamageMission.swift */, BA436AE02BD3D66800BE3E4F /* MassKillMission.swift */, BA436AE22BD3D6FF00BE3E4F /* MassDeathMission.swift */, + BA436AE62BD4180400BE3E4F /* MassEffectMission.swift */, ); path = Implemented; sourceTree = ""; @@ -1604,6 +1607,7 @@ 3C9955A12BA47DA500D33FA5 /* BaseTower.swift in Sources */, BAEC99FA2BD13F2600E0C437 /* AbstractTypeWrapper.swift in Sources */, 5299D1432BC3AB38003EF746 /* GameRankProvider.swift in Sources */, + BA436AE72BD4180400BE3E4F /* MassEffectMission.swift in Sources */, 5250B42F2BAE0DB000F16CF6 /* LabelComponent.swift in Sources */, 3CCF9CB32BAB1F42004D170E /* SystemManager.swift in Sources */, 5295A20F2BAAE7CF005018A8 /* TeamController.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Missions/Implemented/MassEffectMission.swift b/TowerForge/TowerForge/Metrics/Missions/Implemented/MassEffectMission.swift new file mode 100644 index 00000000..301c07cb --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Missions/Implemented/MassEffectMission.swift @@ -0,0 +1,27 @@ +// +// MassEffectMission.swift +// TowerForge +// +// Created by Rubesh on 20/4/24. +// + +import Foundation + +final class MassEffectMission: Mission { + var name: String = "Mass Effect" + var description: String = "Attain 100 Kills & Deaths in 1 game" + var currentParameters: [StatisticTypeWrapper: any Statistic] + + static var definedParameters: [StatisticTypeWrapper: Double] { + [ + TotalKillsStatistic.asType: 100.0, + TotalDeathsStatistic.asType: 100.0 + ] + } + + init(dependentStatistics: [Statistic]) { + var stats: [StatisticTypeWrapper: any Statistic] = [:] + dependentStatistics.forEach { stats[$0.statisticName] = $0 } + self.currentParameters = stats + } +} diff --git a/TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift b/TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift index 6c9eeef5..65a87a25 100644 --- a/TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift @@ -13,7 +13,8 @@ class MissionsFactory { [ String(describing: MassDamageMission.self): MassDamageMission.self, String(describing: MassKillMission.self): MassKillMission.self, - String(describing: MassDeathMission.self): MassDeathMission.self + String(describing: MassDeathMission.self): MassDeathMission.self, + String(describing: MassEffectMission.self): MassEffectMission.self ] static func registerMissionType(_ stat: T) { From 63e778978262e10f402825f20b409ab04da2063e Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sat, 20 Apr 2024 23:50:27 +0800 Subject: [PATCH 06/29] Add cell tint color --- .../TowerForge/ViewControllers/PlayerStatsViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift index 4825a00d..7d367c74 100644 --- a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift @@ -175,6 +175,7 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl // cell.progressPercentage.text = String(describing: Float(achievement.overallProgressRateRounded)) let statusImageName = achievement.isComplete ? "checkmark.circle" : "x.circle" cell.statusImageView.image = UIImage(systemName: statusImageName) + cell.statusImageView.tintColor = achievement.isComplete ? .green : .red return cell } @@ -193,6 +194,7 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl missionCell.missionImageView.image = UIImage(named: mission.imageIdentifier) let statusImageName = mission.isComplete ? "checkmark.circle" : "x.circle" missionCell.statusImageView.image = UIImage(systemName: statusImageName) + missionCell.statusImageView.tintColor = mission.isComplete ? .green : .red return missionCell From 5b1fa42ac85821e59447455280227fe915a69287 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 00:03:19 +0800 Subject: [PATCH 07/29] Update KD calculation to accomodate empty values --- TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift index b40be392..78a169fd 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -30,7 +30,10 @@ class RankingEngine: InferenceEngine, InferenceDataDelegate { } var currentKd: Double { - getPermanentValueFor(TotalKillsStatistic.self) / getPermanentValueFor(TotalDeathsStatistic.self) + let kills = getPermanentValueFor(TotalKillsStatistic.self) + let deaths = getPermanentValueFor(TotalDeathsStatistic.self) + + return kills > 0 && deaths > 0 ? kills / deaths : .zero } var currentExpAsString: String { From 81ace5f57359821a615a22927924efca5752c15f Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 01:11:37 +0800 Subject: [PATCH 08/29] Add debug log statements --- .../Firebase/Authentication/AuthenticationManager.swift | 1 + TowerForge/TowerForge/Metrics/Statistics/Statistic.swift | 4 ++++ .../Metrics/Statistics/StatisticsDatabase+Merge.swift | 3 +++ .../Metrics/Statistics/StatisticsDatabase.swift | 6 ++++++ TowerForge/TowerForge/Storage/LocalMetadataManager.swift | 9 +++++++++ TowerForge/TowerForge/Storage/Metadata.swift | 6 +++--- TowerForge/TowerForge/Storage/StorageManager.swift | 5 +++++ 7 files changed, 31 insertions(+), 3 deletions(-) diff --git a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift index 4a7c4329..4dd6adeb 100644 --- a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift +++ b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift @@ -87,6 +87,7 @@ class AuthenticationManager: AuthenticationProtocol { email: email, username: user.displayName) StorageManager.onLogin(with: userData.userId) // TODO: Consider if there might be a better way to do this + Logger.log("LOGIN: userId is \(userData.userId), email is \(userData.email)", self) self.delegate?.onLogin() completion(userData, nil) } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 80dc1dd1..aa0d8aa4 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -48,6 +48,10 @@ extension Statistic { self.init(permanentValue: .zero, currentValue: .zero, maxCurrentValue: .zero) } + func toString() -> String { + "[\(prettyName): \(permanentValue)]" + } + static func equals(lhs: Self, rhs: Self) -> Bool { (lhs.statisticName == rhs.statisticName) && (lhs.permanentValue == rhs.permanentValue) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index d3ad43f1..b3b0c4f0 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -31,7 +31,9 @@ extension StatisticsDatabase: Equatable { /// - 3. There should not be any keys in the final database that are not within the first or second database. /// - 4. For duplicate keys that have the same value, either value can be retained in the final database. static func merge(this: StatisticsDatabase?, that: StatisticsDatabase?) -> StatisticsDatabase? { + Logger.log("SDB: Merge function entered.", self) guard this != nil || that != nil else { + Logger.log("SDB: Both nil, returning nil", self) return nil } @@ -77,6 +79,7 @@ extension StatisticsDatabase: Equatable { } } + Logger.log("SDB: Merged stats contain \(mergedStats.toString())") return mergedStats } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift index 88c2f7f1..be9c20a0 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift @@ -26,4 +26,10 @@ final class StatisticsDatabase: StorageDatabase { func setToDefault() { statistics = StatisticsFactory.getDefaultStatisticsDatabase().statistics } + + func toString() -> String { + var output = "" + statistics.values.forEach { output += ($0.toString() + "\n") } + return output + } } diff --git a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift index d5449fbd..96a951ac 100644 --- a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift @@ -47,6 +47,15 @@ class LocalMetadataManager { // RemoteMetadataManager.updateMetadataInFirebase() } + static func synchronizeMetadataInLocalStorage() { + let metadata = loadMetadataFromLocalStorage() ?? Metadata() + metadata.lastUpdated = Date.now + metadata.uniqueIdentifier = Constants.CURRENT_PLAYER_ID + saveMetadataToLocalStorage(metadata) + Logger.log("Metadata synchronized at: \(metadata.lastUpdated)", self) + // RemoteMetadataManager.updateMetadataInFirebase() + } + static func saveMetadataToLocalStorage(_ metadata: Metadata) { do { let folderURL = try LocalStorageManager.createFolderIfNeeded(folderName: folderName) diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index abeca6e7..f492ce2b 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -12,16 +12,16 @@ import Foundation /// - Information about device and the current user for use with Remote Storage /// - Meta-information about files stored locally, possibly for use with conflict resolution. class Metadata: Codable, Comparable, Equatable { - let uniqueIdentifier: String + var uniqueIdentifier: String var lastUpdated: Date - init(lastUpdated: Date, + init(lastUpdated: Date = .now, uniqueIdentifier: String = Constants.CURRENT_PLAYER_ID) { self.lastUpdated = lastUpdated self.uniqueIdentifier = uniqueIdentifier } - required init() { + init() { self.lastUpdated = Date() self.uniqueIdentifier = UUID().uuidString } diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift index 0695f803..3e6fd0b5 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -27,12 +27,14 @@ class StorageManager { } static func onLogin(with userId: String) { + Logger.log("LOGIN: onLogin method invoked from Storage Manager", self) let localStorage = LocalStorageManager.loadDatabaseFromLocalStorage() ?? StatisticsFactory.getDefaultStatisticsDatabase() _ = Self.saveUniversally(localStorage) Constants.CURRENT_PLAYER_ID = userId + LocalMetadataManager.synchronizeMetadataInLocalStorage() var remoteStorage = StatisticsDatabase() RemoteStorageManager.loadDatabaseFromFirebase { statisticsDatabase, error in @@ -49,6 +51,7 @@ class StorageManager { } if let finalStorage = Self.resolveConflict(this: localStorage, that: remoteStorage) { + Logger.log("LOGIN: Resolve conflict closure entered", self) _ = Self.saveUniversally(finalStorage) } @@ -138,6 +141,7 @@ class StorageManager { /// This ensures that no information is overwritten in the process. private static func pushToRemote() -> StatisticsDatabase? { // Explicitly load storage to ensure that uploaded data is + Logger.log("PUSH: Push to remote entered", self) var localStorage: StatisticsDatabase var remoteStorage = StatisticsDatabase() @@ -162,6 +166,7 @@ class StorageManager { } guard let finalStorage = StatisticsDatabase.merge(this: localStorage, that: remoteStorage) else { + Logger.log("PUSH: Merge failed, returning nil") return nil } From 85d301b38f7ba0bb9001268429d3974d9bc73441 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 02:11:09 +0800 Subject: [PATCH 09/29] Add new LocalStorage methods --- .../TowerForge.xcodeproj/project.pbxproj | 24 +++++ .../TowerForge/GameModule/GameWorld.swift | 6 +- .../Metrics/Statistics/StatisticsEngine.swift | 9 +- TowerForge/TowerForge/Storage/Metadata.swift | 7 +- .../StorageAPI/LocalStorage+Metadata.swift | 64 +++++++++++++ .../TowerForge/StorageAPI/LocalStorage.swift | 96 +++++++++++++++++++ .../TowerForge/StorageAPI/RemoteStorage.swift | 13 +++ .../StorageAPI/StorageHandler.swift | 64 +++++++++++++ 8 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift create mode 100644 TowerForge/TowerForge/StorageAPI/LocalStorage.swift create mode 100644 TowerForge/TowerForge/StorageAPI/RemoteStorage.swift create mode 100644 TowerForge/TowerForge/StorageAPI/StorageHandler.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index bca88c36..733da2d4 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -201,6 +201,10 @@ BA436AE12BD3D66800BE3E4F /* MassKillMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AE02BD3D66800BE3E4F /* MassKillMission.swift */; }; BA436AE32BD3D6FF00BE3E4F /* MassDeathMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AE22BD3D6FF00BE3E4F /* MassDeathMission.swift */; }; BA436AE72BD4180400BE3E4F /* MassEffectMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AE62BD4180400BE3E4F /* MassEffectMission.swift */; }; + BA436AEA2BD42F5400BE3E4F /* StorageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */; }; + BA436AEC2BD42F7800BE3E4F /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */; }; + BA436AEE2BD42F8100BE3E4F /* RemoteStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */; }; + BA436AF02BD437D900BE3E4F /* LocalStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */; }; BA443D3D2BAD9557009F0FFB /* RemoveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */; }; BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */; }; BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D412BAD9885009F0FFB /* DamageEventTests.swift */; }; @@ -478,6 +482,10 @@ BA436AE02BD3D66800BE3E4F /* MassKillMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MassKillMission.swift; sourceTree = ""; }; BA436AE22BD3D6FF00BE3E4F /* MassDeathMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MassDeathMission.swift; sourceTree = ""; }; BA436AE62BD4180400BE3E4F /* MassEffectMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MassEffectMission.swift; sourceTree = ""; }; + BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageHandler.swift; sourceTree = ""; }; + BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorage.swift; sourceTree = ""; }; + BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStorage.swift; sourceTree = ""; }; + BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorage+Metadata.swift"; sourceTree = ""; }; BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSystem.swift; sourceTree = ""; }; BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEvent.swift; sourceTree = ""; }; BA443D412BAD9885009F0FFB /* DamageEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageEventTests.swift; sourceTree = ""; }; @@ -915,6 +923,7 @@ 5295A2082BAAE14B005018A8 /* ViewControllers */, 52DF5FDB2BA32CEF00135367 /* LevelModule */, BAFFB9332BB0A24400D8301F /* GameModule */, + BA436AE82BD42F1100BE3E4F /* StorageAPI */, BAFFB9512BB342E200D8301F /* Storage */, BA2F5ABF2BC80BD200CBD8E9 /* Metrics */, ); @@ -1040,6 +1049,17 @@ path = Views; sourceTree = ""; }; + BA436AE82BD42F1100BE3E4F /* StorageAPI */ = { + isa = PBXGroup; + children = ( + BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */, + BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */, + BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */, + BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */, + ); + path = StorageAPI; + sourceTree = ""; + }; BA443D402BAD9872009F0FFB /* EventTests */ = { isa = PBXGroup; children = ( @@ -1696,6 +1716,7 @@ 9B0406162BB89E140026E903 /* InvulnerabilityPowerUpDelegate.swift in Sources */, 5295A2152BAAF335005018A8 /* UnitSelectionNode.swift in Sources */, 52DF5FF92BA35D2B00135367 /* MovableComponent.swift in Sources */, + BA436AEA2BD42F5400BE3E4F /* StorageHandler.swift in Sources */, 5299D1342BC31067003EF746 /* AuthenticationProtocol.swift in Sources */, 527A07822BB3F8D300CD9D08 /* TimerProp.swift in Sources */, 52DF5FDE2BA32D7E00135367 /* EntityManager.swift in Sources */, @@ -1711,6 +1732,7 @@ 52A794092BBC35F20083C976 /* Constant.swift in Sources */, 3C9955C82BA5865C00D33FA5 /* ConcurrentEvent.swift in Sources */, 52F268702BB4B319009599AD /* GameModeViewController.swift in Sources */, + BA436AEC2BD42F7800BE3E4F /* LocalStorage.swift in Sources */, BA82C7862BCD2B44000515A0 /* MissionTypeWrapper.swift in Sources */, BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */, 527A07762BB3E4CF00CD9D08 /* GameState.swift in Sources */, @@ -1720,9 +1742,11 @@ 3CAC4A672BB6975200A5D22E /* RenderStage.swift in Sources */, BA82C75D2BCB1451000515A0 /* InferenceEngine.swift in Sources */, 3C9955BE2BA57E4B00D33FA5 /* EventManager.swift in Sources */, + BA436AF02BD437D900BE3E4F /* LocalStorage+Metadata.swift in Sources */, BAFFB9852BBDBA7D00D8301F /* MediaEnums.swift in Sources */, 527A07842BB3FD9A00CD9D08 /* TimerSystem.swift in Sources */, BA82C7462BC8797F000515A0 /* StatisticsDatabase.swift in Sources */, + BA436AEE2BD42F8100BE3E4F /* RemoteStorage.swift in Sources */, 3C3CBDF72BB81D970001B8A9 /* TFCameraNode.swift in Sources */, 9B274DCA2BD252330062715C /* NoCostPowerUpDelegate.swift in Sources */, BA82C76D2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift in Sources */, diff --git a/TowerForge/TowerForge/GameModule/GameWorld.swift b/TowerForge/TowerForge/GameModule/GameWorld.swift index a9a29b5a..ce66d4a3 100644 --- a/TowerForge/TowerForge/GameModule/GameWorld.swift +++ b/TowerForge/TowerForge/GameModule/GameWorld.swift @@ -19,7 +19,9 @@ class GameWorld { private var renderer: Renderer? private let worldBounds: CGRect private var popup: StatePopupNode - private var statisticsEngine = StatisticsEngine() + + private var storageHandler: StorageHandler + private var statisticsEngine: StatisticsEngine unowned var scene: GameScene? { didSet { setUpScene() } } unowned var delegate: SceneManagerDelegate? @@ -33,6 +35,8 @@ class GameWorld { powerUpSelectionNode = PowerUpSelectionNode(eventManager: gameEngine.eventManager) grid = Grid(screenSize: worldBounds) popup = StatePopupNode() + storageHandler = StorageHandler() + statisticsEngine = StatisticsEngine(with: storageHandler.statisticsDatabase) setUp() } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 03836640..b2ebde3c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -14,6 +14,14 @@ class StatisticsEngine { var inferenceEngines: [InferenceEngineTypeWrapper: InferenceEngine] = [:] init() { + self.initializeStatistics() + self.loadStatistics() + self.setUpLinks() + self.setUpInferenceEngines() + } + + init(with statistics: StatisticsDatabase) { + self.statistics = statistics self.initializeStatistics() self.setUpLinks() self.setUpInferenceEngines() @@ -32,7 +40,6 @@ class StatisticsEngine { private func initializeStatistics() { eventStatisticLinks = StatisticsFactory.getDefaultEventLinkDatabase() - loadStatistics() } private func setUpInferenceEngines() { diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index f492ce2b..24d64ed7 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -22,10 +22,15 @@ class Metadata: Codable, Comparable, Equatable { } init() { - self.lastUpdated = Date() + self.lastUpdated = .now self.uniqueIdentifier = UUID().uuidString } + func update() { + Logger.log("Metadata update as at \(Date.now.ISO8601Format())") + lastUpdated = .now + } + static func == (lhs: Metadata, rhs: Metadata) -> Bool { lhs.lastUpdated == rhs.lastUpdated && lhs.uniqueIdentifier == rhs.uniqueIdentifier } diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift new file mode 100644 index 00000000..af225615 --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift @@ -0,0 +1,64 @@ +// +// LocalStorage+Metadata.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +/// This extension adds utility methods specifically for local metadata operations +extension LocalStorage { + static func initializeMetadataToLocalStorage() { + if LocalStorage.loadMetadataFromLocalStorage() != nil { + Logger.log("--- Metadata exists locally", Self.self) + } else { + let defaultMetadata = Metadata() + Constants.CURRENT_PLAYER_ID = defaultMetadata.uniqueIdentifier + Constants.CURRENT_DEVICE_ID = defaultMetadata.uniqueIdentifier + + LocalStorage.saveMetadataToLocalStorage(defaultMetadata) + Logger.log("--- Created and saved a new empty metadata file.", Self.self) + Logger.log("--- Current id: \(Constants.CURRENT_DEVICE_ID)", Self.self) + } + } + + static func saveMetadataToLocalStorage(_ metadata: Metadata) { + do { + let folderURL = try Self.createFolderIfNeeded(folderName: folderName) + let fileURL = folderURL.appendingPathComponent(metadataName) + let data = try JSONEncoder().encode(metadata) + try data.write(to: fileURL) + Logger.log("Metadata saved at: \(fileURL.path)", self) + } catch { + Logger.log("Failed to save metadata: \(error)", self) + } + } + + static func loadMetadataFromLocalStorage() -> Metadata? { + do { + let folderURL = try Self.createFolderIfNeeded(folderName: folderName) + let fileURL = folderURL.appendingPathComponent(metadataName) + let data = try Data(contentsOf: fileURL) + let metadata = try JSONDecoder().decode(Metadata.self, from: data) + return metadata + } catch { + Logger.log("Failed to load metadata: \(error)", self) + return nil + } + } + + static func deleteMetadataFromLocalStorage() { + do { + let folderURL = try LocalStorageManager.createFolderIfNeeded(folderName: folderName) + let fileURL = folderURL.appendingPathComponent(metadataName) + try FileManager.default.removeItem(at: fileURL) + Logger.log("Metadata successfully deleted.", self) + } catch { + Logger.log("Error deleting metadata: \(error)", self) + } + + Logger.log("Metadata successfully deleted.", self) + } + +} diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage.swift new file mode 100644 index 00000000..6840e4ff --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage.swift @@ -0,0 +1,96 @@ +// +// LocalStorage.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +/// Utility class to provide static methods for local storage operations +class LocalStorage { + static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME + static let fileName = Constants.LOCAL_STORAGE_FILE_NAME + static let metadataName = Constants.METADATA_FILE_NAME + + private init() { } + + static func initializeDatabaseToLocalStorage() { + /// Initialize StatisticsDatabase + if Self.loadDatabaseFromLocalStorage() != nil { + Logger.log("--- Database exists locally", Self.self) + } else { + let defaultDatabase = StatisticsFactory.getDefaultStatisticsDatabase() + Self.saveDatabaseToLocalStorage(defaultDatabase) + Logger.log("--- Created and saved a new empty database.", Self.self) + } + } + + /// Saves the input statistics database to file + static func saveDatabaseToLocalStorage(_ stats: StatisticsDatabase) { + let encoder = JSONEncoder() + + do { + let data = try encoder.encode(stats) + let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) + let fileURL = folderURL.appendingPathComponent(Self.fileName) + try data.write(to: fileURL) + Logger.log("Saved Statistics Database at: \(fileURL.path)", self) + } catch { + Logger.log("Error saving statistics Database: \(error)", self) + } + } + + /// Returns the StatisticsDatabase if it exists at the specific location or nil otherwise. + static func loadDatabaseFromLocalStorage() -> StatisticsDatabase? { + do { + let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) + let fileURL = folderURL.appendingPathComponent(Self.fileName) + let data = try Data(contentsOf: fileURL) + let decoder = JSONDecoder() + return try decoder.decode(StatisticsDatabase.self, from: data) + } catch { + Logger.log("Error loading statistics: \(error)", self) + return nil + } + } + + /// Deletes the stored database from file + static func deleteDatabaseFromLocalStorage() { + do { + let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) + let fileURL = folderURL.appendingPathComponent(Self.fileName) + try FileManager.default.removeItem(at: fileURL) + } catch { + Logger.log("Error deleting file: \(Self.fileName), \(error)", self) + } + Logger.log("Database successfully deleted.", self) + } + + /// Helper function to construct a FileURL + static func fileURL(for directory: FileManager.SearchPathDirectory, withName name: String) throws -> URL { + let fileManager = FileManager.default + + return try fileManager.url(for: directory, + in: .userDomainMask, + appropriateFor: nil, + create: true).appendingPathComponent(name) + } + + /// Helper function to create a folder using the shared FileManager for a given folderName + static func createFolderIfNeeded(folderName: String) throws -> URL { + let fileManager = FileManager.default + let documentsURL: URL = try fileManager.url(for: .documentDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: false) + + let folderURL = documentsURL.appendingPathComponent(folderName) + if !fileManager.fileExists(atPath: folderURL.path) { + try fileManager.createDirectory(at: folderURL, + withIntermediateDirectories: true, + attributes: nil) + } + return folderURL + } +} diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift new file mode 100644 index 00000000..4b9ad57e --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift @@ -0,0 +1,13 @@ +// +// RemoteStroage.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +/// Utility class to provide static methods for accessing remote storage +class RemoteStorage { + private init() { } +} diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift new file mode 100644 index 00000000..f8cdefea --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -0,0 +1,64 @@ +// +// StorageHandler.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +class StorageHandler { + static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME + static let fileName = Constants.LOCAL_STORAGE_FILE_NAME + static let metadataName = Constants.METADATA_FILE_NAME + + var hello: Void + + var statisticsDatabase = StatisticsDatabase() + var metadata = Metadata() + + init() { + Self.initializeLocalStorageIfNotPresent() + + if let statistics = LocalStorage.loadDatabaseFromLocalStorage() { + statisticsDatabase = statistics + } else { + statisticsDatabase.setToDefault() + } + + if let metadata = LocalStorage.loadMetadataFromLocalStorage() { + self.metadata = metadata + } else { + self.metadata = Metadata() + } + } + + deinit { + save() + } + + /// Initializes empty statistics and metadata if they don't already exist locally + /// Called by the AppDelegate when the application is run. + static func initializeLocalStorageIfNotPresent() { + Logger.log("Initializing Metadata", Self.self) + LocalStorage.initializeMetadataToLocalStorage() + + Logger.log("Initializing LocalStorage", Self.self) + LocalStorage.initializeDatabaseToLocalStorage() + } + + /// Saves the current statistics database to file + func save() { + Logger.log("SAVE: Saving Stats and Metadata to LocalStorage", Self.self) + LocalStorage.saveDatabaseToLocalStorage(statisticsDatabase) + metadata.update() + LocalStorage.saveMetadataToLocalStorage(metadata) + } + + func delete() { + Logger.log("DELETE: Deleting Stats and Metadata to LocalStorage", Self.self) + LocalStorage.deleteDatabaseFromLocalStorage() + LocalStorage.deleteMetadataFromLocalStorage() + } + +} From d0c6d081190f79bdaf7052ce7ee3e909f025b338 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 02:11:20 +0800 Subject: [PATCH 10/29] Fix style --- TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift index af225615..57b4d604 100644 --- a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift @@ -57,8 +57,7 @@ extension LocalStorage { } catch { Logger.log("Error deleting metadata: \(error)", self) } - + Logger.log("Metadata successfully deleted.", self) } - } From 51fc93c12c3eee081c689daf90259577280ddf82 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 03:07:57 +0800 Subject: [PATCH 11/29] Expand StorageManager to include login check --- .../TowerForge.xcodeproj/project.pbxproj | 4 ++ TowerForge/TowerForge/Storage/Metadata.swift | 19 +++++- .../StorageAPI/LocalStorage+Metadata.swift | 3 +- .../TowerForge/StorageAPI/LocalStorage.swift | 2 +- .../StorageAPI/RemoteStorage+Metadata.swift | 23 +++++++ .../TowerForge/StorageAPI/RemoteStorage.swift | 35 ++++++++++ .../StorageAPI/StorageHandler.swift | 65 +++++++++++++++++-- 7 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 733da2d4..0b7aaed5 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -205,6 +205,7 @@ BA436AEC2BD42F7800BE3E4F /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */; }; BA436AEE2BD42F8100BE3E4F /* RemoteStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */; }; BA436AF02BD437D900BE3E4F /* LocalStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */; }; + BA436AF22BD443A500BE3E4F /* RemoteStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */; }; BA443D3D2BAD9557009F0FFB /* RemoveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */; }; BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */; }; BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D412BAD9885009F0FFB /* DamageEventTests.swift */; }; @@ -486,6 +487,7 @@ BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorage.swift; sourceTree = ""; }; BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStorage.swift; sourceTree = ""; }; BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorage+Metadata.swift"; sourceTree = ""; }; + BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteStorage+Metadata.swift"; sourceTree = ""; }; BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSystem.swift; sourceTree = ""; }; BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEvent.swift; sourceTree = ""; }; BA443D412BAD9885009F0FFB /* DamageEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageEventTests.swift; sourceTree = ""; }; @@ -1056,6 +1058,7 @@ BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */, BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */, BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */, + BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */, ); path = StorageAPI; sourceTree = ""; @@ -1718,6 +1721,7 @@ 52DF5FF92BA35D2B00135367 /* MovableComponent.swift in Sources */, BA436AEA2BD42F5400BE3E4F /* StorageHandler.swift in Sources */, 5299D1342BC31067003EF746 /* AuthenticationProtocol.swift in Sources */, + BA436AF22BD443A500BE3E4F /* RemoteStorage+Metadata.swift in Sources */, 527A07822BB3F8D300CD9D08 /* TimerProp.swift in Sources */, 52DF5FDE2BA32D7E00135367 /* EntityManager.swift in Sources */, 3CBE72FB2BC8D63E00CC446A /* RemoteDamageEvent.swift in Sources */, diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index 24d64ed7..b7a2df6d 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -12,11 +12,14 @@ import Foundation /// - Information about device and the current user for use with Remote Storage /// - Meta-information about files stored locally, possibly for use with conflict resolution. class Metadata: Codable, Comparable, Equatable { + static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } + static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } + var uniqueIdentifier: String var lastUpdated: Date init(lastUpdated: Date = .now, - uniqueIdentifier: String = Constants.CURRENT_PLAYER_ID) { + uniqueIdentifier: String = Metadata.currentPlayerId) { self.lastUpdated = lastUpdated self.uniqueIdentifier = uniqueIdentifier } @@ -26,9 +29,19 @@ class Metadata: Codable, Comparable, Equatable { self.uniqueIdentifier = UUID().uuidString } - func update() { - Logger.log("Metadata update as at \(Date.now.ISO8601Format())") + func updateTimeToNow() { lastUpdated = .now + Logger.log("Metadata time updated to \(self.lastUpdated.ISO8601Format())", self) + } + + func updateIdentifierToCurrentID() { + uniqueIdentifier = Self.currentPlayerId + Logger.log("Metadata id updated to \(self.uniqueIdentifier)", self) + } + + func resetIdentifier() { + uniqueIdentifier = Self.currentDeviceId + Logger.log("Metadata id updated to \(self.uniqueIdentifier)", self) } static func == (lhs: Metadata, rhs: Metadata) -> Bool { diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift index 57b4d604..622d7ae2 100644 --- a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift @@ -48,6 +48,7 @@ extension LocalStorage { } } + /// Deletes the stored metadata from file static func deleteMetadataFromLocalStorage() { do { let folderURL = try LocalStorageManager.createFolderIfNeeded(folderName: folderName) @@ -57,7 +58,7 @@ extension LocalStorage { } catch { Logger.log("Error deleting metadata: \(error)", self) } - + Logger.log("Metadata successfully deleted.", self) } } diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage.swift index 6840e4ff..4a619745 100644 --- a/TowerForge/TowerForge/StorageAPI/LocalStorage.swift +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage.swift @@ -55,7 +55,7 @@ class LocalStorage { } } - /// Deletes the stored database from file + /// Deletes the stored statistics database from file static func deleteDatabaseFromLocalStorage() { do { let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift new file mode 100644 index 00000000..d5dfc185 --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift @@ -0,0 +1,23 @@ +// +// RemoteStorage+Metadata.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +/// This class adds utility methods specifically for Metadata operations +extension RemoteStorage { + + /// Checks if a player's metadata exists without requiring a closure input + static func checkIfPlayerMetadataExists(for playerId: String) -> Bool { + var exists = false + + RemoteStorage.remoteStorageExists(for: .Metadata, player: playerId) { bool in + exists = bool + } + + return exists + } +} diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift index 4b9ad57e..f22d4a24 100644 --- a/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift @@ -9,5 +9,40 @@ import Foundation /// Utility class to provide static methods for accessing remote storage class RemoteStorage { + private init() { } + + /// Queries the firebase backend to determine if remote storage exists for the current player + static func remoteStorageExists(for ref: FirebaseReference, + player: String, + completion: @escaping (Bool) -> Void) { + let databaseReference = FirebaseDatabaseReference(ref) + + databaseReference.child(player).getData(completion: { error, snapshot in + if let error = error { + Logger.log("Error checking data existence: \(error.localizedDescription)", self) + completion(false) // Assuming no data exists if an error occurs + return + } + + // Snapshot must exist AND be non-empty in order to be considered existing + if snapshot?.exists() != nil && snapshot?.value != nil { + completion(true) + } else { + completion(false) + } + }) + } + + /// Checks if a player's statistics exists without requiring a closure input + static func checkIfPlayerStorageExists(for playerId: String) -> Bool { + var exists = false + + RemoteStorage.remoteStorageExists(for: .Statistics, player: playerId) { bool in + exists = bool + } + + return exists + } + } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index f8cdefea..7c566fbc 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -7,25 +7,30 @@ import Foundation -class StorageHandler { +class StorageHandler: AuthenticationDelegate { static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME static let fileName = Constants.LOCAL_STORAGE_FILE_NAME static let metadataName = Constants.METADATA_FILE_NAME - var hello: Void + static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } + static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } + private let authenticationProvider = AuthenticationProvider() var statisticsDatabase = StatisticsDatabase() var metadata = Metadata() init() { Self.initializeLocalStorageIfNotPresent() + authenticationProvider.addObserver(self) + // Set statistic DB to default if it doesn't exist if let statistics = LocalStorage.loadDatabaseFromLocalStorage() { statisticsDatabase = statistics } else { - statisticsDatabase.setToDefault() + statisticsDatabase = StatisticsFactory.getDefaultStatisticsDatabase() } + // Set metadata to default if it doesn't exists if let metadata = LocalStorage.loadMetadataFromLocalStorage() { self.metadata = metadata } else { @@ -34,6 +39,7 @@ class StorageHandler { } deinit { + Logger.log("StorageHandler: Deinit is called", self) save() } @@ -51,7 +57,7 @@ class StorageHandler { func save() { Logger.log("SAVE: Saving Stats and Metadata to LocalStorage", Self.self) LocalStorage.saveDatabaseToLocalStorage(statisticsDatabase) - metadata.update() + metadata.updateTimeToNow() LocalStorage.saveMetadataToLocalStorage(metadata) } @@ -61,4 +67,55 @@ class StorageHandler { LocalStorage.deleteMetadataFromLocalStorage() } + func onLogin() { + Logger.log("IMPT: onLogin IS CALLED FROM STORAGE_HANDLER", Self.self) + + guard let userId = authenticationProvider.getCurrentUserId() else { + Logger.log("IMPT: onLogin failed due to userId nil from STORAGE_HANDLER", Self.self) + return + } + + /// Update the playerId and metadata locally + self.localUpdatePlayerIdAndMetadata(with: userId) + + if checkIfPlayerDataExists() { + + } else { + + } + + // if they don't, execute onFirstLogin + + // if they do, execute onReLogin + } + + func localUpdatePlayerIdAndMetadata(with userId: String) { + Constants.CURRENT_PLAYER_ID = userId + metadata.updateIdentifierToCurrentID() + self.save() + } + + /// Returns true only if both Metadata and Storage exist + func checkIfPlayerDataExists() -> Bool { + let storageExists = RemoteStorage.checkIfPlayerStorageExists(for: Self.currentPlayerId) + let metadataExists = RemoteStorage.checkIfPlayerMetadataExists(for: Self.currentPlayerId) + + if (storageExists && !metadataExists) || (!storageExists && metadataExists) { + Logger.log("Inconsisteny error: Storage Exists: \(storageExists), Metadata Exists: \(metadataExists)") + } + + return storageExists && metadataExists + + } + + func onLogout() { + Logger.log("IMPT: onLOGOUT IS CALLED FROM STORAGE_HANDLER", Self.self) + self.save() // Save any potential unsaved changes + + // Reset metadata to original value + metadata.resetIdentifier() + Logger.log("--- metadata reset to \(metadata.uniqueIdentifier)", Self.self) + self.save() // Save updated metadata + } + } From 701aef581524dfc4d7940a3ba14564afb05c48af Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 03:11:15 +0800 Subject: [PATCH 12/29] Fix style --- TowerForge/TowerForge/StorageAPI/StorageHandler.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 7c566fbc..0d2dd8c8 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -53,7 +53,7 @@ class StorageHandler: AuthenticationDelegate { LocalStorage.initializeDatabaseToLocalStorage() } - /// Saves the current statistics database to file + /// Universal save func save() { Logger.log("SAVE: Saving Stats and Metadata to LocalStorage", Self.self) LocalStorage.saveDatabaseToLocalStorage(statisticsDatabase) @@ -61,6 +61,7 @@ class StorageHandler: AuthenticationDelegate { LocalStorage.saveMetadataToLocalStorage(metadata) } + /// Universal delete func delete() { Logger.log("DELETE: Deleting Stats and Metadata to LocalStorage", Self.self) LocalStorage.deleteDatabaseFromLocalStorage() @@ -78,8 +79,8 @@ class StorageHandler: AuthenticationDelegate { /// Update the playerId and metadata locally self.localUpdatePlayerIdAndMetadata(with: userId) - if checkIfPlayerDataExists() { - + if checkIfRemotePlayerDataExists() { + } else { } @@ -96,7 +97,7 @@ class StorageHandler: AuthenticationDelegate { } /// Returns true only if both Metadata and Storage exist - func checkIfPlayerDataExists() -> Bool { + func checkIfRemotePlayerDataExists() -> Bool { let storageExists = RemoteStorage.checkIfPlayerStorageExists(for: Self.currentPlayerId) let metadataExists = RemoteStorage.checkIfPlayerMetadataExists(for: Self.currentPlayerId) @@ -105,7 +106,6 @@ class StorageHandler: AuthenticationDelegate { } return storageExists && metadataExists - } func onLogout() { From ae7c96f5fffe73b21850793c41822a73ec91ee2d Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 04:53:47 +0800 Subject: [PATCH 13/29] Add some async functions to StorageAPI --- .../TowerForge.xcodeproj/project.pbxproj | 10 +- .../Metrics/Statistics/Statistic.swift | 26 ++-- TowerForge/TowerForge/Storage/Metadata.swift | 2 +- .../StorageAPI/RemoteStorage+Access.swift | 145 ++++++++++++++++++ .../StorageAPI/RemoteStorage+Metadata.swift | 23 --- .../TowerForge/StorageAPI/RemoteStorage.swift | 76 ++++++++- .../StorageDatabase.swift | 2 +- .../StorageAPI/StorageHandler.swift | 57 +++++-- 8 files changed, 284 insertions(+), 57 deletions(-) create mode 100644 TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift delete mode 100644 TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift rename TowerForge/TowerForge/{Storage => StorageAPI}/StorageDatabase.swift (79%) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 0b7aaed5..a33250f3 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -205,7 +205,7 @@ BA436AEC2BD42F7800BE3E4F /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */; }; BA436AEE2BD42F8100BE3E4F /* RemoteStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */; }; BA436AF02BD437D900BE3E4F /* LocalStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */; }; - BA436AF22BD443A500BE3E4F /* RemoteStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */; }; + BA436AF22BD443A500BE3E4F /* RemoteStorage+Access.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */; }; BA443D3D2BAD9557009F0FFB /* RemoveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */; }; BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */; }; BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D412BAD9885009F0FFB /* DamageEventTests.swift */; }; @@ -487,7 +487,7 @@ BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorage.swift; sourceTree = ""; }; BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStorage.swift; sourceTree = ""; }; BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorage+Metadata.swift"; sourceTree = ""; }; - BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteStorage+Metadata.swift"; sourceTree = ""; }; + BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteStorage+Access.swift"; sourceTree = ""; }; BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSystem.swift; sourceTree = ""; }; BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEvent.swift; sourceTree = ""; }; BA443D412BAD9885009F0FFB /* DamageEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageEventTests.swift; sourceTree = ""; }; @@ -1055,10 +1055,11 @@ isa = PBXGroup; children = ( BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */, + BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */, BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */, BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */, BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */, - BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */, + BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */, ); path = StorageAPI; sourceTree = ""; @@ -1430,7 +1431,6 @@ BAFFB9512BB342E200D8301F /* Storage */ = { isa = PBXGroup; children = ( - BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */, BA82C76E2BCBDE91000515A0 /* Metadata.swift */, BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */, BA82C76A2BCBD682000515A0 /* StorageManager.swift */, @@ -1721,7 +1721,7 @@ 52DF5FF92BA35D2B00135367 /* MovableComponent.swift in Sources */, BA436AEA2BD42F5400BE3E4F /* StorageHandler.swift in Sources */, 5299D1342BC31067003EF746 /* AuthenticationProtocol.swift in Sources */, - BA436AF22BD443A500BE3E4F /* RemoteStorage+Metadata.swift in Sources */, + BA436AF22BD443A500BE3E4F /* RemoteStorage+Access.swift in Sources */, 527A07822BB3F8D300CD9D08 /* TimerProp.swift in Sources */, 52DF5FDE2BA32D7E00135367 /* EntityManager.swift in Sources */, 3CBE72FB2BC8D63E00CC446A /* RemoteDamageEvent.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index aa0d8aa4..edf67a57 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -141,18 +141,6 @@ extension Statistic { self.getStatisticUpdateLinks().getAllEventTypes() } - /*func update(for event: T) { - let eventType = T.asType - guard let updateLink = self.getStatisticUpdateLinks().getStatisticUpdateActor(for: eventType) else { - return - } - - updateLink.updateStatistic(statistic: self, withEvent: T.self) - - updateLink?(self) - Logger.log("Value update for eventType \(eventType)", self) - }*/ - /// Updates the statistic according to an UpdateActor that is retrieved from the /// StatisticsUpdateLinkDatabase func update(for event: T) { @@ -186,3 +174,17 @@ extension Statistic { self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) } } + +/// This extension allows Statistic to be merged +extension Statistic { + + static func merge(this: Self, that: Self) -> Self { + let largerPermanent = Double.maximum(this.permanentValue, that.permanentValue) + let largerCurrent = Double.maximum(this.currentValue, that.currentValue) + let largerMaxCurrent = Double.maximum(this.maximumCurrentValue, that.maximumCurrentValue) + + return Self(permanentValue: largerPermanent, + currentValue: largerCurrent, + maxCurrentValue: largerMaxCurrent) + } +} diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index b7a2df6d..fcb62bfd 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -11,7 +11,7 @@ import Foundation /// /// - Information about device and the current user for use with Remote Storage /// - Meta-information about files stored locally, possibly for use with conflict resolution. -class Metadata: Codable, Comparable, Equatable { +class Metadata: StorageDatabase, Comparable, Equatable { static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift new file mode 100644 index 00000000..0b287e63 --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift @@ -0,0 +1,145 @@ +// +// RemoteStorage+Metadata.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +/// This class adds utility methods specifically for access operations. Given +/// the nature of the remote backend, closures are used for async operations. +/// This class abstracts away the closure invocation with default access +/// functions, for both storage and metadata. +extension RemoteStorage { + + /// Checks if a player's metadata exists without requiring a closure input + /*static func checkIfPlayerMetadataExists(for playerId: String) -> Bool { + var exists = false + + Self.remoteStorageExists(for: .Metadata, player: playerId) { bool in + exists = bool + } + + return exists + }*/ + + static func checkIfPlayerMetadataExistsAsync(for playerId: String) async -> Bool { + await withCheckedContinuation { continuation in + Self.remoteStorageExists(for: .Metadata, player: playerId) { exists in + continuation.resume(returning: exists) + } + } + } + + /// Checks if a player's statistics exists without requiring a closure input + static func checkIfPlayerStorageExists(for playerId: String) -> Bool { + var exists = false + + Self.remoteStorageExists(for: .Statistics, player: playerId) { bool in + exists = bool + } + + return exists + } + + static func saveMetadataToFirebase(player: String, with inputData: Metadata) { + let metadataCompletion: (Error?) -> Void = { error in + if let error = error { + Logger.log("Saving metadata to firebase error: \(error)", self) + } else { + Logger.log("Saving metadata to firebase success", self) + } + } + + Self.saveDataToFirebase(for: .Metadata, + player: player, + with: inputData, + completion: metadataCompletion) + } + + static func saveStorageToFirebase(player: String, with inputData: StatisticsDatabase) { + let storageCompletion: (Error?) -> Void = { error in + if let error = error { + Logger.log("Saving storage to firebase error: \(error)", self) + } else { + Logger.log("Saving storage to firebase success", self) + } + } + + Self.saveDataToFirebase(for: .Statistics, + player: player, + with: inputData, + completion: storageCompletion) + } + + /// Deletes metadata for the specified player from firebase + static func deleteMetadataFromFirebase(player: String) { + let completion: (Error?) -> Void = { error in + if let error = error { + Logger.log("Deleting metadata from firebase error: \(error)", self) + } else { + Logger.log("Saving Metadata from firebase success", self) + } + } + + Self.deleteDataFromFirebase(for: .Metadata, player: player, completion: completion) + } + + /// Deletes storage for the specific player from Firebase + static func deleteStorageFromFirebase(player: String) { + let completion: (Error?) -> Void = { error in + if let error = error { + Logger.log("Deleting storage from firebase error: \(error)", self) + } else { + Logger.log("Saving storage from firebase success", self) + } + } + + Self.deleteDataFromFirebase(for: .Statistics, player: player, completion: completion) + } + + static func loadStorageFromFirebase(player: String) -> StatisticsDatabase? { + guard Self.checkIfPlayerStorageExists(for: player) else { + return nil + } + + var stats: StatisticsDatabase? + Self.loadDataFromFirebase(for: .Statistics, + player: player) { (statisticsDatabase: StatisticsDatabase?, error: Error?) in + if let error = error { + Logger.log("Error loading storage from firebase: \(error)", self) + } else if let statistics = statisticsDatabase { + Logger.log("Successfully loaded statistics from firebase", self) + stats = statistics + } else { + // No error and no database implies that database is empty, return nil + Logger.log("No error and empty database, new one will NOT be auto-created", self) + } + } + + return stats + } + + static func loadMetadataFromFirebase(player: String) async -> Metadata? { + guard await Self.checkIfPlayerMetadataExistsAsync(for: player) else { + return nil + } + + var metadata: Metadata? + Self.loadDataFromFirebase(for: .Statistics, + player: player) { (remoteMetadata: Metadata?, error: Error?) in + if let error = error { + Logger.log("Error loading storage from firebase: \(error)", self) + } else if let currentMetadata = remoteMetadata { + Logger.log("Successfully loaded statistics from firebase", self) + metadata = currentMetadata + } else { + // No error and no database implies that database is empty, return nil + Logger.log("No error and empty database, new one will NOT be auto-created", self) + } + } + + return metadata + } +} diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift deleted file mode 100644 index d5dfc185..00000000 --- a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// RemoteStorage+Metadata.swift -// TowerForge -// -// Created by Rubesh on 21/4/24. -// - -import Foundation - -/// This class adds utility methods specifically for Metadata operations -extension RemoteStorage { - - /// Checks if a player's metadata exists without requiring a closure input - static func checkIfPlayerMetadataExists(for playerId: String) -> Bool { - var exists = false - - RemoteStorage.remoteStorageExists(for: .Metadata, player: playerId) { bool in - exists = bool - } - - return exists - } -} diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift index f22d4a24..0d9c9220 100644 --- a/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift @@ -34,15 +34,79 @@ class RemoteStorage { }) } - /// Checks if a player's statistics exists without requiring a closure input - static func checkIfPlayerStorageExists(for playerId: String) -> Bool { - var exists = false + /// Saves the input StorageDatabase to firebase + static func saveDataToFirebase(for ref: FirebaseReference, + player: String, + with inputData: StorageDatabase, + completion: @escaping (Error?) -> Void) { + let databaseReference = FirebaseDatabaseReference(ref) + + do { + let encoder = JSONEncoder() + let data = try encoder.encode(inputData) + let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] - RemoteStorage.remoteStorageExists(for: .Statistics, player: playerId) { bool in - exists = bool + databaseReference.child(player).setValue(dictionary) { error, _ in + if let error = error { + Logger.log("Data could not be saved: \(error).", Self.self) + completion(error) + } else { + Logger.log("Data saved to Firebase successfully!", Self.self) + completion(nil) + } + } + } catch { + Logger.log("Error encoding Data: \(error)", Self.self) + completion(error) } + } + + /// Saves the input StorageDatabase to firebase + static func deleteDataFromFirebase(for ref: FirebaseReference, + player: String, + completion: @escaping (Error?) -> Void) { + let databaseReference = FirebaseDatabaseReference(ref) + + // Remove the data at the specific player node + databaseReference.child(player).removeValue { error, _ in + if let error = error { + Logger.log("Error deleting data: \(error).", self) + completion(error) + return + } + + Logger.log("Data for player \(player) successfully deleted from Firebase.", self) + completion(nil) + } + } - return exists + static func loadDataFromFirebase(for ref: FirebaseReference, + player: String, + completion: @escaping (T?, Error?) -> Void) { + let databaseReference = FirebaseDatabaseReference(ref) + + databaseReference.child(player).getData(completion: { error, snapshot in + if let error = error { + Logger.log("Error loading data from firebase: \(error.localizedDescription)", self) + completion(nil, error) + return + } + + guard let value = snapshot?.value as? [String: Any], + let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []) else { + completion(nil, nil) + return + } + + do { + let decoder = JSONDecoder() + let storageDatabase = try decoder.decode(T.self, from: jsonData) + completion(storageDatabase, nil) + } catch { + Logger.log("Error decoding StatisticsDatabase from Firebase: \(error)", self) + completion(nil, error) + } + }) } } diff --git a/TowerForge/TowerForge/Storage/StorageDatabase.swift b/TowerForge/TowerForge/StorageAPI/StorageDatabase.swift similarity index 79% rename from TowerForge/TowerForge/Storage/StorageDatabase.swift rename to TowerForge/TowerForge/StorageAPI/StorageDatabase.swift index 4c1e13e0..5c0b57b8 100644 --- a/TowerForge/TowerForge/Storage/StorageDatabase.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageDatabase.swift @@ -7,7 +7,7 @@ import Foundation -protocol StorageDatabase: Codable { +protocol StorageDatabase: Codable, AnyObject { func encode(to encoder: Encoder) throws init(from decoder: Decoder) throws } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 0d2dd8c8..ae3de7c1 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -8,6 +8,7 @@ import Foundation class StorageHandler: AuthenticationDelegate { + static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME static let fileName = Constants.LOCAL_STORAGE_FILE_NAME static let metadataName = Constants.METADATA_FILE_NAME @@ -69,6 +70,12 @@ class StorageHandler: AuthenticationDelegate { } func onLogin() { + Task { + await onAsyncLogin() + } + } + + func onAsyncLogin() async { Logger.log("IMPT: onLogin IS CALLED FROM STORAGE_HANDLER", Self.self) guard let userId = authenticationProvider.getCurrentUserId() else { @@ -78,11 +85,10 @@ class StorageHandler: AuthenticationDelegate { /// Update the playerId and metadata locally self.localUpdatePlayerIdAndMetadata(with: userId) - - if checkIfRemotePlayerDataExists() { - + if await checkIfRemotePlayerDataExists() { + await self.onReLogin() } else { - + self.onFirstLogin() } // if they don't, execute onFirstLogin @@ -97,9 +103,9 @@ class StorageHandler: AuthenticationDelegate { } /// Returns true only if both Metadata and Storage exist - func checkIfRemotePlayerDataExists() -> Bool { + func checkIfRemotePlayerDataExists() async -> Bool { let storageExists = RemoteStorage.checkIfPlayerStorageExists(for: Self.currentPlayerId) - let metadataExists = RemoteStorage.checkIfPlayerMetadataExists(for: Self.currentPlayerId) + let metadataExists = await RemoteStorage.checkIfPlayerMetadataExistsAsync(for: Self.currentPlayerId) if (storageExists && !metadataExists) || (!storageExists && metadataExists) { Logger.log("Inconsisteny error: Storage Exists: \(storageExists), Metadata Exists: \(metadataExists)") @@ -111,11 +117,44 @@ class StorageHandler: AuthenticationDelegate { func onLogout() { Logger.log("IMPT: onLOGOUT IS CALLED FROM STORAGE_HANDLER", Self.self) self.save() // Save any potential unsaved changes - - // Reset metadata to original value - metadata.resetIdentifier() + metadata.resetIdentifier() // Reset metadata to original value Logger.log("--- metadata reset to \(metadata.uniqueIdentifier)", Self.self) self.save() // Save updated metadata } + /// Returns true if re-login success, false otherwise + func onReLogin() async -> Bool { + // Fetch metadata + guard let remoteMetadata = await RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) else { + Logger.log("RELOGIN ERROR: REMOTE METADATA NOT FOUND") + return false + } + + // Fetch storage + guard let remoteStorage = RemoteStorage.loadStorageFromFirebase(player: Self.currentPlayerId) else { + Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") + return false + } + + var finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: statisticsDatabase) + + // Merge storage - MERGE + // Merge metadata - KEEP LATEST + // Set storage to merged storage + // Save new storage to file + // Universal save will automatically update metadata + // Universal save to remote + + return true + + } + + func onFirstLogin() { + RemoteStorage.saveStorageToFirebase(player: Self.currentPlayerId, + with: statisticsDatabase) + + RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, + with: metadata) + } + } From 014670f1b76493b15af4bed6604195cfecdbc3dc Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 10:05:56 +0800 Subject: [PATCH 14/29] Add Async methods for StorageAPI --- .../TowerForge.xcodeproj/project.pbxproj | 4 + .../AuthenticationProvider.swift | 14 +- .../Metrics/Statistics/Statistic.swift | 12 ++ .../StorageAPI/RemoteStorage+Access.swift | 95 +++++++--- .../StorageAPI/StorageHandler+Auth.swift | 169 ++++++++++++++++++ .../StorageAPI/StorageHandler.swift | 126 ++++--------- 6 files changed, 304 insertions(+), 116 deletions(-) create mode 100644 TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index a33250f3..3aaf1c9c 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -206,6 +206,7 @@ BA436AEE2BD42F8100BE3E4F /* RemoteStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */; }; BA436AF02BD437D900BE3E4F /* LocalStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */; }; BA436AF22BD443A500BE3E4F /* RemoteStorage+Access.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */; }; + BA436AF42BD4AB8400BE3E4F /* StorageHandler+Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF32BD4AB8400BE3E4F /* StorageHandler+Auth.swift */; }; BA443D3D2BAD9557009F0FFB /* RemoveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */; }; BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */; }; BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D412BAD9885009F0FFB /* DamageEventTests.swift */; }; @@ -488,6 +489,7 @@ BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStorage.swift; sourceTree = ""; }; BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorage+Metadata.swift"; sourceTree = ""; }; BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteStorage+Access.swift"; sourceTree = ""; }; + BA436AF32BD4AB8400BE3E4F /* StorageHandler+Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageHandler+Auth.swift"; sourceTree = ""; }; BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSystem.swift; sourceTree = ""; }; BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEvent.swift; sourceTree = ""; }; BA443D412BAD9885009F0FFB /* DamageEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageEventTests.swift; sourceTree = ""; }; @@ -1055,6 +1057,7 @@ isa = PBXGroup; children = ( BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */, + BA436AF32BD4AB8400BE3E4F /* StorageHandler+Auth.swift */, BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */, BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */, BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */, @@ -1624,6 +1627,7 @@ 9B0406122BB889940026E903 /* PowerUpNode.swift in Sources */, 3CCF9CAF2BAB1A96004D170E /* SceneUpdateDelegate.swift in Sources */, BA82C7402BC8674A000515A0 /* StatisticsFactory.swift in Sources */, + BA436AF42BD4AB8400BE3E4F /* StorageHandler+Auth.swift in Sources */, 3CAC4A692BB697A400A5D22E /* SpriteRenderStage.swift in Sources */, 523C29302BBD0916004C6EAC /* GameWaitingRoomViewController.swift in Sources */, BA2F5AC12BC80BE500CBD8E9 /* Statistic.swift in Sources */, diff --git a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationProvider.swift b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationProvider.swift index 1c99efa5..be74952c 100644 --- a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationProvider.swift +++ b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationProvider.swift @@ -55,12 +55,16 @@ class AuthenticationProvider { observers.forEach { $0.onLogout() } } - func getCurrentUserId() -> String? { - var currentUserId: String? - self.authenticationManager.getUserData { authData, _ in - currentUserId = authData?.userId + func getCurrentUserId(completion: @escaping (String?, Error?) -> Void) { + self.authenticationManager.getUserData { authData, error in + if let error = error { + Logger.log("Error retrieving CurrentUserId \(error)", self) + completion(nil, error) + } else if let playerId = authData?.userId { + Logger.log("Successfully retrieved currentUserId", self) + completion(playerId, nil) + } } - return currentUserId } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index edf67a57..d5954cd9 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -178,6 +178,18 @@ extension Statistic { /// This extension allows Statistic to be merged extension Statistic { + func merge(with that: Self) -> Self { + let this = self + + let largerPermanent = Double.maximum(this.permanentValue, that.permanentValue) + let largerCurrent = Double.maximum(this.currentValue, that.currentValue) + let largerMaxCurrent = Double.maximum(this.maximumCurrentValue, that.maximumCurrentValue) + + return Self(permanentValue: largerPermanent, + currentValue: largerCurrent, + maxCurrentValue: largerMaxCurrent) + } + static func merge(this: Self, that: Self) -> Self { let largerPermanent = Double.maximum(this.permanentValue, that.permanentValue) let largerCurrent = Double.maximum(this.currentValue, that.currentValue) diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift index 0b287e63..0a079275 100644 --- a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift @@ -13,17 +13,6 @@ import Foundation /// functions, for both storage and metadata. extension RemoteStorage { - /// Checks if a player's metadata exists without requiring a closure input - /*static func checkIfPlayerMetadataExists(for playerId: String) -> Bool { - var exists = false - - Self.remoteStorageExists(for: .Metadata, player: playerId) { bool in - exists = bool - } - - return exists - }*/ - static func checkIfPlayerMetadataExistsAsync(for playerId: String) async -> Bool { await withCheckedContinuation { continuation in Self.remoteStorageExists(for: .Metadata, player: playerId) { exists in @@ -32,23 +21,48 @@ extension RemoteStorage { } } - /// Checks if a player's statistics exists without requiring a closure input - static func checkIfPlayerStorageExists(for playerId: String) -> Bool { - var exists = false - - Self.remoteStorageExists(for: .Statistics, player: playerId) { bool in - exists = bool + static func checkIfRemotePlayerDataExists(playerId: String, completion: @escaping (Bool) -> Void) { + // Check for player storage existence + RemoteStorage.checkIfPlayerDatabaseExists(for: playerId) { storageExists in + // Check for player metadata existence + RemoteStorage.checkIfPlayerMetadataExists(for: playerId) { metadataExists in + // Check for any inconsistencies + if (storageExists && !metadataExists) || (!storageExists && metadataExists) { + Logger.log("Inconsistency error: Storage Exists: \(storageExists), Metadata Exists: \(metadataExists)") + } + + // Return the combined result + completion(storageExists && metadataExists) + } } + } + + /// Checks if a player's metadata exists + static func checkIfPlayerMetadataExists(for playerId: String, + completion: @escaping (Bool) -> Void) { - return exists + Self.remoteStorageExists(for: .Metadata, player: playerId, completion: completion) } - static func saveMetadataToFirebase(player: String, with inputData: Metadata) { + /// Checks if a player's statistics exists + static func checkIfPlayerDatabaseExists(for playerId: String, + completion: @escaping (Bool) -> Void) { + + Self.remoteStorageExists(for: .Statistics, player: playerId, completion: completion) + } + + /// Checks if a player's statistics exists without requiring a closure input + + static func saveMetadataToFirebase(player: String, + with inputData: Metadata, + completion: @escaping (Bool) -> Void) { let metadataCompletion: (Error?) -> Void = { error in if let error = error { Logger.log("Saving metadata to firebase error: \(error)", self) + completion(false) } else { Logger.log("Saving metadata to firebase success", self) + completion(true) } } @@ -58,12 +72,16 @@ extension RemoteStorage { completion: metadataCompletion) } - static func saveStorageToFirebase(player: String, with inputData: StatisticsDatabase) { + static func saveDatabaseToFirebase(player: String, + with inputData: StatisticsDatabase, + completion: @escaping (Bool) -> Void) { let storageCompletion: (Error?) -> Void = { error in if let error = error { Logger.log("Saving storage to firebase error: \(error)", self) + completion(false) } else { Logger.log("Saving storage to firebase success", self) + completion(true) } } @@ -79,7 +97,7 @@ extension RemoteStorage { if let error = error { Logger.log("Deleting metadata from firebase error: \(error)", self) } else { - Logger.log("Saving Metadata from firebase success", self) + Logger.log("Deleting Metadata from firebase success", self) } } @@ -87,7 +105,7 @@ extension RemoteStorage { } /// Deletes storage for the specific player from Firebase - static func deleteStorageFromFirebase(player: String) { + static func deleteDatabaseFromFirebase(player: String) { let completion: (Error?) -> Void = { error in if let error = error { Logger.log("Deleting storage from firebase error: \(error)", self) @@ -99,7 +117,36 @@ extension RemoteStorage { Self.deleteDataFromFirebase(for: .Statistics, player: player, completion: completion) } - static func loadStorageFromFirebase(player: String) -> StatisticsDatabase? { + static func loadMetadataFromFirebase(player: String, + completion: @escaping (Metadata?, Error?) -> Void) { + Self.checkIfPlayerDatabaseExists(for: player) { exists in + guard exists else { + completion(nil, nil) // Consider custom error + return + } + + Self.loadDataFromFirebase(for: .Metadata, player: player) { (metadata: Metadata?, error: Error?) in + completion(metadata, error) + } + } + } + + static func loadDatabaseFromFirebase(player: String, + completion: @escaping (StatisticsDatabase?, Error?) -> Void) { + Self.checkIfPlayerDatabaseExists(for: player) { exists in + guard exists else { + completion(nil, nil) // Consider custom error + return + } + + Self.loadDataFromFirebase(for: .Statistics, + player: player) { (statistics: StatisticsDatabase?, error: Error?) in + completion(statistics, error) + } + } + } + + /*static func loadStorageFromFirebase(player: String) -> StatisticsDatabase? { guard Self.checkIfPlayerStorageExists(for: player) else { return nil } @@ -141,5 +188,5 @@ extension RemoteStorage { } return metadata - } + }*/ } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift new file mode 100644 index 00000000..7c199e20 --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -0,0 +1,169 @@ +// +// StorageHandler+Auth.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +/// This extension adds authentication methods to StorageHandler +extension StorageHandler { + /*func onLogin() { + Task { + onAsyncLogin() + } + }*/ + + func onLogin() { + Logger.log("LOGIN: IS CALLED FROM STORAGE HANDLER", Self.self) + + authenticationProvider.getCurrentUserId { [weak self] userId, error in + guard let self = self else { + return + } + + if let error = error { + Logger.log("IMPT: onLogin failed due to error: \(error.localizedDescription) from STORAGE_HANDLER", self) + return + } + + guard let userId = userId else { + Logger.log("IMPT: onLogin failed due to userId nil from STORAGE_HANDLER", self) + return + } + + // Update the playerId and metadata locally + self.localUpdatePlayerIdAndMetadata(with: userId) + + // Asynchronously check if remote data exists for currentPlayerId + self.checkIfRemotePlayerDataExists { exists in + if exists { + self.onReLogin { reloginSuccess in + if !reloginSuccess { + Logger.log("RE-LOGIN FAILED: Remote player exists but Re-login failure", self) + } + } + } else { + self.onFirstLogin() + } + } + } + } + + /// Helper function to asynchronously check if both Metadata and Storage exist + func checkIfRemotePlayerDataExists(completion: @escaping (Bool) -> Void) { + RemoteStorage.checkIfRemotePlayerDataExists(playerId: Self.currentPlayerId, completion: completion) + } + + func onFirstLogin() { + self.remoteSave() + } + + /// Returns true if re-login success, false otherwise + func onReLogin(completion: @escaping (Bool) -> Void) { + RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) { remoteMetadata, _ in + guard let remoteMetadata = remoteMetadata else { + Logger.log("RELOGIN ERROR: REMOTE METADATA NOT FOUND") + completion(false) + return + } + + RemoteStorage.loadDatabaseFromFirebase(player: Self.currentPlayerId) { remoteStorage, _ in + guard let remoteStorage = remoteStorage else { + Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") + completion(false) + return + } + + guard let finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: self.statisticsDatabase) else { + Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") + completion(false) + return + } + + LocalStorage.saveDatabaseToLocalStorage(finalStorage) + RemoteStorage.saveDatabaseToFirebase(player: Self.currentPlayerId, + with: finalStorage) { saveStorageSuccess in + if !saveStorageSuccess { + completion(false) + return + } + + self.metadata = remoteMetadata + RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, with: self.metadata) { + saveMetadataSuccess in + + completion(saveMetadataSuccess) + } + } + } + } + } + + func localUpdatePlayerIdAndMetadata(with userId: String) { + Constants.CURRENT_PLAYER_ID = userId + metadata.updateIdentifierToCurrentID() + self.localSave() + } + + func onLogout() { + Logger.log("LOGOUT: CALLED FROM STORAGE_HANDLER", Self.self) + self.save() // Save any potential unsaved changes + metadata.resetIdentifier() // Reset metadata to original value + Logger.log("LOGOUT: metadata reset to \(metadata.uniqueIdentifier)", Self.self) + self.save() // Save updated metadata + } + + /// Returns true if re-login success, false otherwise + /*func onReLogin() -> Bool { + // Fetch metadata + guard let remoteMetadata = RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) else { + Logger.log("RELOGIN ERROR: REMOTE METADATA NOT FOUND") + return false + } + + // Fetch storage + guard let remoteStorage = RemoteStorage.loadDatabaseFromFirebase(player: Self.currentPlayerId) else { + Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") + return false + } + + var finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: statisticsDatabase) + + // Merge storage - MERGE + // Merge metadata - KEEP LATEST + // Set storage to merged storage + // Save new storage to file + // Universal save will automatically update metadata + // Universal save to remote + + return true + + }*/ + + /*func onReLoginAsync() async -> Bool { + do { + // Fetch metadata and storage asynchronously + let remoteMetadata = try await RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) + let remoteStorage = try await RemoteStorage.loadDatabaseFromFirebase(player: Self.currentPlayerId) + + // Merge storage - assume a static merge function in StatisticsDatabase + let finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: statisticsDatabase) + + // Save merged storage locally and remotely + LocalStorage.saveDatabaseToLocal(finalStorage) // Assuming there's a method to save locally + try await RemoteStorage.saveDatabaseToFirebase(player: Self.currentPlayerId, with: finalStorage) + + // Update metadata locally and remotely + // Assuming newer metadata is better or merge strategy is implemented + metadata = remoteMetadata + try await RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, with: metadata) + + return true + } catch { + Logger.log("RELOGIN ERROR: \(error)") + return false + } + }*/ +} diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index ae3de7c1..33ffd4d0 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -16,7 +16,7 @@ class StorageHandler: AuthenticationDelegate { static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } - private let authenticationProvider = AuthenticationProvider() + let authenticationProvider = AuthenticationProvider() var statisticsDatabase = StatisticsDatabase() var metadata = Metadata() @@ -56,105 +56,57 @@ class StorageHandler: AuthenticationDelegate { /// Universal save func save() { - Logger.log("SAVE: Saving Stats and Metadata to LocalStorage", Self.self) - LocalStorage.saveDatabaseToLocalStorage(statisticsDatabase) - metadata.updateTimeToNow() - LocalStorage.saveMetadataToLocalStorage(metadata) + Logger.log("U-SAVE: Saving Stats and Metadata Universally", Self.self) + self.localSave() + self.remoteSave() } - /// Universal delete - func delete() { - Logger.log("DELETE: Deleting Stats and Metadata to LocalStorage", Self.self) - LocalStorage.deleteDatabaseFromLocalStorage() - LocalStorage.deleteMetadataFromLocalStorage() + func localSave() { + Logger.log("L-SAVE: Saving Stats and Metadata to LocalStorage", Self.self) + LocalStorage.saveDatabaseToLocalStorage(self.statisticsDatabase) + metadata.updateTimeToNow() + LocalStorage.saveMetadataToLocalStorage(self.metadata) } - func onLogin() { - Task { - await onAsyncLogin() + func remoteSave() { + Logger.log("R-SAVE: Saving Stats and Metadata to RemoteData", Self.self) + RemoteStorage.saveDatabaseToFirebase(player: Self.currentPlayerId, with: self.statisticsDatabase) { + if $0 { + Logger.log("R-SAVE-DB SUCCESS") + } else { + Logger.log("R-SAVE-DB FAILURE") + } } - } - func onAsyncLogin() async { - Logger.log("IMPT: onLogin IS CALLED FROM STORAGE_HANDLER", Self.self) - - guard let userId = authenticationProvider.getCurrentUserId() else { - Logger.log("IMPT: onLogin failed due to userId nil from STORAGE_HANDLER", Self.self) - return - } - - /// Update the playerId and metadata locally - self.localUpdatePlayerIdAndMetadata(with: userId) - if await checkIfRemotePlayerDataExists() { - await self.onReLogin() - } else { - self.onFirstLogin() - } - - // if they don't, execute onFirstLogin - - // if they do, execute onReLogin - } - - func localUpdatePlayerIdAndMetadata(with userId: String) { - Constants.CURRENT_PLAYER_ID = userId - metadata.updateIdentifierToCurrentID() - self.save() - } - - /// Returns true only if both Metadata and Storage exist - func checkIfRemotePlayerDataExists() async -> Bool { - let storageExists = RemoteStorage.checkIfPlayerStorageExists(for: Self.currentPlayerId) - let metadataExists = await RemoteStorage.checkIfPlayerMetadataExistsAsync(for: Self.currentPlayerId) - - if (storageExists && !metadataExists) || (!storageExists && metadataExists) { - Logger.log("Inconsisteny error: Storage Exists: \(storageExists), Metadata Exists: \(metadataExists)") + metadata.updateTimeToNow() + RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, with: self.metadata) { + if $0 { + Logger.log("R-SAVE-MD SUCCESS") + } else { + Logger.log("R-SAVE-MD FAILURE") + } } - - return storageExists && metadataExists } - func onLogout() { - Logger.log("IMPT: onLOGOUT IS CALLED FROM STORAGE_HANDLER", Self.self) - self.save() // Save any potential unsaved changes - metadata.resetIdentifier() // Reset metadata to original value - Logger.log("--- metadata reset to \(metadata.uniqueIdentifier)", Self.self) - self.save() // Save updated metadata + /// Universal delete + func delete() { + Logger.log("U-DELETE: Deleting Stats and Metadata Universally", Self.self) + self.statisticsDatabase.setToDefault() + self.metadata = Metadata() + self.localDelete() + self.remoteDelete() } - /// Returns true if re-login success, false otherwise - func onReLogin() async -> Bool { - // Fetch metadata - guard let remoteMetadata = await RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) else { - Logger.log("RELOGIN ERROR: REMOTE METADATA NOT FOUND") - return false - } - - // Fetch storage - guard let remoteStorage = RemoteStorage.loadStorageFromFirebase(player: Self.currentPlayerId) else { - Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") - return false - } - - var finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: statisticsDatabase) - - // Merge storage - MERGE - // Merge metadata - KEEP LATEST - // Set storage to merged storage - // Save new storage to file - // Universal save will automatically update metadata - // Universal save to remote - - return true - + func localDelete() { + Logger.log("L-DELETE: Deleting Stats and Metadata from LocalStorage", Self.self) + LocalStorage.deleteDatabaseFromLocalStorage() + LocalStorage.deleteMetadataFromLocalStorage() } - func onFirstLogin() { - RemoteStorage.saveStorageToFirebase(player: Self.currentPlayerId, - with: statisticsDatabase) - - RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, - with: metadata) + func remoteDelete() { + Logger.log("R-DELETE: Deleting Stats and Metadata from LocalStorage", Self.self) + RemoteStorage.deleteDatabaseFromFirebase(player: Self.currentPlayerId) + RemoteStorage.deleteMetadataFromFirebase(player: Self.currentPlayerId) } } From 034525d0174fd652663939722b88264e04f845aa Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 10:34:20 +0800 Subject: [PATCH 15/29] Finalize updated StorageAPI --- .../TowerForge.xcodeproj/project.pbxproj | 4 + .../Metrics/Statistics/Statistic.swift | 12 +-- .../StorageAPI/StorageHandler+Auth.swift | 26 +++---- .../StorageAPI/StorageHandler+Conflict.swift | 77 +++++++++++++++++++ .../StorageAPI/StorageHandler.swift | 1 + 5 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 3aaf1c9c..a5aad37a 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -207,6 +207,7 @@ BA436AF02BD437D900BE3E4F /* LocalStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */; }; BA436AF22BD443A500BE3E4F /* RemoteStorage+Access.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */; }; BA436AF42BD4AB8400BE3E4F /* StorageHandler+Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF32BD4AB8400BE3E4F /* StorageHandler+Auth.swift */; }; + BA436AF62BD4AC2200BE3E4F /* StorageHandler+Conflict.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF52BD4AC2200BE3E4F /* StorageHandler+Conflict.swift */; }; BA443D3D2BAD9557009F0FFB /* RemoveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */; }; BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */; }; BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D412BAD9885009F0FFB /* DamageEventTests.swift */; }; @@ -490,6 +491,7 @@ BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorage+Metadata.swift"; sourceTree = ""; }; BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteStorage+Access.swift"; sourceTree = ""; }; BA436AF32BD4AB8400BE3E4F /* StorageHandler+Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageHandler+Auth.swift"; sourceTree = ""; }; + BA436AF52BD4AC2200BE3E4F /* StorageHandler+Conflict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageHandler+Conflict.swift"; sourceTree = ""; }; BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSystem.swift; sourceTree = ""; }; BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEvent.swift; sourceTree = ""; }; BA443D412BAD9885009F0FFB /* DamageEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageEventTests.swift; sourceTree = ""; }; @@ -1058,6 +1060,7 @@ children = ( BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */, BA436AF32BD4AB8400BE3E4F /* StorageHandler+Auth.swift */, + BA436AF52BD4AC2200BE3E4F /* StorageHandler+Conflict.swift */, BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */, BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */, BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */, @@ -1774,6 +1777,7 @@ 3C9955AF2BA48FD200D33FA5 /* MeleeUnit.swift in Sources */, 5240D0AD2BB33D4C004F1486 /* PositionSystem.swift in Sources */, 5295A2022BA9FBD9005018A8 /* SceneManagerDelegate.swift in Sources */, + BA436AF62BD4AC2200BE3E4F /* StorageHandler+Conflict.swift in Sources */, 9B274DC42BD24B210062715C /* DamagePowerUp.swift in Sources */, 52DF5FE12BA3349600135367 /* TFTextures.swift in Sources */, 520062582BA8ED73000DBA30 /* HomeComponent.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index d5954cd9..0fc3540c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -181,9 +181,9 @@ extension Statistic { func merge(with that: Self) -> Self { let this = self - let largerPermanent = Double.maximum(this.permanentValue, that.permanentValue) - let largerCurrent = Double.maximum(this.currentValue, that.currentValue) - let largerMaxCurrent = Double.maximum(this.maximumCurrentValue, that.maximumCurrentValue) + let largerPermanent = max(this.permanentValue, that.permanentValue) + let largerCurrent = max(this.currentValue, that.currentValue) + let largerMaxCurrent = max(this.maximumCurrentValue, that.maximumCurrentValue) return Self(permanentValue: largerPermanent, currentValue: largerCurrent, @@ -191,9 +191,9 @@ extension Statistic { } static func merge(this: Self, that: Self) -> Self { - let largerPermanent = Double.maximum(this.permanentValue, that.permanentValue) - let largerCurrent = Double.maximum(this.currentValue, that.currentValue) - let largerMaxCurrent = Double.maximum(this.maximumCurrentValue, that.maximumCurrentValue) + let largerPermanent = max(this.permanentValue, that.permanentValue) + let largerCurrent = max(this.currentValue, that.currentValue) + let largerMaxCurrent = max(this.maximumCurrentValue, that.maximumCurrentValue) return Self(permanentValue: largerPermanent, currentValue: largerCurrent, diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index 7c199e20..cef9f6a6 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -62,6 +62,8 @@ extension StorageHandler { /// Returns true if re-login success, false otherwise func onReLogin(completion: @escaping (Bool) -> Void) { + // Executed upon confirmation that both types of data exist remotely + // 1. Load metadata from firebase RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) { remoteMetadata, _ in guard let remoteMetadata = remoteMetadata else { Logger.log("RELOGIN ERROR: REMOTE METADATA NOT FOUND") @@ -69,6 +71,7 @@ extension StorageHandler { return } + // 2. Load statistics from firebase RemoteStorage.loadDatabaseFromFirebase(player: Self.currentPlayerId) { remoteStorage, _ in guard let remoteStorage = remoteStorage else { Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") @@ -76,26 +79,19 @@ extension StorageHandler { return } - guard let finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: self.statisticsDatabase) else { - Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") - completion(false) - return - } - - LocalStorage.saveDatabaseToLocalStorage(finalStorage) - RemoteStorage.saveDatabaseToFirebase(player: Self.currentPlayerId, - with: finalStorage) { saveStorageSuccess in - if !saveStorageSuccess { + // 3. Resolve conflict between remote statistics and current statistics + Self.resolveConflict(this: remoteStorage, that: self.statisticsDatabase) { resolvedStats in + guard let finalStorage = resolvedStats else { + Logger.log("RELOGIN ERROR: CONFLICT RESOLUTION FAILURE") completion(false) return } - self.metadata = remoteMetadata - RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, with: self.metadata) { - saveMetadataSuccess in + // 4. Update current instance to resolve storage + self.statisticsDatabase = finalStorage - completion(saveMetadataSuccess) - } + // 4. Save newly resolved storage universally + self.save() } } } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift new file mode 100644 index 00000000..71f78974 --- /dev/null +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift @@ -0,0 +1,77 @@ +// +// StorageHandler+Conflict.swift +// TowerForge +// +// Created by Rubesh on 21/4/24. +// + +import Foundation + +/// This extension adds conflict resolution methods to StorageHandler +extension StorageHandler { + + /// Returns the StatisticsDatabase from the location that corresponds to the most recent save. + static func getLocationWithLatestMetadata(completion: @escaping (StorageLocation?) -> Void) { + RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) { remoteMetadata, remoteError in + let localMetadata = LocalStorage.loadMetadataFromLocalStorage() + + // Handle errors or nil cases + if let remoteError = remoteError { + Logger.log("Error occurred retrieving metadata: \(remoteError)", self) + } + + switch (remoteMetadata, localMetadata) { + case (_?, nil): + completion(.Remote) + case (nil, _?): + completion(.Local) + case (let remote?, let local?): + completion(remote > local ? .Remote : .Local) + default: + completion(nil) + } + } + } + + static func loadLatest(completion: @escaping (StatisticsDatabase?) -> Void) { + Self.getLocationWithLatestMetadata { location in + switch location { + + case .Local: + if let stats = LocalStorage.loadDatabaseFromLocalStorage() { + completion(stats) + } else { + Logger.log("Error: Failed to load local database.", self) + completion(nil) + } + + case .Remote: + RemoteStorageManager.loadDatabaseFromFirebase { statsData, error in + if let error = error { + Logger.log("Error occurred loading from database: \(error)", self) + completion(nil) + } else { + completion(statsData) + } + } + + default: + Logger.log("No valid metadata found, cannot determine latest storage.", self) + completion(nil) + } + } + } + + static func resolveConflict(this: StatisticsDatabase, + that: StatisticsDatabase, + completion: @escaping (StatisticsDatabase?) -> Void) { + + switch CONFLICT_RESOLUTION { + case .MERGE: + completion(StatisticsDatabase.merge(this: this, that: that)) + case .KEEP_LATEST_ONLY: + Self.loadLatest { completion($0) } + } + } + +} diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 33ffd4d0..57978061 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -13,6 +13,7 @@ class StorageHandler: AuthenticationDelegate { static let fileName = Constants.LOCAL_STORAGE_FILE_NAME static let metadataName = Constants.METADATA_FILE_NAME + static var CONFLICT_RESOLUTION: StorageConflictResolution { Constants.CONFLICT_RESOLTION } static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } From e489d4bf4663d9fefbfdc1d0e881a6bf591294c0 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 10:52:15 +0800 Subject: [PATCH 16/29] Add updated Statistics merging --- .../Metrics/Statistics/Statistic.swift | 8 +++--- .../Statistics/StatisticsDatabase+Merge.swift | 26 +++++-------------- .../StorageAPI/StorageHandler+Auth.swift | 2 +- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 0fc3540c..d1a4601c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -190,13 +190,13 @@ extension Statistic { maxCurrentValue: largerMaxCurrent) } - static func merge(this: Self, that: Self) -> Self { + static func merge(this: T, that: T) -> T { let largerPermanent = max(this.permanentValue, that.permanentValue) let largerCurrent = max(this.currentValue, that.currentValue) let largerMaxCurrent = max(this.maximumCurrentValue, that.maximumCurrentValue) - return Self(permanentValue: largerPermanent, - currentValue: largerCurrent, - maxCurrentValue: largerMaxCurrent) + return T(permanentValue: largerPermanent, + currentValue: largerCurrent, + maxCurrentValue: largerMaxCurrent) } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index b3b0c4f0..348cd7d9 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -48,38 +48,26 @@ extension StatisticsDatabase: Equatable { rhs = that } - let mergedStats = StatisticsDatabase() + let mergedStats = StatisticsFactory.getDefaultStatisticsDatabase() // Merge lhs statistics for (key, lhsStat) in lhs.statistics { mergedStats.statistics[key] = lhsStat } - // Merge rhs statistics and resolve conflicts for (key, rhsStat) in rhs.statistics { if let lhsStat = mergedStats.statistics[key] { - - // If lhs has the key, compare and choose the one with the greater magnitude. - if lhsStat.permanentValue < rhsStat.permanentValue || lhsStat.currentValue < rhsStat.currentValue { - - mergedStats.statistics[key]?.permanentValue = Double.maximumMagnitude(lhsStat.permanentValue, - rhsStat.permanentValue) - - mergedStats.statistics[key]?.currentValue = Double.maximumMagnitude(lhsStat.currentValue, - rhsStat.currentValue) - - mergedStats.statistics[key]?.currentValue = Double.maximumMagnitude(lhsStat.maximumCurrentValue, - rhsStat.maximumCurrentValue) - } - // If they are equal, lhsStat is already set, so do nothing. + mergedStats.statistics[key] = lhsStat.merge(with: rhsStat) + Logger.log("MERGE-LOOP: Statistic \(key) updated to " + + "\(String(describing: mergedStats.statistics[key]))", self) } else { - - // If lhs does not have the key, simply add the rhs stat. mergedStats.statistics[key] = rhsStat + Logger.log("MERGE-LOOP: Statistic \(key) created with " + + "\(String(describing: mergedStats.statistics[key]))", self) } } - Logger.log("SDB: Merged stats contain \(mergedStats.toString())") + Logger.log("SDB: Merged stats contain \(mergedStats.toString())", self) return mergedStats } } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index cef9f6a6..d12a39cf 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -24,7 +24,7 @@ extension StorageHandler { } if let error = error { - Logger.log("IMPT: onLogin failed due to error: \(error.localizedDescription) from STORAGE_HANDLER", self) + Logger.log("IMPT: onLogin failed from STORAGE_HANDLER: \(error.localizedDescription)", self) return } From d993b180ee4cb6c33a6b555ae91c51cdc4614445 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 12:19:41 +0800 Subject: [PATCH 17/29] Add statistics merge functionality --- .../TotalDamageDealtStatistic.swift | 18 ++++++++++++++++++ .../Implemented/TotalDeathsStatistic.swift | 18 ++++++++++++++++++ .../Implemented/TotalGamesStatistic.swift | 18 ++++++++++++++++++ .../Implemented/TotalKillsStatistic.swift | 19 +++++++++++++++++++ .../Metrics/Statistics/Statistic.swift | 15 +++------------ .../Statistics/StatisticsDatabase+Merge.swift | 3 ++- 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift index e1855c74..45472b31 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift @@ -54,4 +54,22 @@ final class TotalDamageDealtStatistic: Statistic { self.init(permanentValue: value, currentValue: current) } + + func merge(with that: T) -> T? { + guard let that = that as? Self else { + return nil + } + + let largerPermanent = max(self.permanentValue, that.permanentValue) + let largerCurrent = max(self.currentValue, that.currentValue) + let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + + guard let stat = Self(permanentValue: largerPermanent, + currentValue: largerCurrent, + maxCurrentValue: largerMaxCurrent) as? T else { + return nil + } + + return stat + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 54b2343d..271bb698 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -55,4 +55,22 @@ final class TotalDeathsStatistic: Statistic { self.init(permanentValue: value, currentValue: current) } + func merge(with that: T) -> T? { + guard let that = that as? Self else { + return nil + } + + let largerPermanent = max(self.permanentValue, that.permanentValue) + let largerCurrent = max(self.currentValue, that.currentValue) + let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + + guard let stat = Self(permanentValue: largerPermanent, + currentValue: largerCurrent, + maxCurrentValue: largerMaxCurrent) as? T else { + return nil + } + + return stat + } + } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index fecb18b0..e3025772 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -52,4 +52,22 @@ final class TotalGamesStatistic: Statistic { self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) } + func merge(with that: T) -> T? { + guard let that = that as? Self else { + return nil + } + + let largerPermanent = max(self.permanentValue, that.permanentValue) + let largerCurrent = max(self.currentValue, that.currentValue) + let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + + guard let stat = Self(permanentValue: largerPermanent, + currentValue: largerCurrent, + maxCurrentValue: largerMaxCurrent) as? T else { + return nil + } + + return stat + } + } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index f406d296..5fbcae3e 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -53,4 +53,23 @@ final class TotalKillsStatistic: Statistic { self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) } + + func merge(with that: T) -> T? { + guard let that = that as? Self else { + return nil + } + + let largerPermanent = max(self.permanentValue, that.permanentValue) + let largerCurrent = max(self.currentValue, that.currentValue) + let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + + guard let stat = Self(permanentValue: largerPermanent, + currentValue: largerCurrent, + maxCurrentValue: largerMaxCurrent) as? T else { + return nil + } + + return stat + } + } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index d1a4601c..3d246c92 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -32,6 +32,9 @@ protocol Statistic: AnyObject, Codable { /// The significance value which this Statistic holds in generating XP static var expMultiplier: Double { get } + /// Function to specify merging of Statistics + func merge(with that: T) -> T? + /// Returns a StatisticUpdateLinkDatabase pertaining to this Statistic. /// Conforming Statistic types will have to implement their own links between event /// types and the action to take upon reception of that event's execution. @@ -178,18 +181,6 @@ extension Statistic { /// This extension allows Statistic to be merged extension Statistic { - func merge(with that: Self) -> Self { - let this = self - - let largerPermanent = max(this.permanentValue, that.permanentValue) - let largerCurrent = max(this.currentValue, that.currentValue) - let largerMaxCurrent = max(this.maximumCurrentValue, that.maximumCurrentValue) - - return Self(permanentValue: largerPermanent, - currentValue: largerCurrent, - maxCurrentValue: largerMaxCurrent) - } - static func merge(this: T, that: T) -> T { let largerPermanent = max(this.permanentValue, that.permanentValue) let largerCurrent = max(this.currentValue, that.currentValue) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index 348cd7d9..467ec7b0 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -19,7 +19,8 @@ extension StatisticsDatabase: Equatable { return lhs.statistics.keys.allSatisfy { (lhs.statistics[$0]?.statisticName == rhs.statistics[$0]?.statisticName) && (lhs.statistics[$0]?.permanentValue == rhs.statistics[$0]?.permanentValue) && - (lhs.statistics[$0]?.currentValue == rhs.statistics[$0]?.currentValue) + (lhs.statistics[$0]?.currentValue == rhs.statistics[$0]?.currentValue) && + (lhs.statistics[$0]?.maximumCurrentValue == rhs.statistics[$0]?.maximumCurrentValue) } } From e4a5740e1b8cef35d0726a437297df684b16a206 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 12:21:39 +0800 Subject: [PATCH 18/29] Update StatisticsDatabase merge function --- .../Metrics/Statistics/StatisticsDatabase+Merge.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index 467ec7b0..65f966fe 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -43,10 +43,14 @@ extension StatisticsDatabase: Equatable { if let this = this { lhs = this + } else { + lhs.setToDefault() } if let that = that { rhs = that + } else { + rhs.setToDefault() } let mergedStats = StatisticsFactory.getDefaultStatisticsDatabase() From fa5824bd2373b1bf1bc6904e2b125ad7fe9dea01 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 14:55:18 +0800 Subject: [PATCH 19/29] Finalize local storage functionality --- .../AppMain/Application/AppDelegate.swift | 3 +- .../AuthenticationManager.swift | 6 +- .../TowerForge/GameModule/GameWorld.swift | 6 +- .../Achievements/AchievementsEngine.swift | 2 +- .../Metrics/Missions/MissionsEngine.swift | 2 +- .../Metrics/Ranking/RankingEngine.swift | 20 +++--- .../TotalDamageDealtStatistic.swift | 4 +- .../Implemented/TotalDeathsStatistic.swift | 4 +- .../Implemented/TotalGamesStatistic.swift | 4 +- .../Implemented/TotalKillsStatistic.swift | 4 +- .../Statistics/StatisticsDatabase+Merge.swift | 4 +- .../Statistics/StatisticsDatabase.swift | 4 ++ .../Metrics/Statistics/StatisticsEngine.swift | 44 ++++++++----- .../StorageAPI/LocalStorage+Metadata.swift | 4 +- .../StorageAPI/StorageHandler+Auth.swift | 3 +- .../StorageAPI/StorageHandler.swift | 20 +++--- .../PlayerStatsViewController.swift | 61 ++++++++++++------- 17 files changed, 123 insertions(+), 72 deletions(-) diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index 0a68e094..f74de74f 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -23,7 +23,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { FirebaseApp.configure() /// Initialize all local storage - StorageManager.initializeAllStorage() + // StorageManager.initializeAllStorage() + StorageHandler.initializeLocalStorageIfNotPresent() /// Prepare audio player to begin playing music AudioManager.shared.setupAllAudioPlayers() diff --git a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift index 4dd6adeb..c73d3ec7 100644 --- a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift +++ b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift @@ -86,7 +86,8 @@ class AuthenticationManager: AuthenticationProtocol { let userData = AuthenticationData(userId: user.uid, email: email, username: user.displayName) - StorageManager.onLogin(with: userData.userId) // TODO: Consider if there might be a better way to do this + // StorageManager.onLogin(with: userData.userId) // TODO: Consider if there might be a better way to do this + // Constants.CURRENT_PLAYER_ID = userData.userId Logger.log("LOGIN: userId is \(userData.userId), email is \(userData.email)", self) self.delegate?.onLogin() completion(userData, nil) @@ -97,7 +98,8 @@ class AuthenticationManager: AuthenticationProtocol { do { try Auth.auth().signOut() self.delegate?.onLogout() - StorageManager.onLogout() + // StorageManager.onLogout() + // Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID completion(nil) } catch let error as NSError { completion(error) diff --git a/TowerForge/TowerForge/GameModule/GameWorld.swift b/TowerForge/TowerForge/GameModule/GameWorld.swift index ce66d4a3..4978c2de 100644 --- a/TowerForge/TowerForge/GameModule/GameWorld.swift +++ b/TowerForge/TowerForge/GameModule/GameWorld.swift @@ -20,7 +20,7 @@ class GameWorld { private let worldBounds: CGRect private var popup: StatePopupNode - private var storageHandler: StorageHandler + private var storageHandler = StorageHandler() private var statisticsEngine: StatisticsEngine unowned var scene: GameScene? { didSet { setUpScene() } } @@ -36,7 +36,7 @@ class GameWorld { grid = Grid(screenSize: worldBounds) popup = StatePopupNode() storageHandler = StorageHandler() - statisticsEngine = StatisticsEngine(with: storageHandler.statisticsDatabase) + statisticsEngine = StatisticsEngine(with: storageHandler) setUp() } @@ -130,6 +130,6 @@ extension GameWorld: GameEngineDelegate { func onGameCompleted(gameState: GameState, gameResults: [GameResult]) { Logger.log("\(gameState)", self) delegate?.showGameOverScene(isWin: gameState == .WIN, results: gameResults) - statisticsEngine.finalize() + statisticsEngine.finalizeAndSave() } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift index 1cd2d421..83d609ad 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift @@ -17,7 +17,7 @@ class AchievementsEngine: InferenceEngine, InferenceDataDelegate { var achievementsDatabase: AchievementsDatabase var statisticsDatabase: StatisticsDatabase { - statisticsEngine.statistics + statisticsEngine.statisticsDatabase } init(_ statisticsEngine: StatisticsEngine) { diff --git a/TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift b/TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift index 017eb69c..2955dcdc 100644 --- a/TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift @@ -11,7 +11,7 @@ class MissionsEngine: InferenceEngine, InferenceDataDelegate { unowned var statisticsEngine: StatisticsEngine var missionsDatabase: MissionsDatabase var statisticsDatabase: StatisticsDatabase { - statisticsEngine.statistics + statisticsEngine.statisticsDatabase } init(_ statisticsEngine: StatisticsEngine) { diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift index 78a169fd..fe80bd52 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -9,16 +9,22 @@ import Foundation /// The RankingEngine is responsible for generating rank and exp information. class RankingEngine: InferenceEngine, InferenceDataDelegate { + unowned var statisticsEngine: StatisticsEngine - // TODO: Consider expanding to more formula for .e.g double exp. static var defaultExpFormula: ((StatisticsDatabase) -> Double) = { $0.statistics.values.map { $0.rankValue }.reduce(into: .zero) { $0 += $1 } } - unowned var statisticsEngine: StatisticsEngine + init(_ statisticsEngine: StatisticsEngine) { + self.statisticsEngine = statisticsEngine + } + + deinit { + Logger.log("DEINIT: RankingEngine is deinitialized", self) + } var statisticsDatabase: StatisticsDatabase { - statisticsEngine.statistics + statisticsEngine.statisticsDatabase } var currentExp: Double { @@ -49,16 +55,12 @@ class RankingEngine: InferenceEngine, InferenceDataDelegate { currentRank.isOfficer() } - init(_ statisticsEngine: StatisticsEngine) { - self.statisticsEngine = statisticsEngine - } - - func updateOnReceive() { } - func getPermanentValueFor(_ stat: T.Type) -> Double { statisticsDatabase.getStatistic(for: stat.asType)?.permanentValue ?? .zero } + func updateOnReceive() { } + func percentageToNextRank() -> Double { let minScore = currentRank.valueRange.lowerBound let currentScore = Int(currentExp) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift index 45472b31..2613611b 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift @@ -61,12 +61,12 @@ final class TotalDamageDealtStatistic: Statistic { } let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerCurrent = max(self.currentValue, that.currentValue) let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: largerCurrent, + currentValue: .zero, maxCurrentValue: largerMaxCurrent) as? T else { + Logger.log("Statistic merging failed", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 271bb698..63f43189 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -61,12 +61,12 @@ final class TotalDeathsStatistic: Statistic { } let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerCurrent = max(self.currentValue, that.currentValue) let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: largerCurrent, + currentValue: .zero, maxCurrentValue: largerMaxCurrent) as? T else { + Logger.log("Statistic merging failed", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index e3025772..b4c151bb 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -58,12 +58,12 @@ final class TotalGamesStatistic: Statistic { } let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerCurrent = max(self.currentValue, that.currentValue) let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: largerCurrent, + currentValue: .zero, maxCurrentValue: largerMaxCurrent) as? T else { + Logger.log("Statistic merging failed", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index 5fbcae3e..6b1a1007 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -60,12 +60,12 @@ final class TotalKillsStatistic: Statistic { } let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerCurrent = max(self.currentValue, that.currentValue) let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: largerCurrent, + currentValue: .zero, maxCurrentValue: largerMaxCurrent) as? T else { + Logger.log("Statistic merging failed", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index 65f966fe..d3131f03 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -64,11 +64,11 @@ extension StatisticsDatabase: Equatable { if let lhsStat = mergedStats.statistics[key] { mergedStats.statistics[key] = lhsStat.merge(with: rhsStat) Logger.log("MERGE-LOOP: Statistic \(key) updated to " + - "\(String(describing: mergedStats.statistics[key]))", self) + "\(String(describing: mergedStats.statistics[key]?.toString()))", self) } else { mergedStats.statistics[key] = rhsStat Logger.log("MERGE-LOOP: Statistic \(key) created with " + - "\(String(describing: mergedStats.statistics[key]))", self) + "\(String(describing: mergedStats.statistics[key]?.toString()))", self) } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift index be9c20a0..11011d4d 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift @@ -23,6 +23,10 @@ final class StatisticsDatabase: StorageDatabase { statistics[statName] } + func getPermanentValueFor(_ stat: T.Type) -> Double { + self.getStatistic(for: stat.asType)?.permanentValue ?? .zero + } + func setToDefault() { statistics = StatisticsFactory.getDefaultStatisticsDatabase().statistics } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index b2ebde3c..b7d9d674 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -7,33 +7,46 @@ import Foundation -class StatisticsEngine { +class StatisticsEngine: InferenceDataDelegate { /// Core storage of Statistics - var statistics = StatisticsDatabase() + weak var statsEngineDelegate: StatisticsEngineDelegate? + var statisticsDatabase: StatisticsDatabase var eventStatisticLinks = EventStatisticLinkDatabase() var inferenceEngines: [InferenceEngineTypeWrapper: InferenceEngine] = [:] - init() { + /*init() { self.initializeStatistics() self.loadStatistics() self.setUpLinks() self.setUpInferenceEngines() - } + }*/ + + /*init(with statistics: StatisticsDatabase) { + self.statisticsDatabase = statistics + self.initializeStatistics() + self.setUpLinks() + self.setUpInferenceEngines() + }*/ - init(with statistics: StatisticsDatabase) { - self.statistics = statistics + init(with storageHandler: StatisticsEngineDelegate) { + self.statsEngineDelegate = storageHandler + self.statisticsDatabase = storageHandler.statisticsDatabase self.initializeStatistics() self.setUpLinks() self.setUpInferenceEngines() } + deinit { + Logger.log("DEINIT: StatisticsEngine is deinitialized", self) + } + /// Add statistics links manually private func setUpLinks() { let links = StatisticsFactory.eventStatisticLinks for key in links.keys { links[key]?.forEach { eventStatisticLinks.addStatisticLink(for: key.type, - with: statistics.getStatistic(for: $0)) + with: statisticsDatabase.getStatistic(for: $0)) } } } @@ -57,8 +70,8 @@ class StatisticsEngine { } /// Transfers over all transient metrics within statistics to permanent value. - func finalize() { - statistics.statistics.values.forEach { $0.finalizeStatistic() } + func finalizeAndSave() { + statisticsDatabase.statistics.values.forEach { $0.finalizeStatistic() } saveStatistics() } @@ -71,7 +84,7 @@ class StatisticsEngine { // stats.forEach { $0.update(for: eventType) } stats.forEach { $0.update(for: event) } - saveStatistics() + // saveStatistics() } /// TODO: Consider if passing the stats database directly is better or @@ -96,13 +109,16 @@ class StatisticsEngine { } private func saveStatistics() { - _ = StorageManager.saveUniversally(statistics) + Logger.log("STATISTICS_ENGINE SAVE: Statistics save triggered", self) + // Logger.log("STATISTICS_ENGINE SAVE: \(String(describing: statsEngineDelegate?.statisticsDatabase.toString()))", self) + statsEngineDelegate?.save() + // _ = StorageManager.saveUniversally(statistics) } - private func loadStatistics() { + /*private func loadStatistics() { if let loadedStats = StorageManager.loadUniversally() { - statistics = loadedStats + statisticsDatabase = loadedStats } - } + }*/ } diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift index 622d7ae2..37bd8c16 100644 --- a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift @@ -10,8 +10,10 @@ import Foundation /// This extension adds utility methods specifically for local metadata operations extension LocalStorage { static func initializeMetadataToLocalStorage() { - if LocalStorage.loadMetadataFromLocalStorage() != nil { + if let metadata = LocalStorage.loadMetadataFromLocalStorage() { Logger.log("--- Metadata exists locally", Self.self) + Constants.CURRENT_PLAYER_ID = metadata.uniqueIdentifier + Constants.CURRENT_DEVICE_ID = metadata.uniqueIdentifier } else { let defaultMetadata = Metadata() Constants.CURRENT_PLAYER_ID = defaultMetadata.uniqueIdentifier diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index d12a39cf..33c1f42c 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -104,11 +104,12 @@ extension StorageHandler { } func onLogout() { + Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID Logger.log("LOGOUT: CALLED FROM STORAGE_HANDLER", Self.self) self.save() // Save any potential unsaved changes metadata.resetIdentifier() // Reset metadata to original value Logger.log("LOGOUT: metadata reset to \(metadata.uniqueIdentifier)", Self.self) - self.save() // Save updated metadata + self.localSave() // Save updated metadata } /// Returns true if re-login success, false otherwise diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 57978061..4b3b1d94 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -7,7 +7,12 @@ import Foundation -class StorageHandler: AuthenticationDelegate { +protocol StatisticsEngineDelegate: AnyObject { + var statisticsDatabase: StatisticsDatabase { get set } + func save() +} + +class StorageHandler: AuthenticationDelegate, StatisticsEngineDelegate { static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME static let fileName = Constants.LOCAL_STORAGE_FILE_NAME @@ -42,7 +47,6 @@ class StorageHandler: AuthenticationDelegate { deinit { Logger.log("StorageHandler: Deinit is called", self) - save() } /// Initializes empty statistics and metadata if they don't already exist locally @@ -57,7 +61,7 @@ class StorageHandler: AuthenticationDelegate { /// Universal save func save() { - Logger.log("U-SAVE: Saving Stats and Metadata Universally", Self.self) + Logger.log("U-SAVE: Saving Stats and Metadata Universally for \(Self.currentPlayerId)", Self.self) self.localSave() self.remoteSave() } @@ -70,21 +74,21 @@ class StorageHandler: AuthenticationDelegate { } func remoteSave() { - Logger.log("R-SAVE: Saving Stats and Metadata to RemoteData", Self.self) + Logger.log("R-SAVE: Saving Stats and Metadata to RemoteData for \(Self.currentPlayerId)", Self.self) RemoteStorage.saveDatabaseToFirebase(player: Self.currentPlayerId, with: self.statisticsDatabase) { if $0 { - Logger.log("R-SAVE-DB SUCCESS") + Logger.log("R-SAVE-DB SUCCESS", self) } else { - Logger.log("R-SAVE-DB FAILURE") + Logger.log("R-SAVE-DB FAILURE", self) } } metadata.updateTimeToNow() RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, with: self.metadata) { if $0 { - Logger.log("R-SAVE-MD SUCCESS") + Logger.log("R-SAVE-MD SUCCESS", self) } else { - Logger.log("R-SAVE-MD FAILURE") + Logger.log("R-SAVE-MD FAILURE", self) } } } diff --git a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift index 7d367c74..7eaa5141 100644 --- a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift @@ -32,15 +32,12 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl @IBOutlet private var col: UILabel! @IBOutlet private var gen: UILabel! - var statsEngine = StatisticsEngine() - + // static var staticEngine = StatisticsEngine(with: StorageHandler()) + var statisticsEngine = StatisticsEngine(with: StorageHandler()) + var statisticsDatabase: StatisticsDatabase { statisticsEngine.statisticsDatabase } var achievements: AchievementsDatabase = getAchievements() var missions: MissionsDatabase = getMissions() - - var rankingEngine: RankingEngine { - let rankEngine = statsEngine.inferenceEngines[RankingEngine.asType] as? RankingEngine - return rankEngine! - } + var rankingEngine: RankingEngine { RankingEngine(statisticsEngine) } var rank: Rank { rankingEngine.currentRank } var exp: String { rankingEngine.currentExpAsString } @@ -49,25 +46,45 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl var deaths: Int { Int(rankingEngine.getPermanentValueFor(TotalDeathsStatistic.self)) } var games: Int { Int(rankingEngine.getPermanentValueFor(TotalGamesStatistic.self)) } + static func getMissions() -> MissionsDatabase { + let statsEngine = StatisticsEngine(with: StorageHandler()) + let missionsEngine = statsEngine.inferenceEngines[MissionsEngine.asType] as? MissionsEngine + missionsEngine!.missionsDatabase.setToDefault() + return missionsEngine!.missionsDatabase + } + + /*static func getRankingEngine() -> RankingEngine { + let statsEngine = StatisticsEngine(with: StorageHandler()) + guard let rankEngine = staticEngine.inferenceEngines[RankingEngine.asType] + as? RankingEngine else { + Logger.log("PLAYER STATS: Error - Ranking engine cannot be found or cast") + return RankingEngine(staticEngine) + } + return rankEngine + }*/ + static func getAchievements() -> AchievementsDatabase { - let statsEngine = StatisticsEngine() + let statsEngine = StatisticsEngine(with: StorageHandler()) let achEngine = statsEngine.inferenceEngines[AchievementsEngine.asType] as? AchievementsEngine achEngine!.achievementsDatabase.setToDefault() return achEngine!.achievementsDatabase } - static func getMissions() -> MissionsDatabase { - let statsEngine = StatisticsEngine() - let missionsEngine = statsEngine.inferenceEngines[MissionsEngine.asType] as? MissionsEngine - missionsEngine!.missionsDatabase.setToDefault() - return missionsEngine!.missionsDatabase + // Designated initializer for programmatic instantiation + /*init() { + self.statisticsEngine = StatisticsEngine(with: StorageHandler()) + super.init(nibName: nil, bundle: nil) } - static func getRankingEngine() -> RankingEngine { - let statsEngine = StatisticsEngine() - let rankEngine = statsEngine.inferenceEngines[RankingEngine.asType] as? RankingEngine - return rankEngine! - } + // Required initializer for loading the view controller from a storyboard + required init?(coder: NSCoder) { + self.statisticsEngine = StatisticsEngine(with: StorageHandler()) + super.init(coder: coder) + + // You might set a default value for myProperty here, or perhaps + // this initializer will never actually be used in your app if you're + // not loading this view controller from a storyboard. + }*/ override func viewDidLoad() { super.viewDidLoad() @@ -85,7 +102,7 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl initializePlayerStats() initializeRanks() highlightCurrentRank() - reloadAchievements() + reloadAll() } func initializeBackground() { @@ -152,7 +169,7 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl if tableView == achievementsView { return achievements.count } else if tableView == missionsView { - return missions.missions.count + return missions.count } return 0 } @@ -200,9 +217,11 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl } - func reloadAchievements() { + func reloadAll() { achievementsView.reloadData() + missionsView.reloadData() rankNameLabel.reloadInputViews() + view.reloadInputViews() } } From 653f24bf15344f2a142756d3da15417da75ab25c Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 15:07:11 +0800 Subject: [PATCH 20/29] Add log statements --- .../Metrics/Statistics/StatisticsDatabase+Merge.swift | 1 - .../TowerForge/Metrics/Statistics/StatisticsEngine.swift | 5 +++-- TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index d3131f03..fba9276e 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -19,7 +19,6 @@ extension StatisticsDatabase: Equatable { return lhs.statistics.keys.allSatisfy { (lhs.statistics[$0]?.statisticName == rhs.statistics[$0]?.statisticName) && (lhs.statistics[$0]?.permanentValue == rhs.statistics[$0]?.permanentValue) && - (lhs.statistics[$0]?.currentValue == rhs.statistics[$0]?.currentValue) && (lhs.statistics[$0]?.maximumCurrentValue == rhs.statistics[$0]?.maximumCurrentValue) } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index b7d9d674..7c3c681c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -71,6 +71,7 @@ class StatisticsEngine: InferenceDataDelegate { /// Transfers over all transient metrics within statistics to permanent value. func finalizeAndSave() { + Logger.log("------------------- STATISTICS ARE FINALIZED ----------------------- ", self) statisticsDatabase.statistics.values.forEach { $0.finalizeStatistic() } saveStatistics() } @@ -84,7 +85,7 @@ class StatisticsEngine: InferenceDataDelegate { // stats.forEach { $0.update(for: eventType) } stats.forEach { $0.update(for: event) } - // saveStatistics() + //saveStatistics() } /// TODO: Consider if passing the stats database directly is better or @@ -110,7 +111,7 @@ class StatisticsEngine: InferenceDataDelegate { private func saveStatistics() { Logger.log("STATISTICS_ENGINE SAVE: Statistics save triggered", self) - // Logger.log("STATISTICS_ENGINE SAVE: \(String(describing: statsEngineDelegate?.statisticsDatabase.toString()))", self) + Logger.log("STATISTICS_ENGINE SAVE: \(String(describing: statsEngineDelegate?.statisticsDatabase.toString()))", self) statsEngineDelegate?.save() // _ = StorageManager.saveUniversally(statistics) } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index 33c1f42c..8dbb0ab1 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -62,6 +62,7 @@ extension StorageHandler { /// Returns true if re-login success, false otherwise func onReLogin(completion: @escaping (Bool) -> Void) { + Logger.log("RE-LOGIN ENTERED", self) // Executed upon confirmation that both types of data exist remotely // 1. Load metadata from firebase RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) { remoteMetadata, _ in From 44463d69107ecd6ebeee90c3dff6a312c56427ba Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 15:53:14 +0800 Subject: [PATCH 21/29] Comment out merge functionality for now --- .../TowerForge.xcodeproj/project.pbxproj | 2 +- .../Commons/Enums/StorageEnums.swift | 1 + .../AuthenticationManager.swift | 2 +- .../TotalDamageDealtStatistic.swift | 9 +++---- .../Implemented/TotalDeathsStatistic.swift | 9 +++---- .../Implemented/TotalGamesStatistic.swift | 11 +++++---- .../Implemented/TotalKillsStatistic.swift | 9 +++---- .../Metrics/Statistics/Statistic.swift | 4 ++-- .../Metrics/Statistics/StatisticsEngine.swift | 24 +++++++------------ .../TowerForge/Storage/StorageManager.swift | 2 ++ .../{Storage => StorageAPI}/Metadata.swift | 0 .../StorageAPI/StorageHandler+Auth.swift | 10 +++++--- .../StorageAPI/StorageHandler+Conflict.swift | 2 ++ .../StorageAPI/StorageHandler.swift | 1 + 14 files changed, 47 insertions(+), 39 deletions(-) rename TowerForge/TowerForge/{Storage => StorageAPI}/Metadata.swift (100%) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index a5aad37a..3a711ccb 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -1058,6 +1058,7 @@ BA436AE82BD42F1100BE3E4F /* StorageAPI */ = { isa = PBXGroup; children = ( + BA82C76E2BCBDE91000515A0 /* Metadata.swift */, BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */, BA436AF32BD4AB8400BE3E4F /* StorageHandler+Auth.swift */, BA436AF52BD4AC2200BE3E4F /* StorageHandler+Conflict.swift */, @@ -1437,7 +1438,6 @@ BAFFB9512BB342E200D8301F /* Storage */ = { isa = PBXGroup; children = ( - BA82C76E2BCBDE91000515A0 /* Metadata.swift */, BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */, BA82C76A2BCBD682000515A0 /* StorageManager.swift */, BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, diff --git a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift index bf169c07..a11dccd0 100644 --- a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift +++ b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift @@ -34,6 +34,7 @@ class StorageEnums { enum StorageConflictResolution: String { case MERGE case KEEP_LATEST_ONLY + case PRESERVE_LOCAL } struct DynamicCodingKeys: CodingKey { diff --git a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift index c73d3ec7..fb9a90ad 100644 --- a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift +++ b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift @@ -108,7 +108,7 @@ class AuthenticationManager: AuthenticationProtocol { func getUserData(completion: @escaping (AuthenticationData?, Error?) -> Void) { if let currentUser = Auth.auth().currentUser { // User is currently logged in, fetch user data - print(currentUser) + Logger.log("Current user is \(currentUser)", self) let userData = AuthenticationData(userId: currentUser.uid, email: currentUser.email ?? "", username: currentUser.displayName) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift index 2613611b..1355421e 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift @@ -60,13 +60,14 @@ final class TotalDamageDealtStatistic: Statistic { return nil } - let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + let largerPermanent = Double.maximum(self.permanentValue, that.permanentValue) + let largerCurrent = Double.maximum(self.currentValue, that.currentValue) + let largerMaxCurrent = Double.maximum(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: .zero, + currentValue: largerCurrent, maxCurrentValue: largerMaxCurrent) as? T else { - Logger.log("Statistic merging failed", self) + Logger.log("Statistic merging failed for \(self.toString())", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 63f43189..17cdecce 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -60,13 +60,14 @@ final class TotalDeathsStatistic: Statistic { return nil } - let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + let largerPermanent = Double.maximum(self.permanentValue, that.permanentValue) + let largerCurrent = Double.maximum(self.currentValue, that.currentValue) + let largerMaxCurrent = Double.maximum(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: .zero, + currentValue: largerCurrent, maxCurrentValue: largerMaxCurrent) as? T else { - Logger.log("Statistic merging failed", self) + Logger.log("Statistic merging failed for \(self.toString())", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index b4c151bb..e4142471 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -29,7 +29,7 @@ final class TotalGamesStatistic: Statistic { func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: GameStartEvent.self) let eventUpdateClosure: (Statistic, GameStartEvent?) -> Void = { statistic, event in - statistic.updateCurrentValue(by: 1.0) + statistic.updatePermanentValue(by: 1.0) Logger.log("Updating statistic with event detail: \(String(describing: event))", self) } @@ -57,13 +57,14 @@ final class TotalGamesStatistic: Statistic { return nil } - let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + let largerPermanent = Double.maximum(self.permanentValue, that.permanentValue) + let largerCurrent = Double.maximum(self.currentValue, that.currentValue) + let largerMaxCurrent = Double.maximum(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: .zero, + currentValue: largerCurrent, maxCurrentValue: largerMaxCurrent) as? T else { - Logger.log("Statistic merging failed", self) + Logger.log("Statistic merging failed for \(self.toString())", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index 6b1a1007..08bc30d9 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -59,13 +59,14 @@ final class TotalKillsStatistic: Statistic { return nil } - let largerPermanent = max(self.permanentValue, that.permanentValue) - let largerMaxCurrent = max(self.maximumCurrentValue, that.maximumCurrentValue) + let largerPermanent = Double.maximum(self.permanentValue, that.permanentValue) + let largerCurrent = Double.maximum(self.currentValue, that.currentValue) + let largerMaxCurrent = Double.maximum(self.maximumCurrentValue, that.maximumCurrentValue) guard let stat = Self(permanentValue: largerPermanent, - currentValue: .zero, + currentValue: largerCurrent, maxCurrentValue: largerMaxCurrent) as? T else { - Logger.log("Statistic merging failed", self) + Logger.log("Statistic merging failed for \(self.toString())", self) return nil } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 3d246c92..4365f91a 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -178,7 +178,7 @@ extension Statistic { } } -/// This extension allows Statistic to be merged +/*/// This extension allows Statistic to be merged extension Statistic { static func merge(this: T, that: T) -> T { @@ -190,4 +190,4 @@ extension Statistic { currentValue: largerCurrent, maxCurrentValue: largerMaxCurrent) } -} +}*/ diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 7c3c681c..1b3a4b4f 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -67,6 +67,7 @@ class StatisticsEngine: InferenceDataDelegate { func update(with event: T) { self.updateStatisticsOnReceive(event) self.notifyInferenceEngines() + } /// Transfers over all transient metrics within statistics to permanent value. @@ -85,7 +86,7 @@ class StatisticsEngine: InferenceDataDelegate { // stats.forEach { $0.update(for: eventType) } stats.forEach { $0.update(for: event) } - //saveStatistics() + saveStatistics() } /// TODO: Consider if passing the stats database directly is better or @@ -95,20 +96,6 @@ class StatisticsEngine: InferenceDataDelegate { inferenceEngines.values.forEach { $0.updateOnReceive() } } - func getCurrentRank() -> Rank? { - if let rankEngine = inferenceEngines[RankingEngine.asType] as? RankingEngine { - return rankEngine.currentRank - } - return nil - } - - func getCurrentExp() -> Double? { - if let rankEngine = inferenceEngines[RankingEngine.asType] as? RankingEngine { - return rankEngine.currentExp - } - return nil - } - private func saveStatistics() { Logger.log("STATISTICS_ENGINE SAVE: Statistics save triggered", self) Logger.log("STATISTICS_ENGINE SAVE: \(String(describing: statsEngineDelegate?.statisticsDatabase.toString()))", self) @@ -116,6 +103,13 @@ class StatisticsEngine: InferenceDataDelegate { // _ = StorageManager.saveUniversally(statistics) } + private func saveStatisticsLocally() { + Logger.log("STATISTICS_ENGINE LOCAL SAVE: Local save triggered", self) + Logger.log("STATISTICS_ENGINE LOCAL SAVE: \(String(describing: statsEngineDelegate?.statisticsDatabase.toString()))", self) + statsEngineDelegate?.localSave() + // _ = StorageManager.saveUniversally(statistics) + } + /*private func loadStatistics() { if let loadedStats = StorageManager.loadUniversally() { statisticsDatabase = loadedStats diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift index 3e6fd0b5..4fcf0cd0 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -128,6 +128,8 @@ class StorageManager { finalStorage = StatisticsDatabase.merge(this: this, that: that) case .KEEP_LATEST_ONLY: finalStorage = Self.loadLatest() + case .PRESERVE_LOCAL: + finalStorage = this } return finalStorage diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/StorageAPI/Metadata.swift similarity index 100% rename from TowerForge/TowerForge/Storage/Metadata.swift rename to TowerForge/TowerForge/StorageAPI/Metadata.swift diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index 8dbb0ab1..c144ddb0 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -35,7 +35,9 @@ extension StorageHandler { // Update the playerId and metadata locally self.localUpdatePlayerIdAndMetadata(with: userId) + self.remoteSave() + /* // Asynchronously check if remote data exists for currentPlayerId self.checkIfRemotePlayerDataExists { exists in if exists { @@ -47,7 +49,7 @@ extension StorageHandler { } else { self.onFirstLogin() } - } + }*/ } } @@ -57,9 +59,11 @@ extension StorageHandler { } func onFirstLogin() { + Logger.log("FIRST LOGIN ENTERED", self) self.remoteSave() } + /* /// Returns true if re-login success, false otherwise func onReLogin(completion: @escaping (Bool) -> Void) { Logger.log("RE-LOGIN ENTERED", self) @@ -81,7 +85,7 @@ extension StorageHandler { } // 3. Resolve conflict between remote statistics and current statistics - Self.resolveConflict(this: remoteStorage, that: self.statisticsDatabase) { resolvedStats in + Self.resolveConflict(this: self.statisticsDatabase, that: remoteStorage) { resolvedStats in guard let finalStorage = resolvedStats else { Logger.log("RELOGIN ERROR: CONFLICT RESOLUTION FAILURE") completion(false) @@ -96,7 +100,7 @@ extension StorageHandler { } } } - } + }*/ func localUpdatePlayerIdAndMetadata(with userId: String) { Constants.CURRENT_PLAYER_ID = userId diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift index 71f78974..120d38b2 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift @@ -71,6 +71,8 @@ extension StorageHandler { completion(StatisticsDatabase.merge(this: this, that: that)) case .KEEP_LATEST_ONLY: Self.loadLatest { completion($0) } + case .PRESERVE_LOCAL: + completion(this) } } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 4b3b1d94..8c2f90dc 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -10,6 +10,7 @@ import Foundation protocol StatisticsEngineDelegate: AnyObject { var statisticsDatabase: StatisticsDatabase { get set } func save() + func localSave() } class StorageHandler: AuthenticationDelegate, StatisticsEngineDelegate { From e305d927cd85716fcca658b733843cdaa9d1072e Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 15:54:50 +0800 Subject: [PATCH 22/29] Remove old StorageAPI files --- .../TowerForge.xcodeproj/project.pbxproj | 32 --- .../Storage/LocalMetadataManager.swift | 94 --------- .../Storage/LocalStorageManager.swift | 118 ----------- .../TowerForge/Storage/MetadataManager.swift | 64 ------ .../Storage/RemoteMetadataManager.swift | 150 -------------- .../Storage/RemoteStorageManager.swift | 132 ------------- .../TowerForge/Storage/StorageManager.swift | 187 ------------------ .../StorageAPI/LocalStorage+Metadata.swift | 2 +- .../StorageAPI/StorageHandler+Conflict.swift | 2 +- 9 files changed, 2 insertions(+), 779 deletions(-) delete mode 100644 TowerForge/TowerForge/Storage/LocalMetadataManager.swift delete mode 100644 TowerForge/TowerForge/Storage/LocalStorageManager.swift delete mode 100644 TowerForge/TowerForge/Storage/MetadataManager.swift delete mode 100644 TowerForge/TowerForge/Storage/RemoteMetadataManager.swift delete mode 100644 TowerForge/TowerForge/Storage/RemoteStorageManager.swift delete mode 100644 TowerForge/TowerForge/Storage/StorageManager.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 3a711ccb..893135b3 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -228,18 +228,12 @@ BA82C75F2BCB1528000515A0 /* AchievementsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C75E2BCB1528000515A0 /* AchievementsDatabase.swift */; }; BA82C7612BCBBA8A000515A0 /* FiftyKillsAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7602BCBBA8A000515A0 /* FiftyKillsAchievement.swift */; }; BA82C7632BCBBB2A000515A0 /* Double+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7622BCBBB2A000515A0 /* Double+Extensions.swift */; }; - BA82C7652BCBC868000515A0 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */; }; BA82C7672BCBCB00000515A0 /* StatisticsDatabase+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7662BCBCB00000515A0 /* StatisticsDatabase+Codable.swift */; }; - BA82C7692BCBD21F000515A0 /* RemoteStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */; }; - BA82C76B2BCBD682000515A0 /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C76A2BCBD682000515A0 /* StorageManager.swift */; }; BA82C76D2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C76C2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift */; }; BA82C76F2BCBDE91000515A0 /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C76E2BCBDE91000515A0 /* Metadata.swift */; }; - BA82C7732BCBF657000515A0 /* LocalMetadataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7722BCBF657000515A0 /* LocalMetadataManager.swift */; }; BA82C7752BCC689F000515A0 /* AchievementsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7742BCC689F000515A0 /* AchievementsFactory.swift */; }; BA82C7772BCC6913000515A0 /* HundredKillsAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7762BCC6913000515A0 /* HundredKillsAchievement.swift */; }; BA82C7792BCC6943000515A0 /* CenturionAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7782BCC6943000515A0 /* CenturionAchievement.swift */; }; - BA82C77B2BCD05DC000515A0 /* MetadataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */; }; - BA82C77D2BCD07F4000515A0 /* RemoteMetadataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C77C2BCD07F4000515A0 /* RemoteMetadataManager.swift */; }; BA82C7802BCD284D000515A0 /* InferenceEngineFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C77F2BCD284D000515A0 /* InferenceEngineFactory.swift */; }; BA82C7832BCD2B30000515A0 /* Mission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7822BCD2B30000515A0 /* Mission.swift */; }; BA82C7862BCD2B44000515A0 /* MissionTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7852BCD2B44000515A0 /* MissionTypeWrapper.swift */; }; @@ -512,18 +506,12 @@ BA82C75E2BCB1528000515A0 /* AchievementsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsDatabase.swift; sourceTree = ""; }; BA82C7602BCBBA8A000515A0 /* FiftyKillsAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiftyKillsAchievement.swift; sourceTree = ""; }; BA82C7622BCBBB2A000515A0 /* Double+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Double+Extensions.swift"; path = "TowerForge/Commons/Extensions/Double+Extensions.swift"; sourceTree = SOURCE_ROOT; }; - BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorageManager.swift; sourceTree = ""; }; BA82C7662BCBCB00000515A0 /* StatisticsDatabase+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatisticsDatabase+Codable.swift"; sourceTree = ""; }; - BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStorageManager.swift; sourceTree = ""; }; - BA82C76A2BCBD682000515A0 /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = ""; }; BA82C76C2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatisticsDatabase+Merge.swift"; sourceTree = ""; }; BA82C76E2BCBDE91000515A0 /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = ""; }; - BA82C7722BCBF657000515A0 /* LocalMetadataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMetadataManager.swift; sourceTree = ""; }; BA82C7742BCC689F000515A0 /* AchievementsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsFactory.swift; sourceTree = ""; }; BA82C7762BCC6913000515A0 /* HundredKillsAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HundredKillsAchievement.swift; sourceTree = ""; }; BA82C7782BCC6943000515A0 /* CenturionAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenturionAchievement.swift; sourceTree = ""; }; - BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataManager.swift; sourceTree = ""; }; - BA82C77C2BCD07F4000515A0 /* RemoteMetadataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteMetadataManager.swift; sourceTree = ""; }; BA82C77F2BCD284D000515A0 /* InferenceEngineFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngineFactory.swift; sourceTree = ""; }; BA82C7822BCD2B30000515A0 /* Mission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mission.swift; sourceTree = ""; }; BA82C7852BCD2B44000515A0 /* MissionTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionTypeWrapper.swift; sourceTree = ""; }; @@ -930,7 +918,6 @@ 52DF5FDB2BA32CEF00135367 /* LevelModule */, BAFFB9332BB0A24400D8301F /* GameModule */, BA436AE82BD42F1100BE3E4F /* StorageAPI */, - BAFFB9512BB342E200D8301F /* Storage */, BA2F5ABF2BC80BD200CBD8E9 /* Metrics */, ); path = TowerForge; @@ -1435,19 +1422,6 @@ path = GameModuleTests; sourceTree = ""; }; - BAFFB9512BB342E200D8301F /* Storage */ = { - isa = PBXGroup; - children = ( - BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */, - BA82C76A2BCBD682000515A0 /* StorageManager.swift */, - BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, - BA82C7722BCBF657000515A0 /* LocalMetadataManager.swift */, - BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */, - BA82C77C2BCD07F4000515A0 /* RemoteMetadataManager.swift */, - ); - path = Storage; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1664,7 +1638,6 @@ BA82C75A2BCB0DFD000515A0 /* AchievementTypeWrapper.swift in Sources */, 3C9955C22BA5838900D33FA5 /* EventOutput.swift in Sources */, 523E5C4C2BC53F70007444DA /* WaveSpawnEvent.swift in Sources */, - BA82C77D2BCD07F4000515A0 /* RemoteMetadataManager.swift in Sources */, 5240D0912BAF3453004F1486 /* Life.swift in Sources */, 52A794192BBC630F0083C976 /* RoomData.swift in Sources */, BA82C7632BCBBB2A000515A0 /* Double+Extensions.swift in Sources */, @@ -1703,7 +1676,6 @@ BA436ADF2BD3CE9600BE3E4F /* CustomMissionCell.swift in Sources */, 3C3CBDF92BB821500001B8A9 /* TFScene.swift in Sources */, BA82C79F2BCE7FBA000515A0 /* AbstractGoal.swift in Sources */, - BA82C7652BCBC868000515A0 /* LocalStorageManager.swift in Sources */, 9B8696552BAD759F0002377C /* Grid.swift in Sources */, 527A077E2BB3F75700CD9D08 /* Timer.swift in Sources */, 52578B872BA6209700B4D76C /* DamageComponent.swift in Sources */, @@ -1749,7 +1721,6 @@ 527A07762BB3E4CF00CD9D08 /* GameState.swift in Sources */, 3C9955AD2BA483B100D33FA5 /* TFSystem.swift in Sources */, BA82C79D2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift in Sources */, - BA82C77B2BCD05DC000515A0 /* MetadataManager.swift in Sources */, 3CAC4A672BB6975200A5D22E /* RenderStage.swift in Sources */, BA82C75D2BCB1451000515A0 /* InferenceEngine.swift in Sources */, 3C9955BE2BA57E4B00D33FA5 /* EventManager.swift in Sources */, @@ -1798,14 +1769,12 @@ 52A794062BBC32A10083C976 /* FirebaseRepositoryProtocol.swift in Sources */, 3CD7DE382BCEB6D200CB21F0 /* RemoteConcedeEvent.swift in Sources */, BA82C7442BC86FFE000515A0 /* GameStartEvent.swift in Sources */, - BA82C7692BCBD21F000515A0 /* RemoteStorageManager.swift in Sources */, 9B0406102BB879990026E903 /* InvulnerabilityPowerUp.swift in Sources */, BA436AE12BD3D66800BE3E4F /* MassKillMission.swift in Sources */, 5299D1412BC3AA3A003EF746 /* GameRankData.swift in Sources */, 52F930E72BC63F7F003D11B5 /* LeaderboardSelectionViewController.swift in Sources */, BA82C76F2BCBDE91000515A0 /* Metadata.swift in Sources */, BA82C7752BCC689F000515A0 /* AchievementsFactory.swift in Sources */, - BA82C76B2BCBD682000515A0 /* StorageManager.swift in Sources */, 9BD669682BAFDE5E00DC8C4C /* GridDelegate.swift in Sources */, 52DF5FEB2BA3400C00135367 /* TFAnimatableNode.swift in Sources */, 3C3CBDFF2BB8708A0001B8A9 /* CGPoint+Extensions.swift in Sources */, @@ -1843,7 +1812,6 @@ BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */, 3CD37AA32BBEC0F900222D8A /* FirebaseRemoteEventPublisher.swift in Sources */, 3CAC4A6B2BB6992F00A5D22E /* TFNode.swift in Sources */, - BA82C7732BCBF657000515A0 /* LocalMetadataManager.swift in Sources */, 3CBE73012BC8D69A00CC446A /* RemoteLifeEvent.swift in Sources */, 3CAC4A6D2BB6A13B00A5D22E /* PositionRenderStage.swift in Sources */, BA82C7942BCDAA83000515A0 /* InferenceDataDelegate.swift in Sources */, diff --git a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift deleted file mode 100644 index 96a951ac..00000000 --- a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// LocalMetadataManager.swift -// TowerForge -// -// Created by Rubesh on 14/4/24. -// - -import Foundation - -/// This extension allows the LocalStorageManager to facilitate metadata storage -/// and loading functionality. -/// -/// A custom Metadata class is implemented to provide more nuanced control over -/// metadata storage as opposed to using FileAttributesKey, although the option -/// to retrieve iOS-defined metadata is still available via a custom method. -class LocalMetadataManager { - - static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME - static let metadataFileName = Constants.METADATA_FILE_NAME - static let fileManager = FileManager.default - - static func initializeUserIdentifier() { - let metadata = LocalMetadataManager.checkAndCreateMetadata() - Constants.CURRENT_PLAYER_ID = metadata.uniqueIdentifier - Constants.CURRENT_DEVICE_ID = metadata.uniqueIdentifier - Logger.log("Current player set to \(Constants.CURRENT_PLAYER_ID)", self) - RemoteMetadataManager.initializeRemoteMetadata() - } - - static func checkAndCreateMetadata() -> Metadata { - if let existingMetadata = loadMetadataFromLocalStorage() { - Logger.log("Existing metadata loaded", self) - return existingMetadata - } else { - let newMetadata = Metadata() - Logger.log("New metadata being created", self) - saveMetadataToLocalStorage(newMetadata) - return newMetadata - } - } - - static func updateMetadataInLocalStorage() { - let metadata = loadMetadataFromLocalStorage() ?? Metadata() - metadata.lastUpdated = Date.now - saveMetadataToLocalStorage(metadata) - Logger.log("Metadata updated at: \(metadata.lastUpdated)", self) - // RemoteMetadataManager.updateMetadataInFirebase() - } - - static func synchronizeMetadataInLocalStorage() { - let metadata = loadMetadataFromLocalStorage() ?? Metadata() - metadata.lastUpdated = Date.now - metadata.uniqueIdentifier = Constants.CURRENT_PLAYER_ID - saveMetadataToLocalStorage(metadata) - Logger.log("Metadata synchronized at: \(metadata.lastUpdated)", self) - // RemoteMetadataManager.updateMetadataInFirebase() - } - - static func saveMetadataToLocalStorage(_ metadata: Metadata) { - do { - let folderURL = try LocalStorageManager.createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(metadataFileName) - let data = try JSONEncoder().encode(metadata) - try data.write(to: fileURL) - Logger.log("Metadata saved at: \(fileURL.path)", self) - } catch { - Logger.log("Failed to save metadata: \(error)", self) - } - } - - static func loadMetadataFromLocalStorage() -> Metadata? { - do { - let folderURL = try LocalStorageManager.createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(metadataFileName) - let data = try Data(contentsOf: fileURL) - let metadata = try JSONDecoder().decode(Metadata.self, from: data) - return metadata - } catch { - Logger.log("Failed to load metadata: \(error)", self) - return nil - } - } - - static func deleteMetadataFromLocalStorage() { - do { - let folderURL = try LocalStorageManager.createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(metadataFileName) - try fileManager.removeItem(at: fileURL) - Logger.log("Metadata successfully deleted.", self) - } catch { - Logger.log("Error deleting metadata: \(error)", self) - } - } -} diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift deleted file mode 100644 index eeca5936..00000000 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// LocalStorageManager.swift -// TowerForge -// -// Created by Rubesh on 14/4/24. -// - -import Foundation - -/// A utility class to provide standard storage operations and interaction with -/// the on-device files storage system via the FileManager API. -/// -/// Currently the Storage means is limited to storing Statistics only, possible -/// expansion to a generic type can be considered. -class LocalStorageManager { - static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME - static let fileName = Constants.LOCAL_STORAGE_FILE_NAME - static let metadataName = Constants.METADATA_FILE_NAME - - /// Creates an empty local file to store the database if one doesn't already exist. - /// Called by the AppDelegate when the application is run. - static func initializeLocalStatisticsDatabase() { - if Self.loadDatabaseFromLocalStorage() != nil { - Logger.log("Database exists locally", Self.self) - } else { - Self.saveDatabaseToLocalStorage(StatisticsFactory.getDefaultStatisticsDatabase()) - Logger.log("Created and saved a new empty database.", Self.self) - } - - RemoteStorageManager.initializeRemoteStatisticsDatabase() - } - - /// Helper function to construct a FileURL - static func fileURL(for directory: FileManager.SearchPathDirectory, withName name: String) throws -> URL { - let fileManager = FileManager.default - - return try fileManager.url(for: directory, - in: .userDomainMask, - appropriateFor: nil, - create: true).appendingPathComponent(name) - } - - /// Helper function to create a folder using the shared FileManager for a given folderName - static func createFolderIfNeeded(folderName: String) throws -> URL { - let fileManager = FileManager.default - let documentsURL: URL = try fileManager.url(for: .documentDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: false) - - let folderURL = documentsURL.appendingPathComponent(folderName) - if !fileManager.fileExists(atPath: folderURL.path) { - try fileManager.createDirectory(at: folderURL, - withIntermediateDirectories: true, - attributes: nil) - } - return folderURL - } -} - -extension LocalStorageManager { - /// Saves the input statistics database to file - static func saveDatabaseToLocalStorage(_ stats: StatisticsDatabase) { - let encoder = JSONEncoder() - - do { - let data = try encoder.encode(stats) - let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(Self.fileName) - try data.write(to: fileURL) - Logger.log("Saved Statistics Database at: \(fileURL.path)", self) - } catch { - Logger.log("Error saving statistics Database: \(error)", self) - } - - LocalMetadataManager.updateMetadataInLocalStorage() - } - - /// Loads a database (with the class constant folderName and fileName) from file - static func loadDatabaseFromLocalStorage() -> StatisticsDatabase? { - do { - let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(Self.fileName) - let data = try Data(contentsOf: fileURL) - let decoder = JSONDecoder() - return try decoder.decode(StatisticsDatabase.self, from: data) - } catch { - Logger.log("Error loading statistics: \(error)", self) - return nil - } - } - - /// Deletes the stored database from file - static func deleteDatabaseFromLocalStorage() { - do { - let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(Self.fileName) - try FileManager.default.removeItem(at: fileURL) - } catch { - Logger.log("Error deleting file: \(Self.fileName), \(error)", self) - } - Logger.log("Database successfully deleted.", self) - } - - /// Retrieves file attributes provided by iOS for a given filename in the document directory. - static func getDatabaseFileAttributes() -> [FileAttributeKey: Any]? { - do { - let folderURL = try createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(Self.fileName) - let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path) - return attributes - } catch { - Logger.log("Error retrieving file metadata: \(error)", self) - return nil - } - } - -} diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift deleted file mode 100644 index 4537d774..00000000 --- a/TowerForge/TowerForge/Storage/MetadataManager.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// MetadataManager.swift -// TowerForge -// -// Created by Rubesh on 15/4/24. -// - -import Foundation - -class MetadataManager { - - /// Utility function that returns the StorageLocation indicator for the location that - /// contains the most recent data. - /// - /// This is to be used in circumstances where simple merging cannot work, like in the - /// case of Statistics database. Metadata's comformance to comparable allows for the - /// comparision metric to be determined. - /// - /// Representation invariant is that the Metadata being compared contain the same - /// uuid identifier. - static func getLocationWithLatestMetadata() -> StorageLocation? { - var remoteMetadataExists = false - var localMetadataExists = false - - var remoteMetadata: Metadata? - let localMetadata = LocalMetadataManager.loadMetadataFromLocalStorage() - - RemoteMetadataManager.remoteMetadataExistsForCurrentPlayer { remoteMetadataExists = $0 } - localMetadataExists = localMetadata != nil - - /* --- Accounting for edge cases --- */ - - // A nil value will automatically imply inferiority to any given value - guard remoteMetadataExists || localMetadataExists else { - return nil - } - - if remoteMetadataExists && !localMetadataExists { - return .Remote - } - - if !remoteMetadataExists && localMetadataExists { - return .Local - } - /* ------------------------------- */ - - // The check above will ensure that the metadata file exists remotely, thus, - // there is no need to account for the case where the metadata might be missing. - RemoteMetadataManager.loadMetadataFromFirebase { metadata, error in - if error != nil { - Logger.log("Error occured at retriving metadata") - return - } - - remoteMetadata = metadata - } - - guard let remoteMetadata = remoteMetadata, let localMetadata = localMetadata else { - return nil - } - - return remoteMetadata > localMetadata ? .Remote : .Local - } -} diff --git a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift deleted file mode 100644 index 7f4dc835..00000000 --- a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// RemoteMetadataManager.swift -// TowerForge -// -// Created by Rubesh on 15/4/24. -// - -import Foundation -import FirebaseDatabaseInternal - -class RemoteMetadataManager { - static var currentPlayer: String = Constants.CURRENT_PLAYER_ID - static var currentDevice: String = Constants.CURRENT_DEVICE_ID - - /// Queries the firebase backend to determine if remote metadata exists for the current player - static func remoteMetadataExistsForCurrentPlayer(completion: @escaping (Bool) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Metadata) - - databaseReference.child(currentPlayer).getData(completion: { error, snapshot in - if let error = error { - Logger.log("Error checking data existence: \(error.localizedDescription)", self) - completion(false) // Assuming no data exists if an error occurs - return - } - - if snapshot?.exists() != nil && snapshot?.value != nil { - completion(true) - } else { - completion(false) - } - }) - } - - static func initializeRemoteMetadata() { - Self.loadMetadataFromFirebase { metadata, error in - if let error = error { - Logger.log("Error initializing metadata: \(error)", self) - return - } - - if let metadata = metadata { - Logger.log("Metadata database already initialized : \(metadata)", self) - return - } - - // No error but no metadata implies that metadata is empty, thus initialize new one - Logger.log("No error but empty metadata, new one will be created", self) - let remoteMetadata = Metadata(lastUpdated: Date.now, uniqueIdentifier: Constants.CURRENT_PLAYER_ID) - - Self.saveMetadataToFirebase(remoteMetadata) { error in - if let error = error { - Logger.log("Saving metadata to firebase error: \(error)", self) - } else { - Logger.log("Saving metadata to firebase success", self) - } - } - } - } - - static func updateMetadataInFirebase() { - var existingRemoteMetadata = Metadata(lastUpdated: Date.now, - uniqueIdentifier: Constants.CURRENT_PLAYER_ID) - - Self.loadMetadataFromFirebase { metadata, error in - if let error = error { - Logger.log("Error occured while loading metadata from firebase for update --- \(error)", self) - return - } - - if let metadata = metadata { - existingRemoteMetadata = metadata - } - } - - existingRemoteMetadata.lastUpdated = Date.now - Logger.log("Metadata updated at: \(String(describing: existingRemoteMetadata.lastUpdated))", self) - - Self.saveMetadataToFirebase(existingRemoteMetadata) { error in - if let error = error { - Logger.log("Error occured while saving metadata to firebase for update --- \(error)", self) - return - } - } - } - - static func loadMetadataFromFirebase(completion: @escaping (Metadata?, Error?) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Metadata) - - databaseReference.child(currentPlayer).getData(completion: { error, snapshot in - if let error = error { - Logger.log(error.localizedDescription, self) - completion(nil, error) - return - } - - guard let value = snapshot?.value as? [String: Any], - let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []) else { - completion(nil, nil) - return - } - - do { - let decoder = JSONDecoder() - let metadata = try decoder.decode(Metadata.self, from: jsonData) - completion(metadata, nil) - } catch { - Logger.log("Error decoding Metadata from Firebase: \(error)", self) - completion(nil, error) - } - }) - } - - static func saveMetadataToFirebase(_ stats: Metadata, completion: @escaping (Error?) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Metadata) - - do { - let encoder = JSONEncoder() - let data = try encoder.encode(stats) - let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] - - databaseReference.child(currentPlayer).setValue(dictionary) { error, _ in - if let error = error { - Logger.log("Metadata could not be saved: \(error).", Metadata.self) - completion(error) - } else { - Logger.log("Metadata saved to Firebase successfully!", Metadata.self) - completion(nil) - } - } - } catch { - Logger.log("Error encoding StatisticsDatabase: \(error)", Metadata.self) - completion(error) - } - } - - static func deleteMetadataFromFirebase(completion: @escaping (Error?) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Metadata) - - // Remove the data at the specific currentPlayer node - databaseReference.child(currentPlayer).removeValue { error, _ in - if let error = error { - Logger.log("Error deleting data: \(error).", self) - completion(error) - return - } - Logger.log("Metadata for player \(currentPlayer) successfully deleted from Firebase.", self) - completion(nil) - } - } -} diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift deleted file mode 100644 index 062d0846..00000000 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// RemoteStorageManager.swift -// TowerForge -// -// Created by Rubesh on 14/4/24. -// - -import Foundation -import FirebaseDatabaseInternal - -/// A utility class to provide standard storage operations and interaction with -/// the Firebase Database. -/// -/// Currently the Storage means is limited to storing Statistics only, possible -/// expansion to a generic types can be considered. -class RemoteStorageManager { - static var currentPlayer: String = Constants.CURRENT_PLAYER_ID - static var currentDevice: String = Constants.CURRENT_DEVICE_ID - - static func initializeRemoteStatisticsDatabase() { - Self.loadDatabaseFromFirebase { statisticsDatabase, error in - if let error = error { - Logger.log("Error initializing data: \(error)", self) - return - } - - if statisticsDatabase != nil { - Logger.log("Statistics database already initialized.", self) - return - } - - // No error but no database implies that database is empty, thus initialize new one - Logger.log("No error but empty database, new one will be created", self) - let remoteStorage = StatisticsFactory.getDefaultStatisticsDatabase() - - Self.saveDatabaseToFirebase(remoteStorage) { error in - if let error = error { - Logger.log("Saving to firebase error: \(error)", self) - } else { - Logger.log("Saving to firebase success", self) - } - } - } - } - - /// Queries the firebase backend to determine if remote storage exists for the current player - static func remoteStorageExistsForCurrentPlayer(completion: @escaping (Bool) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Statistics) - - databaseReference.child(currentPlayer).getData(completion: { error, snapshot in - if let error = error { - Logger.log("Error checking data existence: \(error.localizedDescription)", self) - completion(false) // Assuming no data exists if an error occurs - return - } - - if snapshot?.exists() != nil && snapshot?.value != nil { - completion(true) - } else { - completion(false) - } - }) - } - - static func loadDatabaseFromFirebase(completion: @escaping (StatisticsDatabase?, Error?) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Statistics) - - databaseReference.child(currentPlayer).getData(completion: { error, snapshot in - if let error = error { - Logger.log(error.localizedDescription, self) - completion(nil, error) - return - } - - guard let value = snapshot?.value as? [String: Any], - let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []) else { - completion(nil, nil) - return - } - - do { - let decoder = JSONDecoder() - let statsDatabase = try decoder.decode(StatisticsDatabase.self, from: jsonData) - completion(statsDatabase, nil) - } catch { - Logger.log("Error decoding StatisticsDatabase from Firebase: \(error)", self) - completion(nil, error) - } - }) - } - - static func saveDatabaseToFirebase(_ stats: StatisticsDatabase, completion: @escaping (Error?) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Statistics) - - do { - let encoder = JSONEncoder() - let data = try encoder.encode(stats) - let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] - - databaseReference.child(currentPlayer).setValue(dictionary) { error, _ in - if let error = error { - Logger.log("StatisticsDatabase could not be saved: \(error).", StatisticsDatabase.self) - completion(error) - } else { - Logger.log("StatisticsDatabase saved to Firebase successfully!", StatisticsDatabase.self) - completion(nil) - } - } - } catch { - Logger.log("Error encoding StatisticsDatabase: \(error)", StatisticsDatabase.self) - completion(error) - } - - RemoteMetadataManager.updateMetadataInFirebase() - } - - /// Deletes the player's statistics database from Firebase - static func deleteDatabaseFromFirebase(completion: @escaping (Error?) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Statistics) - - // Remove the data at the specific currentPlayer node - databaseReference.child(currentPlayer).removeValue { error, _ in - if let error = error { - Logger.log("Error deleting data: \(error).", self) - completion(error) - return - } - Logger.log("StatisticsDatabase for player \(currentPlayer) successfully deleted from Firebase.", self) - completion(nil) - } - } -} diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift deleted file mode 100644 index 4fcf0cd0..00000000 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ /dev/null @@ -1,187 +0,0 @@ -// -// StorageManager.swift -// TowerForge -// -// Created by Rubesh on 14/4/24. -// - -import Foundation - -/// The class responsible for providing application wide Storage access and -/// synchronizing between Local Storage and Remote Storage. The application interacts only -/// with StorageManager which handles all storage operations. -class StorageManager { - static var CONFLICT_RESOLUTION = Constants.CONFLICT_RESOLTION - - static func initializeAllStorage() { - LocalMetadataManager.initializeUserIdentifier() - LocalStorageManager.initializeLocalStatisticsDatabase() - } - - static var defaultErrorClosure: (Error?) -> Void = { error in - if let error = error { - Logger.log("Generic error message invoked: \(error)") - } else { - Logger.log("Generic success message invoked.") - } - } - - static func onLogin(with userId: String) { - Logger.log("LOGIN: onLogin method invoked from Storage Manager", self) - let localStorage = LocalStorageManager.loadDatabaseFromLocalStorage() - ?? StatisticsFactory.getDefaultStatisticsDatabase() - - _ = Self.saveUniversally(localStorage) - - Constants.CURRENT_PLAYER_ID = userId - LocalMetadataManager.synchronizeMetadataInLocalStorage() - var remoteStorage = StatisticsDatabase() - - RemoteStorageManager.loadDatabaseFromFirebase { statisticsDatabase, error in - if let error = error { - Logger.log("Error loading data: \(error)", self) - } else if let statisticsDatabase = statisticsDatabase { - Logger.log("Successfully loaded statistics database.", self) - remoteStorage = statisticsDatabase - } else { - // No error and no database implies that database is empty, thus initialize new one - Logger.log("No error and empty database, new one will be created", self) - remoteStorage = StatisticsFactory.getDefaultStatisticsDatabase() - } - } - - if let finalStorage = Self.resolveConflict(this: localStorage, that: remoteStorage) { - Logger.log("LOGIN: Resolve conflict closure entered", self) - _ = Self.saveUniversally(finalStorage) - } - - } - - static func onLogout() { - Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID - } - - static func resetAllStorage() { - Self.deleteAllRemoteStorage() - Self.deleteAllLocalStorage() - } - - static func deleteAllLocalStorage() { - LocalStorageManager.deleteDatabaseFromLocalStorage() - LocalMetadataManager.deleteMetadataFromLocalStorage() - } - - static func deleteAllRemoteStorage() { - RemoteStorageManager.deleteDatabaseFromFirebase { error in - if let error = error { - Logger.log("Deletion of all remote storage failed by StorageManager: \(error)", self) - } else { - Logger.log("Delete of all remote storage success", self) - } - } - } - - static func saveUniversally(_ statistics: StatisticsDatabase) -> StatisticsDatabase? { - LocalStorageManager.saveDatabaseToLocalStorage(statistics) - return Self.pushToRemote() - } - - static func loadUniversally() -> StatisticsDatabase? { - if let stats = LocalStorageManager.loadDatabaseFromLocalStorage() { - return stats - } else { - let localStorage = StatisticsFactory.getDefaultStatisticsDatabase() - return saveUniversally(localStorage) - } - } - - /// Returns the StatisticsDatabase from the location that corresponds to the most recent - /// save. - static func loadLatest() -> StatisticsDatabase? { - var stats: StatisticsDatabase? - - guard let location = MetadataManager.getLocationWithLatestMetadata() else { - return nil - } - - switch location { - case .Local: - stats = LocalStorageManager.loadDatabaseFromLocalStorage() - case .Remote: - RemoteStorageManager.loadDatabaseFromFirebase { statsData, error in - if error != nil { - Logger.log("Error occured loading from database") - } - - stats = statsData - } - } - - return stats - } - - private static func resolveConflict(this: StatisticsDatabase, that: StatisticsDatabase) -> StatisticsDatabase? { - var finalStorage: StatisticsDatabase? - - switch CONFLICT_RESOLUTION { - case .MERGE: - finalStorage = StatisticsDatabase.merge(this: this, that: that) - case .KEEP_LATEST_ONLY: - finalStorage = Self.loadLatest() - case .PRESERVE_LOCAL: - finalStorage = this - } - - return finalStorage - } - - /// Pushes local data to remote - /// - Firstly loads data from local storage (or creates empty storage if it doesn't exist) - /// - Then loads data from remote storage (or creates empty storage if it doesn't exist) - /// - Compares both data, merges them, and pushes back to remote. - /// - /// This ensures that no information is overwritten in the process. - private static func pushToRemote() -> StatisticsDatabase? { - // Explicitly load storage to ensure that uploaded data is - Logger.log("PUSH: Push to remote entered", self) - var localStorage: StatisticsDatabase - var remoteStorage = StatisticsDatabase() - - if let localStats = LocalStorageManager.loadDatabaseFromLocalStorage() { - localStorage = localStats - } else { - LocalStorageManager.initializeLocalStatisticsDatabase() - localStorage = StatisticsFactory.getDefaultStatisticsDatabase() - } - - RemoteStorageManager.loadDatabaseFromFirebase { statisticsDatabase, error in - if let error = error { - Logger.log("Error loading data: \(error)", self) - } else if let statisticsDatabase = statisticsDatabase { - Logger.log("Successfully loaded statistics database.", self) - remoteStorage = statisticsDatabase - } else { - // No error and no database implies that database is empty, thus initialize new one - Logger.log("No error and empty database, new one will be created", self) - remoteStorage = StatisticsFactory.getDefaultStatisticsDatabase() - } - } - - guard let finalStorage = StatisticsDatabase.merge(this: localStorage, that: remoteStorage) else { - Logger.log("PUSH: Merge failed, returning nil") - return nil - } - - RemoteStorageManager.saveDatabaseToFirebase(finalStorage) { error in - if let error = error { - Logger.log("Saving to firebase error: \(error)", self) - } else { - Logger.log("Saving to firebase success", self) - } - } - - LocalStorageManager.saveDatabaseToLocalStorage(finalStorage) - return finalStorage - } - -} diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift index 37bd8c16..254c7aac 100644 --- a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift @@ -53,7 +53,7 @@ extension LocalStorage { /// Deletes the stored metadata from file static func deleteMetadataFromLocalStorage() { do { - let folderURL = try LocalStorageManager.createFolderIfNeeded(folderName: folderName) + let folderURL = try Self.createFolderIfNeeded(folderName: folderName) let fileURL = folderURL.appendingPathComponent(metadataName) try FileManager.default.removeItem(at: fileURL) Logger.log("Metadata successfully deleted.", self) diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift index 120d38b2..2c1a52fb 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift @@ -46,7 +46,7 @@ extension StorageHandler { } case .Remote: - RemoteStorageManager.loadDatabaseFromFirebase { statsData, error in + RemoteStorage.loadDatabaseFromFirebase(player: Self.currentPlayerId) { statsData, error in if let error = error { Logger.log("Error occurred loading from database: \(error)", self) completion(nil) From 1afc71877279530efac3286e460befcf66eeae21 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 16:28:27 +0800 Subject: [PATCH 23/29] Add boolean check for authentication --- .../AuthenticationManager.swift | 2 - .../StorageAPI/LocalStorage+Metadata.swift | 4 +- .../TowerForge/StorageAPI/Metadata.swift | 13 ++- .../StorageAPI/StorageHandler+Auth.swift | 83 ++++--------------- .../StorageAPI/StorageHandler.swift | 1 + 5 files changed, 29 insertions(+), 74 deletions(-) diff --git a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift index 69bfeebd..2dde2f2d 100644 --- a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift +++ b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift @@ -98,8 +98,6 @@ class AuthenticationManager: AuthenticationProtocol { do { try Auth.auth().signOut() self.delegate?.onLogout() - // StorageManager.onLogout() - // Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID completion(nil) } catch let error as NSError { completion(error) diff --git a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift index 254c7aac..9d021bfd 100644 --- a/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift +++ b/TowerForge/TowerForge/StorageAPI/LocalStorage+Metadata.swift @@ -13,11 +13,11 @@ extension LocalStorage { if let metadata = LocalStorage.loadMetadataFromLocalStorage() { Logger.log("--- Metadata exists locally", Self.self) Constants.CURRENT_PLAYER_ID = metadata.uniqueIdentifier - Constants.CURRENT_DEVICE_ID = metadata.uniqueIdentifier + Constants.CURRENT_DEVICE_ID = metadata.deviceIdentifier } else { let defaultMetadata = Metadata() Constants.CURRENT_PLAYER_ID = defaultMetadata.uniqueIdentifier - Constants.CURRENT_DEVICE_ID = defaultMetadata.uniqueIdentifier + Constants.CURRENT_DEVICE_ID = defaultMetadata.deviceIdentifier LocalStorage.saveMetadataToLocalStorage(defaultMetadata) Logger.log("--- Created and saved a new empty metadata file.", Self.self) diff --git a/TowerForge/TowerForge/StorageAPI/Metadata.swift b/TowerForge/TowerForge/StorageAPI/Metadata.swift index fcb62bfd..40eb397f 100644 --- a/TowerForge/TowerForge/StorageAPI/Metadata.swift +++ b/TowerForge/TowerForge/StorageAPI/Metadata.swift @@ -15,18 +15,23 @@ class Metadata: StorageDatabase, Comparable, Equatable { static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } + var deviceIdentifier: String var uniqueIdentifier: String var lastUpdated: Date init(lastUpdated: Date = .now, - uniqueIdentifier: String = Metadata.currentPlayerId) { + uniqueIdentifier: String = Metadata.currentPlayerId, + deviceIdentifer: String = Metadata.currentDeviceId) { self.lastUpdated = lastUpdated self.uniqueIdentifier = uniqueIdentifier + self.deviceIdentifier = deviceIdentifer } init() { self.lastUpdated = .now - self.uniqueIdentifier = UUID().uuidString + let id = UUID().uuidString + self.uniqueIdentifier = id + self.deviceIdentifier = id } func updateTimeToNow() { @@ -36,12 +41,12 @@ class Metadata: StorageDatabase, Comparable, Equatable { func updateIdentifierToCurrentID() { uniqueIdentifier = Self.currentPlayerId - Logger.log("Metadata id updated to \(self.uniqueIdentifier)", self) + Logger.log("Metadata id updated to currentPlayer \(self.uniqueIdentifier)", self) } func resetIdentifier() { uniqueIdentifier = Self.currentDeviceId - Logger.log("Metadata id updated to \(self.uniqueIdentifier)", self) + Logger.log("Metadata id updated to currentDevice \(self.uniqueIdentifier)", self) } static func == (lhs: Metadata, rhs: Metadata) -> Bool { diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index c144ddb0..1fe03eb4 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -35,21 +35,24 @@ extension StorageHandler { // Update the playerId and metadata locally self.localUpdatePlayerIdAndMetadata(with: userId) - self.remoteSave() - /* - // Asynchronously check if remote data exists for currentPlayerId - self.checkIfRemotePlayerDataExists { exists in - if exists { - self.onReLogin { reloginSuccess in - if !reloginSuccess { - Logger.log("RE-LOGIN FAILED: Remote player exists but Re-login failure", self) + if !Self.isLoggedIn { + Self.isLoggedIn = true + // Asynchronously check if remote data exists for currentPlayerId + self.checkIfRemotePlayerDataExists { exists in + if exists { + self.onReLogin { reloginSuccess in + if !reloginSuccess { + Logger.log("RE-LOGIN FAILED: Remote player exists but Re-login failure", self) + } } + } else { + self.onFirstLogin() } - } else { - self.onFirstLogin() } - }*/ + } + + self.remoteSave() } } @@ -63,7 +66,6 @@ extension StorageHandler { self.remoteSave() } - /* /// Returns true if re-login success, false otherwise func onReLogin(completion: @escaping (Bool) -> Void) { Logger.log("RE-LOGIN ENTERED", self) @@ -95,12 +97,12 @@ extension StorageHandler { // 4. Update current instance to resolve storage self.statisticsDatabase = finalStorage - // 4. Save newly resolved storage universally + // 5. Save newly resolved storage universally self.save() } } } - }*/ + } func localUpdatePlayerIdAndMetadata(with userId: String) { Constants.CURRENT_PLAYER_ID = userId @@ -109,6 +111,7 @@ extension StorageHandler { } func onLogout() { + Self.isLoggedIn = false Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID Logger.log("LOGOUT: CALLED FROM STORAGE_HANDLER", Self.self) self.save() // Save any potential unsaved changes @@ -116,56 +119,4 @@ extension StorageHandler { Logger.log("LOGOUT: metadata reset to \(metadata.uniqueIdentifier)", Self.self) self.localSave() // Save updated metadata } - - /// Returns true if re-login success, false otherwise - /*func onReLogin() -> Bool { - // Fetch metadata - guard let remoteMetadata = RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) else { - Logger.log("RELOGIN ERROR: REMOTE METADATA NOT FOUND") - return false - } - - // Fetch storage - guard let remoteStorage = RemoteStorage.loadDatabaseFromFirebase(player: Self.currentPlayerId) else { - Logger.log("RELOGIN ERROR: REMOTE STORAGE NOT FOUND") - return false - } - - var finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: statisticsDatabase) - - // Merge storage - MERGE - // Merge metadata - KEEP LATEST - // Set storage to merged storage - // Save new storage to file - // Universal save will automatically update metadata - // Universal save to remote - - return true - - }*/ - - /*func onReLoginAsync() async -> Bool { - do { - // Fetch metadata and storage asynchronously - let remoteMetadata = try await RemoteStorage.loadMetadataFromFirebase(player: Self.currentPlayerId) - let remoteStorage = try await RemoteStorage.loadDatabaseFromFirebase(player: Self.currentPlayerId) - - // Merge storage - assume a static merge function in StatisticsDatabase - let finalStorage = StatisticsDatabase.merge(this: remoteStorage, that: statisticsDatabase) - - // Save merged storage locally and remotely - LocalStorage.saveDatabaseToLocal(finalStorage) // Assuming there's a method to save locally - try await RemoteStorage.saveDatabaseToFirebase(player: Self.currentPlayerId, with: finalStorage) - - // Update metadata locally and remotely - // Assuming newer metadata is better or merge strategy is implemented - metadata = remoteMetadata - try await RemoteStorage.saveMetadataToFirebase(player: Self.currentPlayerId, with: metadata) - - return true - } catch { - Logger.log("RELOGIN ERROR: \(error)") - return false - } - }*/ } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 8c2f90dc..0a93d875 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -19,6 +19,7 @@ class StorageHandler: AuthenticationDelegate, StatisticsEngineDelegate { static let fileName = Constants.LOCAL_STORAGE_FILE_NAME static let metadataName = Constants.METADATA_FILE_NAME + static var isLoggedIn = false static var CONFLICT_RESOLUTION: StorageConflictResolution { Constants.CONFLICT_RESOLTION } static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } From ba09b3857611691eb6f391c67b9b689d4f4fb98a Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 17:02:36 +0800 Subject: [PATCH 24/29] Add log statements --- .../Statistics/StatisticsDatabase+Merge.swift | 17 +++++++++++------ .../StorageAPI/StorageHandler+Auth.swift | 2 ++ .../StorageAPI/StorageHandler+Conflict.swift | 5 +++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index fba9276e..7a95be18 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -37,22 +37,21 @@ extension StatisticsDatabase: Equatable { return nil } + Logger.log("MERGE --- THIS DB is \(String(describing: this?.toString()))", self) + Logger.log("MERGE --- THAT DB is \(String(describing: that?.toString()))", self) + var lhs = StatisticsDatabase() var rhs = StatisticsDatabase() if let this = this { lhs = this - } else { - lhs.setToDefault() } if let that = that { rhs = that - } else { - rhs.setToDefault() } - let mergedStats = StatisticsFactory.getDefaultStatisticsDatabase() + let mergedStats = StatisticsDatabase() // Merge lhs statistics for (key, lhsStat) in lhs.statistics { @@ -60,9 +59,15 @@ extension StatisticsDatabase: Equatable { } for (key, rhsStat) in rhs.statistics { + Logger.log("MERGE-LOOP: Statistic RHS \(key) is " + + "\(String(describing: rhsStat.toString()))", self) if let lhsStat = mergedStats.statistics[key] { + Logger.log("MERGE-LOOP: Statistic LHS \(key) is " + + "\(String(describing: lhsStat.toString()))", self) + mergedStats.statistics[key] = lhsStat.merge(with: rhsStat) - Logger.log("MERGE-LOOP: Statistic \(key) updated to " + + + Logger.log("MERGE-LOOP: MERGED Statistic \(key) updated to " + "\(String(describing: mergedStats.statistics[key]?.toString()))", self) } else { mergedStats.statistics[key] = rhsStat diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index 1fe03eb4..4ef67803 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -88,6 +88,8 @@ extension StorageHandler { // 3. Resolve conflict between remote statistics and current statistics Self.resolveConflict(this: self.statisticsDatabase, that: remoteStorage) { resolvedStats in + Logger.log("ONRELOGIN --- THIS DB is \(String(describing: self.statisticsDatabase.toString()))", self) + Logger.log("ONRELOGIN --- THAT DB is \(String(describing: remoteStorage.toString()))", self) guard let finalStorage = resolvedStats else { Logger.log("RELOGIN ERROR: CONFLICT RESOLUTION FAILURE") completion(false) diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift index 2c1a52fb..9722c257 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Conflict.swift @@ -68,11 +68,16 @@ extension StorageHandler { switch CONFLICT_RESOLUTION { case .MERGE: + Logger.log("RESOLVE --- THIS DB is \(String(describing: this.toString()))", self) + Logger.log("RESOLVE --- THAT DB is \(String(describing: that.toString()))", self) completion(StatisticsDatabase.merge(this: this, that: that)) + return case .KEEP_LATEST_ONLY: Self.loadLatest { completion($0) } + return case .PRESERVE_LOCAL: completion(this) + return } } From ba03af14a5fccd3638706b0e76a91d76aaaeddad Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 18:15:13 +0800 Subject: [PATCH 25/29] Finalize storage merge --- .../TowerForge/Metrics/Statistics/StatisticsEngine.swift | 2 -- TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift | 6 +++--- TowerForge/TowerForge/StorageAPI/StorageHandler.swift | 3 +++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 1b3a4b4f..a8878fc6 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -67,7 +67,6 @@ class StatisticsEngine: InferenceDataDelegate { func update(with event: T) { self.updateStatisticsOnReceive(event) self.notifyInferenceEngines() - } /// Transfers over all transient metrics within statistics to permanent value. @@ -84,7 +83,6 @@ class StatisticsEngine: InferenceDataDelegate { return } - // stats.forEach { $0.update(for: eventType) } stats.forEach { $0.update(for: event) } saveStatistics() } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index 4ef67803..a625dfab 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -41,18 +41,18 @@ extension StorageHandler { // Asynchronously check if remote data exists for currentPlayerId self.checkIfRemotePlayerDataExists { exists in if exists { + Logger.log("ONLOGIN ---- REMOTE PLAYER DATA EXISTS") self.onReLogin { reloginSuccess in if !reloginSuccess { Logger.log("RE-LOGIN FAILED: Remote player exists but Re-login failure", self) } } } else { + Logger.log("ONLOGIN ---- REMOTE PLAYER DATA DOESN'T EXIST") self.onFirstLogin() } } } - - self.remoteSave() } } @@ -63,7 +63,7 @@ extension StorageHandler { func onFirstLogin() { Logger.log("FIRST LOGIN ENTERED", self) - self.remoteSave() + self.save() } /// Returns true if re-login success, false otherwise diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 0a93d875..4db7ff9d 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -76,6 +76,9 @@ class StorageHandler: AuthenticationDelegate, StatisticsEngineDelegate { } func remoteSave() { + guard authenticationProvider.isUserLoggedIn() else { + return + } Logger.log("R-SAVE: Saving Stats and Metadata to RemoteData for \(Self.currentPlayerId)", Self.self) RemoteStorage.saveDatabaseToFirebase(player: Self.currentPlayerId, with: self.statisticsDatabase) { if $0 { From 833e3455b60ff217d627faccf4233baddef64504 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 18:30:03 +0800 Subject: [PATCH 26/29] Remove unused code --- .../TowerForge.xcodeproj/project.pbxproj | 4 -- .../Utilities/AbstractTypeWrapper.swift | 66 ------------------- .../StorageAPI/StorageHandler+Auth.swift | 7 -- 3 files changed, 77 deletions(-) delete mode 100644 TowerForge/TowerForge/Commons/Utilities/AbstractTypeWrapper.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 9dae2757..441aceae 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -249,7 +249,6 @@ BA82C79D2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C79C2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift */; }; BA82C79F2BCE7FBA000515A0 /* AbstractGoal.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C79E2BCE7FBA000515A0 /* AbstractGoal.swift */; }; BA82C7A22BCE8138000515A0 /* AbstractGoalTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7A12BCE8138000515A0 /* AbstractGoalTypeWrapper.swift */; }; - BAEC99FA2BD13F2600E0C437 /* AbstractTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAEC99F92BD13F2600E0C437 /* AbstractTypeWrapper.swift */; }; BAEC99FC2BD15AAB00E0C437 /* StorageDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */; }; BAEC99FE2BD15E0200E0C437 /* PlayerStatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAEC99FD2BD15E0200E0C437 /* PlayerStatsViewController.swift */; }; BAEC9A002BD1A4B700E0C437 /* CustomAchievementCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAEC99FF2BD1A4B700E0C437 /* CustomAchievementCell.swift */; }; @@ -528,7 +527,6 @@ BA82C79E2BCE7FBA000515A0 /* AbstractGoal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractGoal.swift; sourceTree = ""; }; BA82C7A12BCE8138000515A0 /* AbstractGoalTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractGoalTypeWrapper.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; - BAEC99F92BD13F2600E0C437 /* AbstractTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractTypeWrapper.swift; sourceTree = ""; }; BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageDatabase.swift; sourceTree = ""; }; BAEC99FD2BD15E0200E0C437 /* PlayerStatsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStatsViewController.swift; sourceTree = ""; }; BAEC99FF2BD1A4B700E0C437 /* CustomAchievementCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAchievementCell.swift; sourceTree = ""; }; @@ -1404,7 +1402,6 @@ children = ( BAFFB9482BB0ABC400D8301F /* Logger.swift */, BAFFB9692BB9A64000D8301F /* ObjectSet.swift */, - BAEC99F92BD13F2600E0C437 /* AbstractTypeWrapper.swift */, ); path = Utilities; sourceTree = ""; @@ -1609,7 +1606,6 @@ 523C29302BBD0916004C6EAC /* GameWaitingRoomViewController.swift in Sources */, BA2F5AC12BC80BE500CBD8E9 /* Statistic.swift in Sources */, 3C9955A12BA47DA500D33FA5 /* BaseTower.swift in Sources */, - BAEC99FA2BD13F2600E0C437 /* AbstractTypeWrapper.swift in Sources */, 5299D1432BC3AB38003EF746 /* GameRankProvider.swift in Sources */, BA436AE72BD4180400BE3E4F /* MassEffectMission.swift in Sources */, 5250B42F2BAE0DB000F16CF6 /* LabelComponent.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Utilities/AbstractTypeWrapper.swift b/TowerForge/TowerForge/Commons/Utilities/AbstractTypeWrapper.swift deleted file mode 100644 index 4aaa008a..00000000 --- a/TowerForge/TowerForge/Commons/Utilities/AbstractTypeWrapper.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// AbstractTypeWrapper.swift -// TowerForge -// -// Created by Rubesh on 18/4/24. -// - -import Foundation - -/// TODO: Replace type wrappers with this. -protocol AbstractTypeWrapper: Equatable, Hashable { - associatedtype T: Any - var type: T.Type { get } - - var asString: String { get } -} - -struct GenericTypeWrapper: AbstractTypeWrapper { - let type: T.Type - - var asString: String { - String(describing: type) - } - - static func == (lhs: GenericTypeWrapper, rhs: GenericTypeWrapper) -> Bool { - lhs.type == rhs.type - } - - func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(type)) - } -} - -extension GenericTypeWrapper: Codable { - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - let typeName = self.asString - try container.encode(typeName) - } - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let typeName = try container.decode(String.self) - - guard let statType = typeName.asTFClassFromString as? T.Type else { - Logger.log("Error at decoding StatisticType", Self.self) - - let context = DecodingError.Context(codingPath: container.codingPath, - debugDescription: "Cannot decode \(typeName) as Statistic.Type") - - throw DecodingError.typeMismatch(T.Type.self, context) - } - - self.type = statType - } -} - -protocol TypeRepresentable: AnyObject { - static var asType: GenericTypeWrapper { get } -} - -extension TypeRepresentable { - static var asType: GenericTypeWrapper { - GenericTypeWrapper(type: Self.self) - } -} diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift index a625dfab..1b0f0497 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler+Auth.swift @@ -9,11 +9,6 @@ import Foundation /// This extension adds authentication methods to StorageHandler extension StorageHandler { - /*func onLogin() { - Task { - onAsyncLogin() - } - }*/ func onLogin() { Logger.log("LOGIN: IS CALLED FROM STORAGE HANDLER", Self.self) @@ -88,8 +83,6 @@ extension StorageHandler { // 3. Resolve conflict between remote statistics and current statistics Self.resolveConflict(this: self.statisticsDatabase, that: remoteStorage) { resolvedStats in - Logger.log("ONRELOGIN --- THIS DB is \(String(describing: self.statisticsDatabase.toString()))", self) - Logger.log("ONRELOGIN --- THAT DB is \(String(describing: remoteStorage.toString()))", self) guard let finalStorage = resolvedStats else { Logger.log("RELOGIN ERROR: CONFLICT RESOLUTION FAILURE") completion(false) From 57485cb2057d9edbf8f256b6661723eac64f7cbb Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 18:43:58 +0800 Subject: [PATCH 27/29] Add StorageDatabase generalizability to Metadata --- .../AppMain/Application/AppDelegate.swift | 1 - TowerForge/TowerForge/StorageAPI/Metadata.swift | 13 +++++++++++++ .../TowerForge/StorageAPI/RemoteStorage.swift | 2 +- .../TowerForge/StorageAPI/StorageDatabase.swift | 2 ++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index f74de74f..8c99fa3b 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -23,7 +23,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { FirebaseApp.configure() /// Initialize all local storage - // StorageManager.initializeAllStorage() StorageHandler.initializeLocalStorageIfNotPresent() /// Prepare audio player to begin playing music diff --git a/TowerForge/TowerForge/StorageAPI/Metadata.swift b/TowerForge/TowerForge/StorageAPI/Metadata.swift index 40eb397f..671911d7 100644 --- a/TowerForge/TowerForge/StorageAPI/Metadata.swift +++ b/TowerForge/TowerForge/StorageAPI/Metadata.swift @@ -12,6 +12,7 @@ import Foundation /// - Information about device and the current user for use with Remote Storage /// - Meta-information about files stored locally, possibly for use with conflict resolution. class Metadata: StorageDatabase, Comparable, Equatable { + static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID } static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID } @@ -64,4 +65,16 @@ class Metadata: StorageDatabase, Comparable, Equatable { static func latest(lhs: Metadata, rhs: Metadata) -> Metadata { rhs.lastUpdated > lhs.lastUpdated ? rhs : lhs } + + static func merge(this: Metadata?, that: Metadata?) -> Self? { + guard let lhs = this, let rhs = that else { + return nil + } + + guard let latest = Metadata.latest(lhs: lhs, rhs: rhs) as? Self else { + return nil + } + + return latest + } } diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift index 0d9c9220..59e5af7e 100644 --- a/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage.swift @@ -37,7 +37,7 @@ class RemoteStorage { /// Saves the input StorageDatabase to firebase static func saveDataToFirebase(for ref: FirebaseReference, player: String, - with inputData: StorageDatabase, + with inputData: any StorageDatabase, completion: @escaping (Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(ref) diff --git a/TowerForge/TowerForge/StorageAPI/StorageDatabase.swift b/TowerForge/TowerForge/StorageAPI/StorageDatabase.swift index 5c0b57b8..f2fcf802 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageDatabase.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageDatabase.swift @@ -10,4 +10,6 @@ import Foundation protocol StorageDatabase: Codable, AnyObject { func encode(to encoder: Encoder) throws init(from decoder: Decoder) throws + + static func merge(this: Self?, that: Self?) -> Self? } From b6abe377a62e2e89a3fec15c58d96c6fe92bedaa Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 18:59:51 +0800 Subject: [PATCH 28/29] Update ViewController to be more organized and update mission --- .../TowerForge.xcodeproj/project.pbxproj | 8 +- .../Achievements/AchievementsFactory.swift | 2 +- ...t.swift => ThousandKillsAchievement.swift} | 8 +- .../PlayerStatsViewController.swift | 86 ++++++------------- 4 files changed, 36 insertions(+), 68 deletions(-) rename TowerForge/TowerForge/Metrics/Achievements/Implemented/{HundredKillsAchievement.swift => ThousandKillsAchievement.swift} (70%) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 441aceae..b458dc9e 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -232,7 +232,7 @@ BA82C76D2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C76C2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift */; }; BA82C76F2BCBDE91000515A0 /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C76E2BCBDE91000515A0 /* Metadata.swift */; }; BA82C7752BCC689F000515A0 /* AchievementsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7742BCC689F000515A0 /* AchievementsFactory.swift */; }; - BA82C7772BCC6913000515A0 /* HundredKillsAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7762BCC6913000515A0 /* HundredKillsAchievement.swift */; }; + BA82C7772BCC6913000515A0 /* ThousandKillsAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7762BCC6913000515A0 /* ThousandKillsAchievement.swift */; }; BA82C7792BCC6943000515A0 /* CenturionAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7782BCC6943000515A0 /* CenturionAchievement.swift */; }; BA82C7802BCD284D000515A0 /* InferenceEngineFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C77F2BCD284D000515A0 /* InferenceEngineFactory.swift */; }; BA82C7832BCD2B30000515A0 /* Mission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7822BCD2B30000515A0 /* Mission.swift */; }; @@ -509,7 +509,7 @@ BA82C76C2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatisticsDatabase+Merge.swift"; sourceTree = ""; }; BA82C76E2BCBDE91000515A0 /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = ""; }; BA82C7742BCC689F000515A0 /* AchievementsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsFactory.swift; sourceTree = ""; }; - BA82C7762BCC6913000515A0 /* HundredKillsAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HundredKillsAchievement.swift; sourceTree = ""; }; + BA82C7762BCC6913000515A0 /* ThousandKillsAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThousandKillsAchievement.swift; sourceTree = ""; }; BA82C7782BCC6943000515A0 /* CenturionAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenturionAchievement.swift; sourceTree = ""; }; BA82C77F2BCD284D000515A0 /* InferenceEngineFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngineFactory.swift; sourceTree = ""; }; BA82C7822BCD2B30000515A0 /* Mission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mission.swift; sourceTree = ""; }; @@ -1102,7 +1102,7 @@ isa = PBXGroup; children = ( BA82C7602BCBBA8A000515A0 /* FiftyKillsAchievement.swift */, - BA82C7762BCC6913000515A0 /* HundredKillsAchievement.swift */, + BA82C7762BCC6913000515A0 /* ThousandKillsAchievement.swift */, BA82C7782BCC6943000515A0 /* CenturionAchievement.swift */, ); path = Implemented; @@ -1730,7 +1730,7 @@ BA82C76D2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift in Sources */, 3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */, BA82C7422BC86FE1000515A0 /* TotalGamesStatistic.swift in Sources */, - BA82C7772BCC6913000515A0 /* HundredKillsAchievement.swift in Sources */, + BA82C7772BCC6913000515A0 /* ThousandKillsAchievement.swift in Sources */, BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */, 3CBECF8C2BBE9A41005EF39B /* TFRemoteEvent.swift in Sources */, 5240D0A92BB333B5004F1486 /* PointProp.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift index 1ce2b3cb..b3fe2488 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift @@ -12,7 +12,7 @@ class AchievementsFactory { static var availableAchievementTypes: [String: Achievement.Type] = [ String(describing: FiftyKillsAchievement.self): FiftyKillsAchievement.self, - String(describing: HundredKillsAchievement.self): HundredKillsAchievement.self, + String(describing: ThousandKillsAchievement.self): ThousandKillsAchievement.self, String(describing: CenturionAchievement.self): CenturionAchievement.self ] diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/ThousandKillsAchievement.swift similarity index 70% rename from TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift rename to TowerForge/TowerForge/Metrics/Achievements/Implemented/ThousandKillsAchievement.swift index 58e70b32..24288616 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/ThousandKillsAchievement.swift @@ -7,14 +7,14 @@ import Foundation -final class HundredKillsAchievement: Achievement { - var name: String = "100 Kills" - var description: String = "Attain 100 total kills in TowerForge" +final class ThousandKillsAchievement: Achievement { + var name: String = "1000 Kills" + var description: String = "Attain 1000 total kills in TowerForge" var currentParameters: [StatisticTypeWrapper: any Statistic] = [:] static var definedParameters: [StatisticTypeWrapper: Double] = [ - TotalKillsStatistic.asType: 200.0 + TotalKillsStatistic.asType: 1_000.0 ] init(dependentStatistics: [Statistic]) { diff --git a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift index 7eaa5141..f8d32234 100644 --- a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift @@ -53,16 +53,6 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl return missionsEngine!.missionsDatabase } - /*static func getRankingEngine() -> RankingEngine { - let statsEngine = StatisticsEngine(with: StorageHandler()) - guard let rankEngine = staticEngine.inferenceEngines[RankingEngine.asType] - as? RankingEngine else { - Logger.log("PLAYER STATS: Error - Ranking engine cannot be found or cast") - return RankingEngine(staticEngine) - } - return rankEngine - }*/ - static func getAchievements() -> AchievementsDatabase { let statsEngine = StatisticsEngine(with: StorageHandler()) let achEngine = statsEngine.inferenceEngines[AchievementsEngine.asType] as? AchievementsEngine @@ -70,22 +60,6 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl return achEngine!.achievementsDatabase } - // Designated initializer for programmatic instantiation - /*init() { - self.statisticsEngine = StatisticsEngine(with: StorageHandler()) - super.init(nibName: nil, bundle: nil) - } - - // Required initializer for loading the view controller from a storyboard - required init?(coder: NSCoder) { - self.statisticsEngine = StatisticsEngine(with: StorageHandler()) - super.init(coder: coder) - - // You might set a default value for myProperty here, or perhaps - // this initializer will never actually be used in your app if you're - // not loading this view controller from a storyboard. - }*/ - override func viewDidLoad() { super.viewDidLoad() @@ -97,26 +71,13 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl achievementsView.allowsSelection = false missionsView.allowsSelection = false - // initializeBackground() - initializePlayerStats() initializeRanks() highlightCurrentRank() reloadAll() } - func initializeBackground() { - /* TODO: Add background image - let backgroundImage = UIImageView(frame: UIScreen.main.bounds) - backgroundImage.image = UIImage(named: "stone-tile") - backgroundImage.contentMode = .scaleAspectFill - view.addSubview(backgroundImage) // Add the image view to the view hierarchy - view.sendSubviewToBack(backgroundImage) // Send the image to the background - */ - } - func initializePlayerStats() { - // rankImageView.image = UIImage(named: currentRank.imageIdentifer) rankNameLabel.text = String("--- Rank: \(rank.rawValue) ---") characterImage.image = rank.isOfficer() ? UIImage(named: "Shooter-1") : UIImage(named: "melee-1") currentExp.text = String("XP: \(exp)") @@ -176,27 +137,35 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if tableView == achievementsView { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", - for: indexPath) as? CustomAchievementCell else { - fatalError("Could not dequeue CustomAchievementCell") - } - - let achievementPair = achievements.asSortedArray[indexPath.row] - let achievement = achievementPair.value - - // Configure the cell elements - cell.nameLabel.text = achievement.name - cell.descriptionLabel.text = achievement.description - cell.achievementImageView.image = UIImage(named: achievement.imageIdentifier) - cell.progressView.progress = Float(achievement.overallProgressRateRounded) - // cell.progressPercentage.text = String(describing: Float(achievement.overallProgressRateRounded)) - let statusImageName = achievement.isComplete ? "checkmark.circle" : "x.circle" - cell.statusImageView.image = UIImage(systemName: statusImageName) - cell.statusImageView.tintColor = achievement.isComplete ? .green : .red - - return cell + return getCustomAchievementCell(tableView: tableView, cellForRowAt: indexPath) } + return getCustomMissionCell(tableView: tableView, cellForRowAt: indexPath) + } + + func getCustomAchievementCell(tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", + for: indexPath) as? CustomAchievementCell else { + fatalError("Could not dequeue CustomAchievementCell") + } + + let achievementPair = achievements.asSortedArray[indexPath.row] + let achievement = achievementPair.value + + // Configure the cell elements + cell.nameLabel.text = achievement.name + cell.descriptionLabel.text = achievement.description + cell.achievementImageView.image = UIImage(named: achievement.imageIdentifier) + cell.progressView.progress = Float(achievement.overallProgressRateRounded) + // cell.progressPercentage.text = String(describing: Float(achievement.overallProgressRateRounded)) + let statusImageName = achievement.isComplete ? "checkmark.circle" : "x.circle" + cell.statusImageView.image = UIImage(systemName: statusImageName) + cell.statusImageView.tintColor = achievement.isComplete ? .green : .red + + return cell + } + + func getCustomMissionCell(tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let missionCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? CustomMissionCell else { fatalError("Could not dequeue CustomMissionCell") @@ -214,7 +183,6 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl missionCell.statusImageView.tintColor = mission.isComplete ? .green : .red return missionCell - } func reloadAll() { From 9769a041ccfe188f593df7acc345482ad223f880 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 21 Apr 2024 19:07:40 +0800 Subject: [PATCH 29/29] Fix style --- .../Metrics/Ranking/RankingEngine.swift | 3 +- .../Metrics/Statistics/Statistic.swift | 14 ------ .../Metrics/Statistics/StatisticsEngine.swift | 32 ------------- .../StorageAPI/RemoteStorage+Access.swift | 46 +------------------ .../StorageAPI/StorageHandler.swift | 1 - .../PlayerStatsViewController.swift | 3 +- 6 files changed, 4 insertions(+), 95 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift index fe80bd52..524069ee 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -35,7 +35,8 @@ class RankingEngine: InferenceEngine, InferenceDataDelegate { Rank.allCases.first { $0.valueRange.contains(Int(self.currentExp)) } ?? .PRIVATE } - var currentKd: Double { + /// Returns the current kill/death ratio as a double between 0 and 1. + var currentKdRatio: Double { let kills = getPermanentValueFor(TotalKillsStatistic.self) let deaths = getPermanentValueFor(TotalDeathsStatistic.self) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 4365f91a..23092438 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -177,17 +177,3 @@ extension Statistic { self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) } } - -/*/// This extension allows Statistic to be merged -extension Statistic { - - static func merge(this: T, that: T) -> T { - let largerPermanent = max(this.permanentValue, that.permanentValue) - let largerCurrent = max(this.currentValue, that.currentValue) - let largerMaxCurrent = max(this.maximumCurrentValue, that.maximumCurrentValue) - - return T(permanentValue: largerPermanent, - currentValue: largerCurrent, - maxCurrentValue: largerMaxCurrent) - } -}*/ diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index a8878fc6..dec08504 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -14,20 +14,6 @@ class StatisticsEngine: InferenceDataDelegate { var eventStatisticLinks = EventStatisticLinkDatabase() var inferenceEngines: [InferenceEngineTypeWrapper: InferenceEngine] = [:] - /*init() { - self.initializeStatistics() - self.loadStatistics() - self.setUpLinks() - self.setUpInferenceEngines() - }*/ - - /*init(with statistics: StatisticsDatabase) { - self.statisticsDatabase = statistics - self.initializeStatistics() - self.setUpLinks() - self.setUpInferenceEngines() - }*/ - init(with storageHandler: StatisticsEngineDelegate) { self.statsEngineDelegate = storageHandler self.statisticsDatabase = storageHandler.statisticsDatabase @@ -87,31 +73,13 @@ class StatisticsEngine: InferenceDataDelegate { saveStatistics() } - /// TODO: Consider if passing the stats database directly is better or - /// to follow delegate pattern and have unowned statsEngine/db variables inside - /// InferenceEngines func notifyInferenceEngines() { inferenceEngines.values.forEach { $0.updateOnReceive() } } private func saveStatistics() { Logger.log("STATISTICS_ENGINE SAVE: Statistics save triggered", self) - Logger.log("STATISTICS_ENGINE SAVE: \(String(describing: statsEngineDelegate?.statisticsDatabase.toString()))", self) statsEngineDelegate?.save() - // _ = StorageManager.saveUniversally(statistics) - } - - private func saveStatisticsLocally() { - Logger.log("STATISTICS_ENGINE LOCAL SAVE: Local save triggered", self) - Logger.log("STATISTICS_ENGINE LOCAL SAVE: \(String(describing: statsEngineDelegate?.statisticsDatabase.toString()))", self) - statsEngineDelegate?.localSave() - // _ = StorageManager.saveUniversally(statistics) } - /*private func loadStatistics() { - if let loadedStats = StorageManager.loadUniversally() { - statisticsDatabase = loadedStats - } - }*/ - } diff --git a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift index 0a079275..72061971 100644 --- a/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift +++ b/TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift @@ -26,7 +26,7 @@ extension RemoteStorage { RemoteStorage.checkIfPlayerDatabaseExists(for: playerId) { storageExists in // Check for player metadata existence RemoteStorage.checkIfPlayerMetadataExists(for: playerId) { metadataExists in - // Check for any inconsistencies + // Check for any inconsistencies, if either one don't exist, consider player to be non-existent if (storageExists && !metadataExists) || (!storageExists && metadataExists) { Logger.log("Inconsistency error: Storage Exists: \(storageExists), Metadata Exists: \(metadataExists)") } @@ -145,48 +145,4 @@ extension RemoteStorage { } } } - - /*static func loadStorageFromFirebase(player: String) -> StatisticsDatabase? { - guard Self.checkIfPlayerStorageExists(for: player) else { - return nil - } - - var stats: StatisticsDatabase? - Self.loadDataFromFirebase(for: .Statistics, - player: player) { (statisticsDatabase: StatisticsDatabase?, error: Error?) in - if let error = error { - Logger.log("Error loading storage from firebase: \(error)", self) - } else if let statistics = statisticsDatabase { - Logger.log("Successfully loaded statistics from firebase", self) - stats = statistics - } else { - // No error and no database implies that database is empty, return nil - Logger.log("No error and empty database, new one will NOT be auto-created", self) - } - } - - return stats - } - - static func loadMetadataFromFirebase(player: String) async -> Metadata? { - guard await Self.checkIfPlayerMetadataExistsAsync(for: player) else { - return nil - } - - var metadata: Metadata? - Self.loadDataFromFirebase(for: .Statistics, - player: player) { (remoteMetadata: Metadata?, error: Error?) in - if let error = error { - Logger.log("Error loading storage from firebase: \(error)", self) - } else if let currentMetadata = remoteMetadata { - Logger.log("Successfully loaded statistics from firebase", self) - metadata = currentMetadata - } else { - // No error and no database implies that database is empty, return nil - Logger.log("No error and empty database, new one will NOT be auto-created", self) - } - } - - return metadata - }*/ } diff --git a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift index 4db7ff9d..6fd32451 100644 --- a/TowerForge/TowerForge/StorageAPI/StorageHandler.swift +++ b/TowerForge/TowerForge/StorageAPI/StorageHandler.swift @@ -10,7 +10,6 @@ import Foundation protocol StatisticsEngineDelegate: AnyObject { var statisticsDatabase: StatisticsDatabase { get set } func save() - func localSave() } class StorageHandler: AuthenticationDelegate, StatisticsEngineDelegate { diff --git a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift index f8d32234..113e95a6 100644 --- a/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/PlayerStatsViewController.swift @@ -32,7 +32,6 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl @IBOutlet private var col: UILabel! @IBOutlet private var gen: UILabel! - // static var staticEngine = StatisticsEngine(with: StorageHandler()) var statisticsEngine = StatisticsEngine(with: StorageHandler()) var statisticsDatabase: StatisticsDatabase { statisticsEngine.statisticsDatabase } var achievements: AchievementsDatabase = getAchievements() @@ -41,7 +40,7 @@ class PlayerStatsViewController: UIViewController, UITableViewDataSource, UITabl var rank: Rank { rankingEngine.currentRank } var exp: String { rankingEngine.currentExpAsString } - var kd: Double { rankingEngine.currentKd } + var kd: Double { rankingEngine.currentKdRatio } var kills: Int { Int(rankingEngine.getPermanentValueFor(TotalKillsStatistic.self)) } var deaths: Int { Int(rankingEngine.getPermanentValueFor(TotalDeathsStatistic.self)) } var games: Int { Int(rankingEngine.getPermanentValueFor(TotalGamesStatistic.self)) }