From d9ac8cc34b0e09a66e88894890b7ae631146ac4f Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sat, 13 Apr 2024 21:51:07 +0800 Subject: [PATCH 01/54] Reorganize statistics api and add inline comments --- .../TowerForge.xcodeproj/project.pbxproj | 24 +++- .../Commons/Enums/StorageEnums.swift | 3 +- .../Implemented/DefaultStatistic.swift | 15 ++- .../Statistics/Implemented/Statistic.swift | 74 ------------ .../Implemented/TotalDeathsStatistic.swift | 17 +-- .../Implemented/TotalGamesStatistic.swift | 17 +-- .../Implemented/TotalKillsStatistic.swift | 11 +- .../EventStatisticLinkDatabase.swift | 0 .../Statistics/StatisticsAPI/Statistic.swift | 110 ++++++++++++++++++ .../StatisticsAPI/StatisticTypeWrapper.swift | 23 ++++ .../StatisticUpdateLinkDatabase.swift | 0 .../StatisticsDatabase.swift | 0 .../StatisticsEngine.swift | 0 .../StatisticsFactory.swift | 0 14 files changed, 187 insertions(+), 107 deletions(-) delete mode 100644 TowerForge/TowerForge/Statistics/Implemented/Statistic.swift rename TowerForge/TowerForge/Statistics/{ => StatisticsAPI}/EventStatisticLinkDatabase.swift (100%) create mode 100644 TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift create mode 100644 TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift rename TowerForge/TowerForge/Statistics/{ => StatisticsAPI}/StatisticUpdateLinkDatabase.swift (100%) rename TowerForge/TowerForge/Statistics/{ => StatisticsAPI}/StatisticsDatabase.swift (100%) rename TowerForge/TowerForge/Statistics/{ => StatisticsAPI}/StatisticsEngine.swift (100%) rename TowerForge/TowerForge/Statistics/{ => StatisticsAPI}/StatisticsFactory.swift (100%) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index f99b3489..5efb3ab6 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -202,6 +202,7 @@ BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */; }; BA82C7532BC8A41B000515A0 /* AchievementsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */; }; BA82C7552BC8A441000515A0 /* AchievementStatsLinkDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */; }; + BA82C7582BCAB4C2000515A0 /* StatisticTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -439,6 +440,7 @@ BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDeathsStatistic.swift; sourceTree = ""; }; BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsEngine.swift; sourceTree = ""; }; BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementStatsLinkDatabase.swift; sourceTree = ""; }; + BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticTypeWrapper.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -930,12 +932,8 @@ BA2F5ABF2BC80BD200CBD8E9 /* Statistics */ = { isa = PBXGroup; children = ( + BA82C7562BCAB482000515A0 /* StatisticsAPI */, BA82C73E2BC85D90000515A0 /* Implemented */, - BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, - BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */, - BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */, - BA2F5ACA2BC82CCC00CBD8E9 /* EventStatisticLinkDatabase.swift */, - BA2F5ACC2BC8313900CBD8E9 /* StatisticUpdateLinkDatabase.swift */, ); path = Statistics; sourceTree = ""; @@ -966,7 +964,6 @@ BA82C73E2BC85D90000515A0 /* Implemented */ = { isa = PBXGroup; children = ( - BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */, BA82C7472BC87DB9000515A0 /* DefaultStatistic.swift */, BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */, BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */, @@ -998,6 +995,20 @@ path = Implemented; sourceTree = ""; }; + BA82C7562BCAB482000515A0 /* StatisticsAPI */ = { + isa = PBXGroup; + children = ( + BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */, + BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */, + BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, + BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */, + BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */, + BA2F5ACA2BC82CCC00CBD8E9 /* EventStatisticLinkDatabase.swift */, + BA2F5ACC2BC8313900CBD8E9 /* StatisticUpdateLinkDatabase.swift */, + ); + path = StatisticsAPI; + sourceTree = ""; + }; BAFFB9272BB09E0E00D8301F /* Storyboards */ = { isa = PBXGroup; children = ( @@ -1610,6 +1621,7 @@ 3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */, BAFFB9752BBD833400D8301F /* AudioManager.swift in Sources */, 52DF5FFB2BA3601400135367 /* HealthComponent.swift in Sources */, + BA82C7582BCAB4C2000515A0 /* StatisticTypeWrapper.swift in Sources */, 3C9955BC2BA563A800D33FA5 /* TFEvent.swift in Sources */, 527A077A2BB3F35300CD9D08 /* DeathProp.swift in Sources */, BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift index 68336a67..ed8141cc 100644 --- a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift +++ b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift @@ -51,7 +51,8 @@ class StorageEnums { enum StatisticDefaultCodingKeys: String, CodingKey, Codable { case statisticName - case statisticValue + case permanentValue + case currentValue } } diff --git a/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift index a722e8ae..01acc116 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift @@ -10,21 +10,20 @@ import Foundation final class DefaultStatistic: Statistic { var statisticName: StatisticName = .defaultStatistic - var statisticValue: Double = .zero - - var statisticUpdateLinks: StatisticUpdateLinkDatabase { - self.getStatisticUpdateLinks() - } + var permanentValue: Double = .zero + var currentValue: Double = .zero init(name: StatisticName = .defaultStatistic, - value: Double = .zero) { + permanentValue: Double = .zero, + currentValue: Double = .zero) { self.statisticName = name - self.statisticValue = value + self.permanentValue = permanentValue + self.currentValue = currentValue } func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: DisabledEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateValue(by: 1.0) } + let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } let eventUpdateDictionary = [eventType: updateActor] let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) diff --git a/TowerForge/TowerForge/Statistics/Implemented/Statistic.swift b/TowerForge/TowerForge/Statistics/Implemented/Statistic.swift deleted file mode 100644 index da7f25bd..00000000 --- a/TowerForge/TowerForge/Statistics/Implemented/Statistic.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Statistic.swift -// TowerForge -// -// Created by Rubesh on 11/4/24. -// - -import Foundation - -struct StatisticTypeWrapper: Equatable, Hashable { - let type: Statistic.Type - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.type == rhs.type - } - - func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(type)) - } -} - -/// Each Statistic can be identified with a name and it will be linked to a particular -/// event. -protocol Statistic: AnyObject, Codable { - var statisticName: StatisticName { get } - var statisticUpdateLinks: StatisticUpdateLinkDatabase { get } - - /// The original value of the statistic prior to the start of the game - var statisticValue: Double { get set } - // var statisticAdditionalValue: Double { get set } - // var statisticCurrentValue: Double { get } - - func update(for eventType: TFEventTypeWrapper) - func getEventLinksOnly() -> [TFEventTypeWrapper] - - init(name: StatisticName, value: Double) - -} - -extension Statistic { - - func updateValue(by value: Double) { - statisticValue += value - } - - func update(for eventType: TFEventTypeWrapper) { - guard let updateLink = statisticUpdateLinks - .getStatisticUpdateActor(for: eventType) else { - return - } - - updateLink?(self) - Logger.log("Value update for eventType \(eventType)", self) - } - - func getEventLinksOnly() -> [TFEventTypeWrapper] { - statisticUpdateLinks.getAllEventTypes() - } - - func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) - try container.encode(statisticName, forKey: .statisticName) - try container.encode(statisticValue, forKey: .statisticValue) - } - - init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) - let name = try container.decode(StatisticName.self, forKey: .statisticName) - let value = try container.decode(Double.self, forKey: .statisticValue) - - self.init(name: name, value: value) - } - -} diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift index 5cf9e712..eb1e2aca 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift @@ -1,8 +1,8 @@ // -// TotalDeathsStatistic.swift +// KillStatistic.swift // TowerForge // -// Created by Rubesh on 12/4/24. +// Created by Rubesh on 11/4/24. // import Foundation @@ -10,21 +10,24 @@ import Foundation final class TotalDeathsStatistic: Statistic { var statisticName: StatisticName = .totalDeaths - var statisticValue: Double = .zero + var permanentValue: Double = .zero + var currentValue: Double = .zero var statisticUpdateLinks: StatisticUpdateLinkDatabase { self.getStatisticUpdateLinks() } - init(name: StatisticName = .totalDeaths, - value: Double = .zero) { + init(name: StatisticName = .totalKills, + permanentValue: Double = .zero, + currentValue: Double = .zero) { self.statisticName = name - self.statisticValue = value + self.permanentValue = permanentValue + self.currentValue = currentValue } func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: DeathEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateValue(by: 1.0) } + let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } let eventUpdateDictionary = [eventType: updateActor] let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift index 16ed97d9..174d66bd 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift @@ -1,8 +1,8 @@ // -// TotalGamesStatistic.swift +// KillStatistic.swift // TowerForge // -// Created by Rubesh on 12/4/24. +// Created by Rubesh on 11/4/24. // import Foundation @@ -10,21 +10,24 @@ import Foundation final class TotalGamesStatistic: Statistic { var statisticName: StatisticName = .totalGamesPlayed - var statisticValue: Double = .zero + var permanentValue: Double = .zero + var currentValue: Double = .zero var statisticUpdateLinks: StatisticUpdateLinkDatabase { self.getStatisticUpdateLinks() } - init(name: StatisticName = .totalGamesPlayed, - value: Double = .zero) { + init(name: StatisticName = .totalKills, + permanentValue: Double = .zero, + currentValue: Double = .zero) { self.statisticName = name - self.statisticValue = value + self.permanentValue = permanentValue + self.currentValue = currentValue } func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: GameStartEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateValue(by: 1.0) } + let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } let eventUpdateDictionary = [eventType: updateActor] let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift index 3a0071e3..bad03774 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift @@ -10,21 +10,24 @@ import Foundation final class TotalKillsStatistic: Statistic { var statisticName: StatisticName = .totalKills - var statisticValue: Double = .zero + var permanentValue: Double = .zero + var currentValue: Double = .zero var statisticUpdateLinks: StatisticUpdateLinkDatabase { self.getStatisticUpdateLinks() } init(name: StatisticName = .totalKills, - value: Double = .zero) { + permanentValue: Double = .zero, + currentValue: Double = .zero) { self.statisticName = name - self.statisticValue = value + self.permanentValue = permanentValue + self.currentValue = currentValue } func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: KillEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateValue(by: 1.0) } + let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } let eventUpdateDictionary = [eventType: updateActor] let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) diff --git a/TowerForge/TowerForge/Statistics/EventStatisticLinkDatabase.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/EventStatisticLinkDatabase.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/EventStatisticLinkDatabase.swift rename to TowerForge/TowerForge/Statistics/StatisticsAPI/EventStatisticLinkDatabase.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift new file mode 100644 index 00000000..a358de9e --- /dev/null +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift @@ -0,0 +1,110 @@ +// +// Statistic.swift +// TowerForge +// +// Created by Rubesh on 11/4/24. +// + +import Foundation + +/// A Statistic is a metric to be tracked within TowerForge. +/// +/// Conform +protocol Statistic: AnyObject, Codable { + var statisticName: StatisticName { get } + + /// The original value of the statistic prior to the start of the game seequence + var permanentValue: Double { get set } + + /// The changes incurred to the statistic in the course of the current game sequence + var currentValue: Double { get set } + + /// A computed sum of the permanent value and the current value of the statistic + var currentTotalValue: Double { get } + + /// The principal interface for modifying the Statistic + func update(for eventType: TFEventTypeWrapper) + + /// 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. + func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase + func getEventLinksOnly() -> [TFEventTypeWrapper] + + init(name: StatisticName, permanentValue: Double, currentValue: Double) + +} + +/// This extension adds default utility functions such as generic increments +/// and decrements of values. +extension Statistic { + + /// Returns the total value of the statistic taking into account the + /// permanent value of the Statistic prior to changes and the current changes + /// to value. + var currentTotalValue: Double { + permanentValue + currentValue + } + + /// Increments the permanent value of the statistic by the given amount + func updatePermanentValue(by value: Double) { + permanentValue += value + } + + /// Increments the current value of the statistic by the given amount + func updateCurrentValue(by value: Double) { + currentValue += value + } + + /// Generic increment of the permanent value + func permanentValueUnitIncrement() { + updatePermanentValue(by: 1.0) + } + + /// Generic increment of the current value + func currentValueUnitIncrement() { + updateCurrentValue(by: 1.0) + } + + /// Finalizes the statistic by adding the current value to the + /// permanent value and resetting the current value to zero. This is + /// intended to be executed after the end of a game sequence. + func finalizeStatistic() { + updatePermanentValue(by: currentValue) + currentValue = .zero + } + + /// Returns the event types for which this Statistic would be involved + func getEventLinksOnly() -> [TFEventTypeWrapper] { + self.getStatisticUpdateLinks().getAllEventTypes() + } + + /// Updates the statistic according to an UpdateActor that is retrieved from the + func update(for eventType: TFEventTypeWrapper) { + guard let updateLink = self.getStatisticUpdateLinks().getStatisticUpdateActor(for: eventType) else { + return + } + + updateLink?(self) + Logger.log("Value update for eventType \(eventType)", self) + } +} + +/// This extension adds default implementations for encoding and decoding a statistic +extension Statistic { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) + try container.encode(statisticName, forKey: .statisticName) + try container.encode(permanentValue, forKey: .permanentValue) + try container.encode(currentValue, forKey: .currentValue) + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) + let name = try container.decode(StatisticName.self, forKey: .statisticName) + let value = try container.decode(Double.self, forKey: .permanentValue) + let current = try container.decode(Double.self, forKey: .currentValue) + + self.init(name: name, permanentValue: value, currentValue: current) + } +} diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift new file mode 100644 index 00000000..c972e79a --- /dev/null +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift @@ -0,0 +1,23 @@ +// +// StatisticTypeWrapper.swift +// TowerForge +// +// Created by Rubesh on 13/4/24. +// + +import Foundation + +/// A wrapper for the concrete type of Statistic. +/// +/// Allows the type itself to be used as a hashable key in a dictionary +struct StatisticTypeWrapper: Equatable, Hashable { + let type: Statistic.Type + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.type == rhs.type + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(type)) + } +} diff --git a/TowerForge/TowerForge/Statistics/StatisticUpdateLinkDatabase.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticUpdateLinkDatabase.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticUpdateLinkDatabase.swift rename to TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticUpdateLinkDatabase.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsDatabase.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsDatabase.swift rename to TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsEngine.swift rename to TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsFactory.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsFactory.swift rename to TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift From 582e6a5a37637f66ce7cfd441cf89147bc63b05b Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 01:05:32 +0800 Subject: [PATCH 02/54] Update Statistics API to use TypeWrapper instead of enum --- .../Statistics/StatisticsAPI/Statistic.swift | 12 ++++++-- .../StatisticsAPI/StatisticTypeWrapper.swift | 26 +++++++++++++++++ .../StatisticsAPI/StatisticsDatabase.swift | 29 ++++++++++--------- .../StatisticsAPI/StatisticsEngine.swift | 6 ++-- .../StatisticsAPI/StatisticsFactory.swift | 2 +- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift index a358de9e..9073baff 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift @@ -8,10 +8,8 @@ import Foundation /// A Statistic is a metric to be tracked within TowerForge. -/// -/// Conform protocol Statistic: AnyObject, Codable { - var statisticName: StatisticName { get } + var statisticName: StatisticTypeWrapper { get } /// The original value of the statistic prior to the start of the game seequence var permanentValue: Double { get set } @@ -39,6 +37,14 @@ protocol Statistic: AnyObject, Codable { /// and decrements of values. extension Statistic { + static var asType: StatisticTypeWrapper { + StatisticTypeWrapper(type: Self.self) + } + + var statisticName: StatisticTypeWrapper { + StatisticTypeWrapper(type: Self.self) + } + /// Returns the total value of the statistic taking into account the /// permanent value of the Statistic prior to changes and the current changes /// to value. diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift index c972e79a..5a97de1a 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift @@ -21,3 +21,29 @@ struct StatisticTypeWrapper: Equatable, Hashable { hasher.combine(ObjectIdentifier(type)) } } + +/// This extension allows the wrapped type to be written to file +extension StatisticTypeWrapper: Codable { + enum CodingKeys: String, CodingKey { + case typeName + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + let typeName = String(describing: type) + try container.encode(typeName, forKey: .typeName) + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let typeName = try container.decode(String.self, forKey: .typeName) + + guard let type = NSClassFromString(typeName) as? Statistic.Type else { + throw DecodingError.dataCorruptedError(forKey: .typeName, + in: container, + debugDescription: "Cannot decode Statistic.Type from \(typeName)") + } + + self.type = type + } +} diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift index 014b90ee..9ee7c53a 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift @@ -9,33 +9,33 @@ import Foundation import FirebaseDatabaseInternal class StatisticsDatabase { - var statistics: [StatisticName: Statistic] = [:] + var statistics: [StatisticTypeWrapper: Statistic] = [:] init() { self.load() } - func addStatistic(for statName: StatisticName) { + func addStatistic(for statName: StatisticTypeWrapper) { statistics[statName] = defaultStatisticGenerator[statName]?() } - func getStatistic(for statName: StatisticName) -> Statistic { + func getStatistic(for statName: StatisticTypeWrapper) -> Statistic { statistics[statName] ?? DefaultStatistic() } - private var defaultStatisticDecoder: [StatisticName: (JSONDecoder, Data) throws -> Statistic] { + private var defaultStatisticDecoder: [StatisticTypeWrapper: (JSONDecoder, Data) throws -> Statistic] { [ - .totalKills: { decoder, data in try decoder.decode(TotalKillsStatistic.self, from: data) }, - .totalGamesPlayed: { decoder, data in try decoder.decode(TotalGamesStatistic.self, from: data) }, - .totalDeaths: { decoder, data in try decoder.decode(TotalDeathsStatistic.self, from: data) } + TotalKillsStatistic.asType: { decoder, data in try decoder.decode(TotalKillsStatistic.self, from: data) }, + TotalGamesStatistic.asType: { decoder, data in try decoder.decode(TotalGamesStatistic.self, from: data) }, + TotalDeathsStatistic.asType: { decoder, data in try decoder.decode(TotalDeathsStatistic.self, from: data) } ] } - private var defaultStatisticGenerator: [StatisticName: () -> Statistic] { + private var defaultStatisticGenerator: [StatisticTypeWrapper: () -> Statistic] { [ - .totalKills: { TotalKillsStatistic() }, - .totalGamesPlayed: { TotalGamesStatistic() }, - .totalDeaths: { TotalDeathsStatistic() } + TotalKillsStatistic.asType: { TotalKillsStatistic() }, + TotalGamesStatistic.asType: { TotalGamesStatistic() }, + TotalDeathsStatistic.asType: { TotalDeathsStatistic() } ] } @@ -57,9 +57,10 @@ class StatisticsDatabase { } do { - guard let statisticName = StatisticName(rawValue: key) else { + guard let statisticType = NSClassFromString(key) as? Statistic.Type else { continue } + let statisticName = statisticType.asType let decoder = JSONDecoder() let statistic: Statistic? statistic = try self.defaultStatisticDecoder[statisticName]?(decoder, statisticData) @@ -75,13 +76,13 @@ class StatisticsDatabase { func save() { let databaseReference = FirebaseDatabaseReference(.Statistics) - var statisticsDictionary = [String: Any]() + var statisticsDictionary = [StatisticTypeWrapper: Any]() for (name, statistic) in statistics { do { let data = try JSONEncoder().encode(statistic) let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) - statisticsDictionary[name.rawValue] = dictionary + statisticsDictionary[name] = dictionary } catch { Logger.log("Error encoding statistic \(name): \(error)") } diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift index 01daf855..87ebb396 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift @@ -35,13 +35,13 @@ class StatisticsEngine { /// Add statistics links func setUpLinks() { eventStatisticLinks.addStatisticLink(for: KillEvent.self, - with: statisticsDatabase.getStatistic(for: .totalKills)) + with: statisticsDatabase.getStatistic(for: TotalKillsStatistic.asType)) eventStatisticLinks.addStatisticLink(for: GameStartEvent.self, - with: statisticsDatabase.getStatistic(for: .totalGamesPlayed)) + with: statisticsDatabase.getStatistic(for: TotalGamesStatistic.asType)) eventStatisticLinks.addStatisticLink(for: DeathEvent.self, - with: statisticsDatabase.getStatistic(for: .totalDeaths)) + with: statisticsDatabase.getStatistic(for: TotalDeathsStatistic.asType)) } private func initializeStatistics() { diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift index 9ebc77ea..19a97f5a 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift @@ -24,7 +24,7 @@ class StatisticsFactory { static func getDefaultStatisticsDatabase() -> StatisticsDatabase { let statsDatabase = StatisticsDatabase() - StatisticName.allCases.forEach { statsDatabase.addStatistic(for: $0) } + availableStatisticsTypes.forEach { statsDatabase.addStatistic(for: $0.asType) } return statsDatabase } } From cf1694c9c4f718044e1ca737d15b49eff46f55be Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 01:14:37 +0800 Subject: [PATCH 03/54] Fix style --- .../StatisticsAPI/StatisticTypeWrapper.swift | 2 +- .../StatisticsAPI/StatisticsDatabase.swift | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift index 5a97de1a..8050035d 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift @@ -22,7 +22,7 @@ struct StatisticTypeWrapper: Equatable, Hashable { } } -/// This extension allows the wrapped type to be written to file +/// This extension allows the wrapped type to be written to and read from file extension StatisticTypeWrapper: Codable { enum CodingKeys: String, CodingKey { case typeName diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift index 9ee7c53a..54f2547a 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift @@ -12,7 +12,7 @@ class StatisticsDatabase { var statistics: [StatisticTypeWrapper: Statistic] = [:] init() { - self.load() + self.loadFromFirebase() } func addStatistic(for statName: StatisticTypeWrapper) { @@ -40,7 +40,7 @@ class StatisticsDatabase { } /// TODO: Maybe can change this to FirebaseRepository - func load() { + func loadFromFirebase() { let databaseReference = FirebaseDatabaseReference(.Statistics) databaseReference.child("statistics").getData(completion: { error, snapshot in guard error == nil else { @@ -62,9 +62,13 @@ class StatisticsDatabase { } let statisticName = statisticType.asType let decoder = JSONDecoder() + let statistic: Statistic? statistic = try self.defaultStatisticDecoder[statisticName]?(decoder, statisticData) - if let stat = statistic { self.statistics[statisticName] = stat } + + if let stat = statistic { + self.statistics[statisticName] = stat + } } catch { Logger.log("Error decoding \(key):, \(error)") @@ -74,7 +78,7 @@ class StatisticsDatabase { }) } - func save() { + func saveToFirebase() { let databaseReference = FirebaseDatabaseReference(.Statistics) var statisticsDictionary = [StatisticTypeWrapper: Any]() From df10dc5778f517e62a4d2a14742a1b5892a1e5de Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 02:47:01 +0800 Subject: [PATCH 04/54] Update Metrics API storage functionality --- .../Events/GameEvents/KillEvent.swift | 4 +-- .../Events/WorldEvents/GameStartEvent.swift | 2 +- .../Systems/BaseSystems/StatisticSystem.swift | 2 +- .../Implemented/DefaultStatistic.swift | 6 +--- .../Implemented/TotalDeathsStatistic.swift | 6 +--- .../Implemented/TotalGamesStatistic.swift | 6 +--- .../Implemented/TotalKillsStatistic.swift | 6 +--- .../Statistics/StatisticsAPI/Statistic.swift | 34 +++++++++++++++---- .../StatisticsAPI/StatisticTypeWrapper.swift | 5 +++ .../StatisticsAPI/StatisticsDatabase.swift | 12 ++++--- .../StatisticsAPI/StatisticsEngine.swift | 4 +-- .../StatisticsAPI/StatisticsFactory.swift | 17 +++++++--- 12 files changed, 61 insertions(+), 43 deletions(-) diff --git a/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift b/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift index f41775aa..d026510f 100644 --- a/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift @@ -26,9 +26,9 @@ struct KillEvent: TFEvent { if let statsSystem = target.system(ofType: StatisticSystem.self) { if player != .ownPlayer { - statsSystem.broadcast(for: self) + statsSystem.notify(for: self) } else { - statsSystem.broadcast(for: DeathEvent(entityId)) + statsSystem.notify(for: DeathEvent(entityId)) } } diff --git a/TowerForge/TowerForge/GameModule/Events/WorldEvents/GameStartEvent.swift b/TowerForge/TowerForge/GameModule/Events/WorldEvents/GameStartEvent.swift index 600b427c..4255a739 100644 --- a/TowerForge/TowerForge/GameModule/Events/WorldEvents/GameStartEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/WorldEvents/GameStartEvent.swift @@ -15,7 +15,7 @@ struct GameStartEvent: TFEvent { func execute(in target: any EventTarget) -> EventOutput { if let statsSystem = target.system(ofType: StatisticSystem.self) { - statsSystem.broadcast(for: self) + statsSystem.notify(for: self) } return EventOutput() diff --git a/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift b/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift index 784f9fc8..7bfe39b7 100644 --- a/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift @@ -23,7 +23,7 @@ class StatisticSystem: TFSystem { self.statsEngine = statsEngine } - func broadcast(for event: T) { + func notify(for event: T) { statsEngine.updateStatisticsOnReceive(event) } diff --git a/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift index 01acc116..2cc3bc0e 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift @@ -8,15 +8,11 @@ import Foundation final class DefaultStatistic: Statistic { - - var statisticName: StatisticName = .defaultStatistic var permanentValue: Double = .zero var currentValue: Double = .zero - init(name: StatisticName = .defaultStatistic, - permanentValue: Double = .zero, + init(permanentValue: Double = .zero, currentValue: Double = .zero) { - self.statisticName = name self.permanentValue = permanentValue self.currentValue = currentValue } diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift index eb1e2aca..571ad322 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift @@ -8,8 +8,6 @@ import Foundation final class TotalDeathsStatistic: Statistic { - - var statisticName: StatisticName = .totalDeaths var permanentValue: Double = .zero var currentValue: Double = .zero @@ -17,10 +15,8 @@ final class TotalDeathsStatistic: Statistic { self.getStatisticUpdateLinks() } - init(name: StatisticName = .totalKills, - permanentValue: Double = .zero, + init(permanentValue: Double = .zero, currentValue: Double = .zero) { - self.statisticName = name self.permanentValue = permanentValue self.currentValue = currentValue } diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift index 174d66bd..7f756f96 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift @@ -8,8 +8,6 @@ import Foundation final class TotalGamesStatistic: Statistic { - - var statisticName: StatisticName = .totalGamesPlayed var permanentValue: Double = .zero var currentValue: Double = .zero @@ -17,10 +15,8 @@ final class TotalGamesStatistic: Statistic { self.getStatisticUpdateLinks() } - init(name: StatisticName = .totalKills, - permanentValue: Double = .zero, + init(permanentValue: Double = .zero, currentValue: Double = .zero) { - self.statisticName = name self.permanentValue = permanentValue self.currentValue = currentValue } diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift index bad03774..b775412a 100644 --- a/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift @@ -8,8 +8,6 @@ import Foundation final class TotalKillsStatistic: Statistic { - - var statisticName: StatisticName = .totalKills var permanentValue: Double = .zero var currentValue: Double = .zero @@ -17,10 +15,8 @@ final class TotalKillsStatistic: Statistic { self.getStatisticUpdateLinks() } - init(name: StatisticName = .totalKills, - permanentValue: Double = .zero, + init(permanentValue: Double = .zero, currentValue: Double = .zero) { - self.statisticName = name self.permanentValue = permanentValue self.currentValue = currentValue } diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift index 9073baff..aa809ada 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift @@ -29,7 +29,7 @@ protocol Statistic: AnyObject, Codable { func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase func getEventLinksOnly() -> [TFEventTypeWrapper] - init(name: StatisticName, permanentValue: Double, currentValue: Double) + init(permanentValue: Double, currentValue: Double) } @@ -98,19 +98,39 @@ extension Statistic { /// This extension adds default implementations for encoding and decoding a statistic extension Statistic { - func encode(to encoder: any Encoder) throws { + /*func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) try container.encode(statisticName, forKey: .statisticName) try container.encode(permanentValue, forKey: .permanentValue) try container.encode(currentValue, forKey: .currentValue) - } + }*/ - init(from decoder: any Decoder) throws { + /*init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) - let name = try container.decode(StatisticName.self, forKey: .statisticName) + let type = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) let value = try container.decode(Double.self, forKey: .permanentValue) let current = try container.decode(Double.self, forKey: .currentValue) - self.init(name: name, permanentValue: value, currentValue: current) - } + type.type.init(permanentValue: value, currentValue: current) + self.init(permanentValue: value, currentValue: current) + }*/ + + /*init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) + let type = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) + let typeName = String(describing: type) + let permanentValue = try container.decode(Double.self, forKey: .permanentValue) + let currentValue = try container.decode(Double.self, forKey: .currentValue) + + guard let instance = StatisticsFactory.createInstance(of: typeName, + permanentValue: permanentValue, + currentValue: currentValue) else { + + throw DecodingError.dataCorruptedError(forKey: .statisticName, + in: container, + debugDescription: "Cannot instantiate Statistic of type \(typeName)") + } + + self = instance + }*/ } diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift index 8050035d..7796c143 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift @@ -13,6 +13,10 @@ import Foundation struct StatisticTypeWrapper: Equatable, Hashable { let type: Statistic.Type + var toString: String { + String(describing: type) + } + static func == (lhs: Self, rhs: Self) -> Bool { lhs.type == rhs.type } @@ -20,6 +24,7 @@ struct StatisticTypeWrapper: Equatable, Hashable { func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(type)) } + } /// This extension allows the wrapped type to be written to and read from file diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift index 54f2547a..ec40df8c 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift @@ -13,6 +13,7 @@ class StatisticsDatabase { init() { self.loadFromFirebase() + Logger.log("Current killcount is \(String(describing: self.statistics[TotalKillsStatistic.asType]))", self) } func addStatistic(for statName: StatisticTypeWrapper) { @@ -57,15 +58,16 @@ class StatisticsDatabase { } do { - guard let statisticType = NSClassFromString(key) as? Statistic.Type else { + guard let statisticType = NSClassFromString("TowerForge.\(key)") as? Statistic.Type else { + Logger.log("NSClassFromString call failed for \(key)", self) continue } let statisticName = statisticType.asType let decoder = JSONDecoder() - + let statistic: Statistic? statistic = try self.defaultStatisticDecoder[statisticName]?(decoder, statisticData) - + if let stat = statistic { self.statistics[statisticName] = stat } @@ -80,13 +82,13 @@ class StatisticsDatabase { func saveToFirebase() { let databaseReference = FirebaseDatabaseReference(.Statistics) - var statisticsDictionary = [StatisticTypeWrapper: Any]() + var statisticsDictionary = [String: Any]() for (name, statistic) in statistics { do { let data = try JSONEncoder().encode(statistic) let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) - statisticsDictionary[name] = dictionary + statisticsDictionary[name.toString] = dictionary } catch { Logger.log("Error encoding statistic \(name): \(error)") } diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift index 87ebb396..30218372 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift @@ -51,11 +51,11 @@ class StatisticsEngine { } private func saveStatistics() { - statisticsDatabase.save() + statisticsDatabase.saveToFirebase() } private func loadStatistics() { - statisticsDatabase.load() + statisticsDatabase.loadFromFirebase() } } diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift index 19a97f5a..597bb412 100644 --- a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift +++ b/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift @@ -9,11 +9,11 @@ import Foundation class StatisticsFactory { - static let availableStatisticsTypes: [Statistic.Type] = + static let availableStatisticsTypes: [String: Statistic.Type] = [ - TotalKillsStatistic.self, - TotalGamesStatistic.self, - TotalDeathsStatistic.self + String(describing: TotalKillsStatistic.self): TotalKillsStatistic.self, + String(describing: TotalGamesStatistic.self): TotalGamesStatistic.self, + String(describing: TotalDeathsStatistic.self): TotalDeathsStatistic.self ] static func getDefaultEventLinkDatabase() -> EventStatisticLinkDatabase { @@ -24,7 +24,14 @@ class StatisticsFactory { static func getDefaultStatisticsDatabase() -> StatisticsDatabase { let statsDatabase = StatisticsDatabase() - availableStatisticsTypes.forEach { statsDatabase.addStatistic(for: $0.asType) } + availableStatisticsTypes.values.forEach { statsDatabase.addStatistic(for: $0.asType) } return statsDatabase } + + static func createInstance(of typeName: String, permanentValue: Double, currentValue: Double) -> Statistic? { + guard let type = availableStatisticsTypes[typeName] else { + return nil + } + return type.init(permanentValue: permanentValue, currentValue: currentValue) + } } From 1ba7b94d14deeb7f2363170a42093e7e9eb8bc32 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 03:02:56 +0800 Subject: [PATCH 05/54] Refactor achievements --- .../TowerForge.xcodeproj/project.pbxproj | 48 ++++++++++--------- .../Achievements/AchievementStorage.swift | 4 +- ...Achievement.swift => TFOAchievement.swift} | 4 +- .../Implemented/TotalGamesAchievement.swift | 2 +- .../Implemented/TotalKillsAchievement.swift | 2 +- .../Commons/Utilities/ObjectSet.swift | 2 +- .../Achievements/Achievement.swift} | 16 +------ .../Achievements/AchievementDelegate.swift} | 0 .../AchievementStatsLinkDatabase.swift | 2 +- .../Achievements/AchievementTypeWrapper.swift | 20 ++++++++ .../Achievements}/AchievementsEngine.swift | 0 .../EventStatisticLinkDatabase.swift | 0 .../Implemented/DefaultStatistic.swift | 0 .../Implemented/TotalDeathsStatistic.swift | 0 .../Implemented/TotalGamesStatistic.swift | 0 .../Implemented/TotalKillsStatistic.swift | 0 .../Statistics}/Statistic.swift | 0 .../Statistics}/StatisticTypeWrapper.swift | 0 .../StatisticUpdateLinkDatabase.swift | 0 .../Statistics}/StatisticsDatabase.swift | 0 .../Statistics}/StatisticsEngine.swift | 0 .../Statistics}/StatisticsFactory.swift | 0 22 files changed, 56 insertions(+), 44 deletions(-) rename TowerForge/TowerForge/Achievements/Implemented/{Achievement.swift => TFOAchievement.swift} (95%) rename TowerForge/TowerForge/{TFAchievements/TFAchievement.swift => Metrics/Achievements/Achievement.swift} (60%) rename TowerForge/TowerForge/{TFAchievements/TFAchievementDelegate.swift => Metrics/Achievements/AchievementDelegate.swift} (100%) rename TowerForge/TowerForge/{TFAchievements => Metrics/Achievements}/AchievementStatsLinkDatabase.swift (75%) create mode 100644 TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift rename TowerForge/TowerForge/{TFAchievements => Metrics/Achievements}/AchievementsEngine.swift (100%) rename TowerForge/TowerForge/{Statistics/StatisticsAPI => Metrics/Statistics}/EventStatisticLinkDatabase.swift (100%) rename TowerForge/TowerForge/{ => Metrics}/Statistics/Implemented/DefaultStatistic.swift (100%) rename TowerForge/TowerForge/{ => Metrics}/Statistics/Implemented/TotalDeathsStatistic.swift (100%) rename TowerForge/TowerForge/{ => Metrics}/Statistics/Implemented/TotalGamesStatistic.swift (100%) rename TowerForge/TowerForge/{ => Metrics}/Statistics/Implemented/TotalKillsStatistic.swift (100%) rename TowerForge/TowerForge/{Statistics/StatisticsAPI => Metrics/Statistics}/Statistic.swift (100%) rename TowerForge/TowerForge/{Statistics/StatisticsAPI => Metrics/Statistics}/StatisticTypeWrapper.swift (100%) rename TowerForge/TowerForge/{Statistics/StatisticsAPI => Metrics/Statistics}/StatisticUpdateLinkDatabase.swift (100%) rename TowerForge/TowerForge/{Statistics/StatisticsAPI => Metrics/Statistics}/StatisticsDatabase.swift (100%) rename TowerForge/TowerForge/{Statistics/StatisticsAPI => Metrics/Statistics}/StatisticsEngine.swift (100%) rename TowerForge/TowerForge/{Statistics/StatisticsAPI => Metrics/Statistics}/StatisticsFactory.swift (100%) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 5efb3ab6..0a666662 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -177,9 +177,9 @@ 9B8696552BAD759F0002377C /* Grid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8696542BAD759F0002377C /* Grid.swift */; }; 9BC60BC82BB9BE6D001A6737 /* DisabledEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BC60BC72BB9BE6D001A6737 /* DisabledEvent.swift */; }; 9BD669682BAFDE5E00DC8C4C /* GridDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */; }; - BA2F5ABE2BC80A8B00CBD8E9 /* TFAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */; }; + BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */; }; BA2F5AC12BC80BE500CBD8E9 /* Statistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */; }; - BA2F5AC32BC813F200CBD8E9 /* TFAchievementDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC22BC813F200CBD8E9 /* TFAchievementDelegate.swift */; }; + BA2F5AC32BC813F200CBD8E9 /* AchievementDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */; }; BA2F5AC52BC8143E00CBD8E9 /* TotalKillsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */; }; BA2F5AC72BC8148C00CBD8E9 /* StatisticName.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC62BC8148C00CBD8E9 /* StatisticName.swift */; }; BA2F5AC92BC81BDB00CBD8E9 /* StatisticsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */; }; @@ -203,6 +203,7 @@ BA82C7532BC8A41B000515A0 /* AchievementsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */; }; BA82C7552BC8A441000515A0 /* AchievementStatsLinkDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */; }; BA82C7582BCAB4C2000515A0 /* StatisticTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */; }; + BA82C75A2BCB0DFD000515A0 /* AchievementTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -212,7 +213,7 @@ BAFFB9572BB3449D00D8301F /* TotalKillsAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9562BB3449D00D8301F /* TotalKillsAchievement.swift */; }; BAFFB9592BB345CF00D8301F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9582BB345CF00D8301F /* Storage.swift */; }; BAFFB95D2BB978E500D8301F /* StorageEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB95C2BB978E500D8301F /* StorageEnums.swift */; }; - BAFFB9602BB9828B00D8301F /* Achievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB95F2BB9828B00D8301F /* Achievement.swift */; }; + BAFFB9602BB9828B00D8301F /* TFOAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB95F2BB9828B00D8301F /* TFOAchievement.swift */; }; BAFFB9662BB98ADC00D8301F /* AchievementStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9652BB98ADC00D8301F /* AchievementStorage.swift */; }; BAFFB9682BB9981700D8301F /* TotalGamesAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9672BB9981700D8301F /* TotalGamesAchievement.swift */; }; BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9692BB9A64000D8301F /* ObjectSet.swift */; }; @@ -415,9 +416,9 @@ 9B8696542BAD759F0002377C /* Grid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Grid.swift; sourceTree = ""; }; 9BC60BC72BB9BE6D001A6737 /* DisabledEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisabledEvent.swift; sourceTree = ""; }; 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridDelegate.swift; sourceTree = ""; }; - BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFAchievement.swift; sourceTree = ""; }; + BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Achievement.swift; sourceTree = ""; }; BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistic.swift; sourceTree = ""; }; - BA2F5AC22BC813F200CBD8E9 /* TFAchievementDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFAchievementDelegate.swift; sourceTree = ""; }; + BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementDelegate.swift; sourceTree = ""; }; BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalKillsStatistic.swift; sourceTree = ""; }; BA2F5AC62BC8148C00CBD8E9 /* StatisticName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticName.swift; sourceTree = ""; }; BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsEngine.swift; sourceTree = ""; }; @@ -441,6 +442,7 @@ BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsEngine.swift; sourceTree = ""; }; BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementStatsLinkDatabase.swift; sourceTree = ""; }; BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticTypeWrapper.swift; sourceTree = ""; }; + BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementTypeWrapper.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -451,7 +453,7 @@ BAFFB9562BB3449D00D8301F /* TotalKillsAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalKillsAchievement.swift; sourceTree = ""; }; BAFFB9582BB345CF00D8301F /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; BAFFB95C2BB978E500D8301F /* StorageEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageEnums.swift; sourceTree = ""; }; - BAFFB95F2BB9828B00D8301F /* Achievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Achievement.swift; sourceTree = ""; }; + BAFFB95F2BB9828B00D8301F /* TFOAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFOAchievement.swift; sourceTree = ""; }; BAFFB9652BB98ADC00D8301F /* AchievementStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementStorage.swift; sourceTree = ""; }; BAFFB9672BB9981700D8301F /* TotalGamesAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalGamesAchievement.swift; sourceTree = ""; }; BAFFB9692BB9A64000D8301F /* ObjectSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSet.swift; sourceTree = ""; }; @@ -831,8 +833,7 @@ BAFFB9332BB0A24400D8301F /* GameModule */, BAFFB9512BB342E200D8301F /* Storage */, BAFFB95E2BB9826800D8301F /* Achievements */, - BA2F5ABF2BC80BD200CBD8E9 /* Statistics */, - BA2F5ABC2BC80A2300CBD8E9 /* TFAchievements */, + BA2F5ABF2BC80BD200CBD8E9 /* Metrics */, ); path = TowerForge; sourceTree = ""; @@ -917,25 +918,26 @@ path = Grid; sourceTree = ""; }; - BA2F5ABC2BC80A2300CBD8E9 /* TFAchievements */ = { + BA2F5ABC2BC80A2300CBD8E9 /* Achievements */ = { isa = PBXGroup; children = ( BA82C7512BC8A3CF000515A0 /* Implemented */, - BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */, - BA2F5AC22BC813F200CBD8E9 /* TFAchievementDelegate.swift */, + BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */, + BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */, + BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */, BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */, BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */, ); - path = TFAchievements; + path = Achievements; sourceTree = ""; }; - BA2F5ABF2BC80BD200CBD8E9 /* Statistics */ = { + BA2F5ABF2BC80BD200CBD8E9 /* Metrics */ = { isa = PBXGroup; children = ( - BA82C7562BCAB482000515A0 /* StatisticsAPI */, - BA82C73E2BC85D90000515A0 /* Implemented */, + BA82C7562BCAB482000515A0 /* Statistics */, + BA2F5ABC2BC80A2300CBD8E9 /* Achievements */, ); - path = Statistics; + path = Metrics; sourceTree = ""; }; BA443D402BAD9872009F0FFB /* EventTests */ = { @@ -995,9 +997,10 @@ path = Implemented; sourceTree = ""; }; - BA82C7562BCAB482000515A0 /* StatisticsAPI */ = { + BA82C7562BCAB482000515A0 /* Statistics */ = { isa = PBXGroup; children = ( + BA82C73E2BC85D90000515A0 /* Implemented */, BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */, BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */, BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, @@ -1006,7 +1009,7 @@ BA2F5ACA2BC82CCC00CBD8E9 /* EventStatisticLinkDatabase.swift */, BA2F5ACC2BC8313900CBD8E9 /* StatisticUpdateLinkDatabase.swift */, ); - path = StatisticsAPI; + path = Statistics; sourceTree = ""; }; BAFFB9272BB09E0E00D8301F /* Storyboards */ = { @@ -1271,7 +1274,7 @@ BAFFB96F2BBA7F0100D8301F /* Implemented */ = { isa = PBXGroup; children = ( - BAFFB95F2BB9828B00D8301F /* Achievement.swift */, + BAFFB95F2BB9828B00D8301F /* TFOAchievement.swift */, BAFFB9562BB3449D00D8301F /* TotalKillsAchievement.swift */, BAFFB9672BB9981700D8301F /* TotalGamesAchievement.swift */, ); @@ -1469,7 +1472,7 @@ 5299D1432BC3AB38003EF746 /* GameRankProvider.swift in Sources */, 5250B42F2BAE0DB000F16CF6 /* LabelComponent.swift in Sources */, 3CCF9CB32BAB1F42004D170E /* SystemManager.swift in Sources */, - BAFFB9602BB9828B00D8301F /* Achievement.swift in Sources */, + BAFFB9602BB9828B00D8301F /* TFOAchievement.swift in Sources */, 5295A20F2BAAE7CF005018A8 /* TeamController.swift in Sources */, 9B04060D2BB875740026E903 /* EventTransformation.swift in Sources */, 3C9955A52BA47DC600D33FA5 /* BaseProjectile.swift in Sources */, @@ -1494,6 +1497,7 @@ 5240D0A52BB332FB004F1486 /* GameProp.swift in Sources */, BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */, 5200624E2BA8D597000DBA30 /* AiComponent.swift in Sources */, + BA82C75A2BCB0DFD000515A0 /* AchievementTypeWrapper.swift in Sources */, 3C9955C22BA5838900D33FA5 /* EventOutput.swift in Sources */, BAFFB96C2BB9AB2400D8301F /* LocalDatabase.swift in Sources */, 523E5C4C2BC53F70007444DA /* WaveSpawnEvent.swift in Sources */, @@ -1561,14 +1565,14 @@ 3C9955AD2BA483B100D33FA5 /* TFSystem.swift in Sources */, 3CAC4A672BB6975200A5D22E /* RenderStage.swift in Sources */, 3C9955BE2BA57E4B00D33FA5 /* EventManager.swift in Sources */, - BA2F5AC32BC813F200CBD8E9 /* TFAchievementDelegate.swift in Sources */, + BA2F5AC32BC813F200CBD8E9 /* AchievementDelegate.swift in Sources */, BAFFB9852BBDBA7D00D8301F /* MediaEnums.swift in Sources */, 527A07842BB3FD9A00CD9D08 /* TimerSystem.swift in Sources */, BA82C7462BC8797F000515A0 /* StatisticsDatabase.swift in Sources */, 3C3CBDF72BB81D970001B8A9 /* TFCameraNode.swift in Sources */, 3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */, BA82C7422BC86FE1000515A0 /* TotalGamesStatistic.swift in Sources */, - BA2F5ABE2BC80A8B00CBD8E9 /* TFAchievement.swift in Sources */, + BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */, 3CBECF8C2BBE9A41005EF39B /* TFRemoteEvent.swift in Sources */, 5240D0A92BB333B5004F1486 /* PointProp.swift in Sources */, 52DF5FE62BA33AF300135367 /* TFSpriteNode.swift in Sources */, diff --git a/TowerForge/TowerForge/Achievements/AchievementStorage.swift b/TowerForge/TowerForge/Achievements/AchievementStorage.swift index 248b9e93..c7a60530 100644 --- a/TowerForge/TowerForge/Achievements/AchievementStorage.swift +++ b/TowerForge/TowerForge/Achievements/AchievementStorage.swift @@ -20,12 +20,12 @@ final class AchievementStorage: Storage { } /// Adds storable if it doesn't exists and updates it if it does - func addStorable(_ storable: Achievement) { + func addStorable(_ storable: TFOAchievement) { storedObjects[storable.storableName] = storable } /// Removes a storable value if it exists - func removeStorable(_ storable: Achievement) { + func removeStorable(_ storable: TFOAchievement) { storedObjects.removeValue(forKey: storable.storableName) } } diff --git a/TowerForge/TowerForge/Achievements/Implemented/Achievement.swift b/TowerForge/TowerForge/Achievements/Implemented/TFOAchievement.swift similarity index 95% rename from TowerForge/TowerForge/Achievements/Implemented/Achievement.swift rename to TowerForge/TowerForge/Achievements/Implemented/TFOAchievement.swift index 959767fd..8fe06699 100644 --- a/TowerForge/TowerForge/Achievements/Implemented/Achievement.swift +++ b/TowerForge/TowerForge/Achievements/Implemented/TFOAchievement.swift @@ -12,11 +12,11 @@ import Foundation /// /// TODO: Replace current storable with generic storable that can /// support Achievement -protocol Achievement: Storable { +protocol TFOAchievement: Storable { } -extension Achievement { +extension TFOAchievement { func encode(to encoder: any Encoder) throws { Logger.log("Storable Default encode function called", (any Storable).self) var container = encoder.container(keyedBy: StorageEnums.StorableDefaultCodingKeys.self) diff --git a/TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift b/TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift index 524ea2b2..64022dcf 100644 --- a/TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift +++ b/TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift @@ -8,7 +8,7 @@ import Foundation /// A sample achievement that keeps track of total games started. -class TotalGamesAchievement: Achievement { +class TotalGamesAchievement: TFOAchievement { var storableId = UUID() var storableName: TFStorableType = .totalGamesAchievement var storableValue: Double = 0 diff --git a/TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift b/TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift index 79adeca1..960bbb30 100644 --- a/TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift +++ b/TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift @@ -11,7 +11,7 @@ import Foundation /// The Achievement encapsulates both the name of the Achievement and the value /// corresponding to that achievement. This can be further isolated into a Trackable /// type that purely only tracks game statistic if needed later on. -class TotalKillsAchievement: Achievement { +class TotalKillsAchievement: TFOAchievement { var storableId = UUID() var storableName: TFStorableType = .totalKillsAchievement var storableValue: Double = 0 diff --git a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift index e57fdbc5..3ab816b8 100644 --- a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift +++ b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift @@ -37,7 +37,7 @@ class ObjectSet { .achievementStorage: { storage in AchievementStorage(objects: storage.storedObjects) } ] - static var defaultAchievementCreation: [TFAchievementType: () -> any Achievement] = [ + static var defaultAchievementCreation: [TFAchievementType: () -> any TFOAchievement] = [ .totalKillsAchievement: { TotalKillsAchievement() }, .totalGamesAchievement: { TotalGamesAchievement() } ] diff --git a/TowerForge/TowerForge/TFAchievements/TFAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift similarity index 60% rename from TowerForge/TowerForge/TFAchievements/TFAchievement.swift rename to TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index 3d2b1237..0d6c692a 100644 --- a/TowerForge/TowerForge/TFAchievements/TFAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -7,21 +7,9 @@ import Foundation -struct TFAchievementTypeWrapper: Equatable, Hashable { - let type: TFAchievement.Type - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.type == rhs.type - } - - func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(type)) - } -} - /// The TFAchievement protocol specifies the requirements for all concrete /// achievements to conform to. -protocol TFAchievement { +protocol Achievement { var achievementName: String { get } var achievementDescription: String { get } @@ -33,6 +21,6 @@ protocol TFAchievement { } -extension TFAchievement { +extension Achievement { } diff --git a/TowerForge/TowerForge/TFAchievements/TFAchievementDelegate.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementDelegate.swift similarity index 100% rename from TowerForge/TowerForge/TFAchievements/TFAchievementDelegate.swift rename to TowerForge/TowerForge/Metrics/Achievements/AchievementDelegate.swift diff --git a/TowerForge/TowerForge/TFAchievements/AchievementStatsLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementStatsLinkDatabase.swift similarity index 75% rename from TowerForge/TowerForge/TFAchievements/AchievementStatsLinkDatabase.swift rename to TowerForge/TowerForge/Metrics/Achievements/AchievementStatsLinkDatabase.swift index e1aad6cc..2c11064b 100644 --- a/TowerForge/TowerForge/TFAchievements/AchievementStatsLinkDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementStatsLinkDatabase.swift @@ -9,5 +9,5 @@ import Foundation /// All achievements must be linked to a specific statistic class AchievementStatsLinkDatabase { - var achievementLinks: [TFAchievementTypeWrapper: [Statistic]] = [:] + var achievementLinks: [AchievementTypeWrapper: [Statistic]] = [:] } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift new file mode 100644 index 00000000..8788cb1a --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift @@ -0,0 +1,20 @@ +// +// AchievementTypeWrapper.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +struct AchievementTypeWrapper: Equatable, Hashable { + let type: Achievement.Type + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.type == rhs.type + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(type)) + } +} diff --git a/TowerForge/TowerForge/TFAchievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift similarity index 100% rename from TowerForge/TowerForge/TFAchievements/AchievementsEngine.swift rename to TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/EventStatisticLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/EventStatisticLinkDatabase.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsAPI/EventStatisticLinkDatabase.swift rename to TowerForge/TowerForge/Metrics/Statistics/EventStatisticLinkDatabase.swift diff --git a/TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/DefaultStatistic.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/Implemented/DefaultStatistic.swift rename to TowerForge/TowerForge/Metrics/Statistics/Implemented/DefaultStatistic.swift diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/Implemented/TotalDeathsStatistic.swift rename to TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/Implemented/TotalGamesStatistic.swift rename to TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift diff --git a/TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/Implemented/TotalKillsStatistic.swift rename to TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsAPI/Statistic.swift rename to TowerForge/TowerForge/Metrics/Statistics/Statistic.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticTypeWrapper.swift rename to TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticUpdateLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticUpdateLinkDatabase.swift rename to TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsDatabase.swift rename to TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsEngine.swift rename to TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift diff --git a/TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift similarity index 100% rename from TowerForge/TowerForge/Statistics/StatisticsAPI/StatisticsFactory.swift rename to TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift From ff589914ac288e002b3ea12c36993dc8fdfead1f Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 03:05:53 +0800 Subject: [PATCH 06/54] Remove old achievement api files --- .../TowerForge.xcodeproj/project.pbxproj | 36 ------------ .../Achievements/AchievementManager.swift | 57 ------------------- .../Achievements/AchievementStorage.swift | 31 ---------- .../Implemented/TFOAchievement.swift | 37 ------------ .../Implemented/TotalGamesAchievement.swift | 40 ------------- .../Implemented/TotalKillsAchievement.swift | 43 -------------- 6 files changed, 244 deletions(-) delete mode 100644 TowerForge/TowerForge/Achievements/AchievementManager.swift delete mode 100644 TowerForge/TowerForge/Achievements/AchievementStorage.swift delete mode 100644 TowerForge/TowerForge/Achievements/Implemented/TFOAchievement.swift delete mode 100644 TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift delete mode 100644 TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 0a666662..0e8ab04a 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -210,16 +210,11 @@ BAFFB9502BB12F9D00D8301F /* GameEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94F2BB12F9D00D8301F /* GameEngineTests.swift */; }; BAFFB9532BB342F100D8301F /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9522BB342F100D8301F /* StorageManager.swift */; }; BAFFB9552BB342FE00D8301F /* Storable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9542BB342FE00D8301F /* Storable.swift */; }; - BAFFB9572BB3449D00D8301F /* TotalKillsAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9562BB3449D00D8301F /* TotalKillsAchievement.swift */; }; BAFFB9592BB345CF00D8301F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9582BB345CF00D8301F /* Storage.swift */; }; BAFFB95D2BB978E500D8301F /* StorageEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB95C2BB978E500D8301F /* StorageEnums.swift */; }; - BAFFB9602BB9828B00D8301F /* TFOAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB95F2BB9828B00D8301F /* TFOAchievement.swift */; }; - BAFFB9662BB98ADC00D8301F /* AchievementStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9652BB98ADC00D8301F /* AchievementStorage.swift */; }; - BAFFB9682BB9981700D8301F /* TotalGamesAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9672BB9981700D8301F /* TotalGamesAchievement.swift */; }; BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9692BB9A64000D8301F /* ObjectSet.swift */; }; BAFFB96C2BB9AB2400D8301F /* LocalDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB96B2BB9AB2400D8301F /* LocalDatabase.swift */; }; BAFFB9712BBA830F00D8301F /* StorageManager+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9702BBA830F00D8301F /* StorageManager+Operations.swift */; }; - BAFFB9732BBAC14500D8301F /* AchievementManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9722BBAC14500D8301F /* AchievementManager.swift */; }; BAFFB9752BBD833400D8301F /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9742BBD833400D8301F /* AudioManager.swift */; }; BAFFB97C2BBD83B200D8301F /* beep.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9762BBD83B200D8301F /* beep.mp3 */; }; BAFFB97D2BBD83B200D8301F /* hit-sound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9772BBD83B200D8301F /* hit-sound.mp3 */; }; @@ -450,16 +445,11 @@ BAFFB94F2BB12F9D00D8301F /* GameEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameEngineTests.swift; sourceTree = ""; }; BAFFB9522BB342F100D8301F /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = ""; }; BAFFB9542BB342FE00D8301F /* Storable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storable.swift; sourceTree = ""; }; - BAFFB9562BB3449D00D8301F /* TotalKillsAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalKillsAchievement.swift; sourceTree = ""; }; BAFFB9582BB345CF00D8301F /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; BAFFB95C2BB978E500D8301F /* StorageEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageEnums.swift; sourceTree = ""; }; - BAFFB95F2BB9828B00D8301F /* TFOAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFOAchievement.swift; sourceTree = ""; }; - BAFFB9652BB98ADC00D8301F /* AchievementStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementStorage.swift; sourceTree = ""; }; - BAFFB9672BB9981700D8301F /* TotalGamesAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalGamesAchievement.swift; sourceTree = ""; }; BAFFB9692BB9A64000D8301F /* ObjectSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSet.swift; sourceTree = ""; }; BAFFB96B2BB9AB2400D8301F /* LocalDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalDatabase.swift; sourceTree = ""; }; BAFFB9702BBA830F00D8301F /* StorageManager+Operations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageManager+Operations.swift"; sourceTree = ""; }; - BAFFB9722BBAC14500D8301F /* AchievementManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementManager.swift; sourceTree = ""; }; BAFFB9742BBD833400D8301F /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = ""; }; BAFFB9762BBD83B200D8301F /* beep.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = beep.mp3; sourceTree = ""; }; BAFFB9772BBD83B200D8301F /* hit-sound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "hit-sound.mp3"; sourceTree = ""; }; @@ -832,7 +822,6 @@ 52DF5FDB2BA32CEF00135367 /* LevelModule */, BAFFB9332BB0A24400D8301F /* GameModule */, BAFFB9512BB342E200D8301F /* Storage */, - BAFFB95E2BB9826800D8301F /* Achievements */, BA2F5ABF2BC80BD200CBD8E9 /* Metrics */, ); path = TowerForge; @@ -1261,26 +1250,6 @@ path = Storage; sourceTree = ""; }; - BAFFB95E2BB9826800D8301F /* Achievements */ = { - isa = PBXGroup; - children = ( - BAFFB96F2BBA7F0100D8301F /* Implemented */, - BAFFB9652BB98ADC00D8301F /* AchievementStorage.swift */, - BAFFB9722BBAC14500D8301F /* AchievementManager.swift */, - ); - path = Achievements; - sourceTree = ""; - }; - BAFFB96F2BBA7F0100D8301F /* Implemented */ = { - isa = PBXGroup; - children = ( - BAFFB95F2BB9828B00D8301F /* TFOAchievement.swift */, - BAFFB9562BB3449D00D8301F /* TotalKillsAchievement.swift */, - BAFFB9672BB9981700D8301F /* TotalGamesAchievement.swift */, - ); - path = Implemented; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1472,7 +1441,6 @@ 5299D1432BC3AB38003EF746 /* GameRankProvider.swift in Sources */, 5250B42F2BAE0DB000F16CF6 /* LabelComponent.swift in Sources */, 3CCF9CB32BAB1F42004D170E /* SystemManager.swift in Sources */, - BAFFB9602BB9828B00D8301F /* TFOAchievement.swift in Sources */, 5295A20F2BAAE7CF005018A8 /* TeamController.swift in Sources */, 9B04060D2BB875740026E903 /* EventTransformation.swift in Sources */, 3C9955A52BA47DC600D33FA5 /* BaseProjectile.swift in Sources */, @@ -1480,9 +1448,7 @@ 52578B8C2BA627B200B4D76C /* Team.swift in Sources */, BAFFB9532BB342F100D8301F /* StorageManager.swift in Sources */, 52DD8F992BC52F8500D96BAB /* LevelPopupViewController.swift in Sources */, - BAFFB9682BB9981700D8301F /* TotalGamesAchievement.swift in Sources */, BA82C7532BC8A41B000515A0 /* AchievementsEngine.swift in Sources */, - BAFFB9732BBAC14500D8301F /* AchievementManager.swift in Sources */, 3CBECF8E2BBE9EAC005EF39B /* RemoteSpawnEvent.swift in Sources */, 3CAC4A752BB6BDD500A5D22E /* HealthRenderStage.swift in Sources */, 3CCF9CB72BAB2877004D170E /* MainMenuViewController.swift in Sources */, @@ -1597,7 +1563,6 @@ 3CA829C62BB719A500D8E72A /* ButtonRenderStage.swift in Sources */, 3CE9514F2BAC8936008B2785 /* TFRenderer.swift in Sources */, 527A07802BB3F81C00CD9D08 /* TimerComponent.swift in Sources */, - BAFFB9662BB98ADC00D8301F /* AchievementStorage.swift in Sources */, 3C769A722BA58DE700F454F9 /* MovementSystem.swift in Sources */, 52A794062BBC32A10083C976 /* FirebaseRepositoryProtocol.swift in Sources */, BA82C7482BC87DB9000515A0 /* DefaultStatistic.swift in Sources */, @@ -1612,7 +1577,6 @@ BAFFB95D2BB978E500D8301F /* StorageEnums.swift in Sources */, 52DF5FED2BA34D0300135367 /* TFComponent.swift in Sources */, 527E3A242BA613F000FE1628 /* PlayerComponent.swift in Sources */, - BAFFB9572BB3449D00D8301F /* TotalKillsAchievement.swift in Sources */, 3C9955C52BA585DD00D33FA5 /* HealthSystem.swift in Sources */, 5240D0A02BB330B5004F1486 /* GameMode.swift in Sources */, 520062562BA8E026000DBA30 /* PlayerSpawnable.swift in Sources */, diff --git a/TowerForge/TowerForge/Achievements/AchievementManager.swift b/TowerForge/TowerForge/Achievements/AchievementManager.swift deleted file mode 100644 index 59a68406..00000000 --- a/TowerForge/TowerForge/Achievements/AchievementManager.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// AchievementManager.swift -// TowerForge -// -// Created by Rubesh on 1/4/24. -// - -import Foundation - -/// Utility class to encapsulate all the methods required for storing, loading and updating -/// achievements and their local storage. -class AchievementManager { - - /// TODO: Standardize this to make it more easily expandable to more achievements, maybe with ObjectSet also? - static func incrementTotalGamesStarted() { - guard let achievementStorage = StorageManager - .getStorage(for: .achievementStorage) as? AchievementStorage else { - - return - } - - // Retrieve the specific TotalGamesAchievement - if let totalGamesAchievement = achievementStorage.storedObjects - .first(where: { $0.value is TotalGamesAchievement })? - .value as? TotalGamesAchievement { - - totalGamesAchievement.incrementGameCount() - StorageManager.shared.saveToFile() - } else { - let newAchievement = TotalGamesAchievement() // Create if total games achievement doesn't already exist - newAchievement.incrementGameCount() - achievementStorage.storedObjects[newAchievement.storableName] = newAchievement - StorageManager.shared.saveToFile() - } - } - - static func incrementTotalKillCount() { - guard let achievementStorage = StorageManager - .getStorage(for: .achievementStorage) as? AchievementStorage else { - return - } - - // Retrieve the specific total kill count achievement - if let killAchievement = achievementStorage.storedObjects - .first(where: { $0.value is TotalKillsAchievement })? - .value as? TotalKillsAchievement { - - killAchievement.incrementKillCount() - StorageManager.shared.saveToFile() - } else { - let newAchievement = TotalKillsAchievement() // Create if total kills achievement doesn't already exist - newAchievement.incrementKillCount() - achievementStorage.storedObjects[newAchievement.storableName] = newAchievement - StorageManager.shared.saveToFile() - } - } -} diff --git a/TowerForge/TowerForge/Achievements/AchievementStorage.swift b/TowerForge/TowerForge/Achievements/AchievementStorage.swift deleted file mode 100644 index c7a60530..00000000 --- a/TowerForge/TowerForge/Achievements/AchievementStorage.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// AchievementStorage.swift -// TowerForge -// -// Created by Rubesh on 31/3/24. -// - -import Foundation - -/// A class to encapsulate the storing of Achievements -final class AchievementStorage: Storage { - - override init(storageName: StorageEnums.StorageType = .achievementStorage, - objects: [TFStorableType: any Storable] = [:]) { - super.init(storageName: storageName, objects: objects) - } - - required init(from decoder: any Decoder) throws { - try super.init(from: decoder) - } - - /// Adds storable if it doesn't exists and updates it if it does - func addStorable(_ storable: TFOAchievement) { - storedObjects[storable.storableName] = storable - } - - /// Removes a storable value if it exists - func removeStorable(_ storable: TFOAchievement) { - storedObjects.removeValue(forKey: storable.storableName) - } -} diff --git a/TowerForge/TowerForge/Achievements/Implemented/TFOAchievement.swift b/TowerForge/TowerForge/Achievements/Implemented/TFOAchievement.swift deleted file mode 100644 index 8fe06699..00000000 --- a/TowerForge/TowerForge/Achievements/Implemented/TFOAchievement.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Achievement.swift -// TowerForge -// -// Created by Rubesh on 31/3/24. -// - -import Foundation - -/// Achievement protocol that conforms to Storable and can be stored within -/// Storage. -/// -/// TODO: Replace current storable with generic storable that can -/// support Achievement -protocol TFOAchievement: Storable { - -} - -extension TFOAchievement { - func encode(to encoder: any Encoder) throws { - Logger.log("Storable Default encode function called", (any Storable).self) - var container = encoder.container(keyedBy: StorageEnums.StorableDefaultCodingKeys.self) - try container.encode(storableId, forKey: .storableId) - try container.encode(storableName, forKey: .storableName) - try container.encode(storableValue, forKey: .storableValue) - } - - init(from decoder: any Decoder) throws { - Logger.log("Storable default decoder init called", (any Storable).self) - let container = try decoder.container(keyedBy: StorageEnums.StorableDefaultCodingKeys.self) - let id = try container.decode(UUID.self, forKey: .storableId) - let name = try container.decode(TFStorableType.self, forKey: .storableName) - let value = try container.decode(Double.self, forKey: .storableValue) - - self.init(id: id, name: name, value: value) - } -} diff --git a/TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift b/TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift deleted file mode 100644 index 64022dcf..00000000 --- a/TowerForge/TowerForge/Achievements/Implemented/TotalGamesAchievement.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// SampleAchievement.swift -// TowerForge -// -// Created by Rubesh on 31/3/24. -// - -import Foundation - -/// A sample achievement that keeps track of total games started. -class TotalGamesAchievement: TFOAchievement { - var storableId = UUID() - var storableName: TFStorableType = .totalGamesAchievement - var storableValue: Double = 0 - - var gameCount: Int { - get { Int(storableValue) } - set { storableValue = Double(newValue) } - } - - required init(id: UUID = UUID(), - name: TFStorableType = .totalGamesAchievement, - value: Double = 0) { - self.storableId = id - self.storableName = name - self.storableValue = value - } - - private func updateGameCount(to count: Int) { - gameCount = count - } - - func incrementGameCount() { - gameCount += 1 - } - - func decrementGameCount() { - gameCount -= 1 - } -} diff --git a/TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift b/TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift deleted file mode 100644 index 960bbb30..00000000 --- a/TowerForge/TowerForge/Achievements/Implemented/TotalKillsAchievement.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// TotalKillsAchievement.swift -// TowerForge -// -// Created by Rubesh on 27/3/24. -// - -import Foundation - -/// A sample achievement that keeps track of kill counts across game instances. -/// The Achievement encapsulates both the name of the Achievement and the value -/// corresponding to that achievement. This can be further isolated into a Trackable -/// type that purely only tracks game statistic if needed later on. -class TotalKillsAchievement: TFOAchievement { - var storableId = UUID() - var storableName: TFStorableType = .totalKillsAchievement - var storableValue: Double = 0 - - var killCount: Int { - get { Int(storableValue) } - set { storableValue = Double(newValue) } - } - - required init(id: UUID = UUID(), - name: TFStorableType = .totalKillsAchievement, - value: Double = 0) { - self.storableId = id - self.storableName = name - self.storableValue = value - } - - private func updateKillCount(to count: Int) { - killCount = count - } - - func incrementKillCount() { - killCount += 1 - } - - func decrementKillCount() { - killCount -= 1 - } -} From 664ecccb357247de6390b0448c4dfb94657d9bb6 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 03:14:24 +0800 Subject: [PATCH 07/54] Remove old storage files --- .../TowerForge.xcodeproj/project.pbxproj | 28 +--- .../AppMain/Application/AppDelegate.swift | 2 +- .../Commons/Utilities/ObjectSet.swift | 15 --- ...{Achievement.swift => TFAchievement.swift} | 0 .../TowerForge/Storage/LocalDatabase.swift | 46 ------- TowerForge/TowerForge/Storage/Storable.swift | 56 -------- TowerForge/TowerForge/Storage/Storage.swift | 84 ------------ .../Storage/StorageManager+Operations.swift | 34 ----- .../TowerForge/Storage/StorageManager.swift | 121 ------------------ 9 files changed, 5 insertions(+), 381 deletions(-) rename TowerForge/TowerForge/Metrics/Achievements/{Achievement.swift => TFAchievement.swift} (100%) delete mode 100644 TowerForge/TowerForge/Storage/LocalDatabase.swift delete mode 100644 TowerForge/TowerForge/Storage/Storable.swift delete mode 100644 TowerForge/TowerForge/Storage/Storage.swift delete mode 100644 TowerForge/TowerForge/Storage/StorageManager+Operations.swift delete mode 100644 TowerForge/TowerForge/Storage/StorageManager.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 0e8ab04a..2659b635 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -177,7 +177,7 @@ 9B8696552BAD759F0002377C /* Grid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8696542BAD759F0002377C /* Grid.swift */; }; 9BC60BC82BB9BE6D001A6737 /* DisabledEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BC60BC72BB9BE6D001A6737 /* DisabledEvent.swift */; }; 9BD669682BAFDE5E00DC8C4C /* GridDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */; }; - BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */; }; + BA2F5ABE2BC80A8B00CBD8E9 /* TFAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */; }; BA2F5AC12BC80BE500CBD8E9 /* Statistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */; }; BA2F5AC32BC813F200CBD8E9 /* AchievementDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */; }; BA2F5AC52BC8143E00CBD8E9 /* TotalKillsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */; }; @@ -208,13 +208,8 @@ BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; BAFFB9502BB12F9D00D8301F /* GameEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94F2BB12F9D00D8301F /* GameEngineTests.swift */; }; - BAFFB9532BB342F100D8301F /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9522BB342F100D8301F /* StorageManager.swift */; }; - BAFFB9552BB342FE00D8301F /* Storable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9542BB342FE00D8301F /* Storable.swift */; }; - BAFFB9592BB345CF00D8301F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9582BB345CF00D8301F /* Storage.swift */; }; BAFFB95D2BB978E500D8301F /* StorageEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB95C2BB978E500D8301F /* StorageEnums.swift */; }; BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9692BB9A64000D8301F /* ObjectSet.swift */; }; - BAFFB96C2BB9AB2400D8301F /* LocalDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB96B2BB9AB2400D8301F /* LocalDatabase.swift */; }; - BAFFB9712BBA830F00D8301F /* StorageManager+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9702BBA830F00D8301F /* StorageManager+Operations.swift */; }; BAFFB9752BBD833400D8301F /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9742BBD833400D8301F /* AudioManager.swift */; }; BAFFB97C2BBD83B200D8301F /* beep.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9762BBD83B200D8301F /* beep.mp3 */; }; BAFFB97D2BBD83B200D8301F /* hit-sound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9772BBD83B200D8301F /* hit-sound.mp3 */; }; @@ -411,7 +406,7 @@ 9B8696542BAD759F0002377C /* Grid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Grid.swift; sourceTree = ""; }; 9BC60BC72BB9BE6D001A6737 /* DisabledEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisabledEvent.swift; sourceTree = ""; }; 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridDelegate.swift; sourceTree = ""; }; - BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Achievement.swift; sourceTree = ""; }; + BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFAchievement.swift; sourceTree = ""; }; BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistic.swift; sourceTree = ""; }; BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementDelegate.swift; sourceTree = ""; }; BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalKillsStatistic.swift; sourceTree = ""; }; @@ -443,13 +438,8 @@ BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; BAFFB94A2BB11F9800D8301F /* GameEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameEngine.swift; sourceTree = ""; }; BAFFB94F2BB12F9D00D8301F /* GameEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameEngineTests.swift; sourceTree = ""; }; - BAFFB9522BB342F100D8301F /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = ""; }; - BAFFB9542BB342FE00D8301F /* Storable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storable.swift; sourceTree = ""; }; - BAFFB9582BB345CF00D8301F /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; BAFFB95C2BB978E500D8301F /* StorageEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageEnums.swift; sourceTree = ""; }; BAFFB9692BB9A64000D8301F /* ObjectSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSet.swift; sourceTree = ""; }; - BAFFB96B2BB9AB2400D8301F /* LocalDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalDatabase.swift; sourceTree = ""; }; - BAFFB9702BBA830F00D8301F /* StorageManager+Operations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageManager+Operations.swift"; sourceTree = ""; }; BAFFB9742BBD833400D8301F /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = ""; }; BAFFB9762BBD83B200D8301F /* beep.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = beep.mp3; sourceTree = ""; }; BAFFB9772BBD83B200D8301F /* hit-sound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "hit-sound.mp3"; sourceTree = ""; }; @@ -911,7 +901,7 @@ isa = PBXGroup; children = ( BA82C7512BC8A3CF000515A0 /* Implemented */, - BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */, + BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */, BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */, BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */, BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */, @@ -1241,11 +1231,6 @@ BAFFB9512BB342E200D8301F /* Storage */ = { isa = PBXGroup; children = ( - BAFFB9542BB342FE00D8301F /* Storable.swift */, - BAFFB9582BB345CF00D8301F /* Storage.swift */, - BAFFB96B2BB9AB2400D8301F /* LocalDatabase.swift */, - BAFFB9522BB342F100D8301F /* StorageManager.swift */, - BAFFB9702BBA830F00D8301F /* StorageManager+Operations.swift */, ); path = Storage; sourceTree = ""; @@ -1431,7 +1416,6 @@ files = ( 9B0406122BB889940026E903 /* PowerUpNode.swift in Sources */, 3CCF9CAF2BAB1A96004D170E /* SceneUpdateDelegate.swift in Sources */, - BAFFB9592BB345CF00D8301F /* Storage.swift in Sources */, BA82C74E2BC8A024000515A0 /* DeathEvent.swift in Sources */, BA82C7402BC8674A000515A0 /* StatisticsFactory.swift in Sources */, 3CAC4A692BB697A400A5D22E /* SpriteRenderStage.swift in Sources */, @@ -1444,9 +1428,7 @@ 5295A20F2BAAE7CF005018A8 /* TeamController.swift in Sources */, 9B04060D2BB875740026E903 /* EventTransformation.swift in Sources */, 3C9955A52BA47DC600D33FA5 /* BaseProjectile.swift in Sources */, - BAFFB9552BB342FE00D8301F /* Storable.swift in Sources */, 52578B8C2BA627B200B4D76C /* Team.swift in Sources */, - BAFFB9532BB342F100D8301F /* StorageManager.swift in Sources */, 52DD8F992BC52F8500D96BAB /* LevelPopupViewController.swift in Sources */, BA82C7532BC8A41B000515A0 /* AchievementsEngine.swift in Sources */, 3CBECF8E2BBE9EAC005EF39B /* RemoteSpawnEvent.swift in Sources */, @@ -1465,7 +1447,6 @@ 5200624E2BA8D597000DBA30 /* AiComponent.swift in Sources */, BA82C75A2BCB0DFD000515A0 /* AchievementTypeWrapper.swift in Sources */, 3C9955C22BA5838900D33FA5 /* EventOutput.swift in Sources */, - BAFFB96C2BB9AB2400D8301F /* LocalDatabase.swift in Sources */, 523E5C4C2BC53F70007444DA /* WaveSpawnEvent.swift in Sources */, 5240D0912BAF3453004F1486 /* Life.swift in Sources */, 52A794192BBC630F0083C976 /* RoomData.swift in Sources */, @@ -1501,7 +1482,6 @@ 52A794152BBC4EF50083C976 /* GameRoomDelegate.swift in Sources */, 52DF5FB02BA32B2300135367 /* GameViewController.swift in Sources */, 3CE951512BAC8955008B2785 /* Renderable.swift in Sources */, - BAFFB9712BBA830F00D8301F /* StorageManager+Operations.swift in Sources */, 3CAC4A712BB6AB3100A5D22E /* TFLabelNode.swift in Sources */, 529F91882BA6D7A7009551D9 /* SoldierUnit.swift in Sources */, BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */, @@ -1538,7 +1518,7 @@ 3C3CBDF72BB81D970001B8A9 /* TFCameraNode.swift in Sources */, 3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */, BA82C7422BC86FE1000515A0 /* TotalGamesStatistic.swift in Sources */, - BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */, + BA2F5ABE2BC80A8B00CBD8E9 /* TFAchievement.swift in Sources */, 3CBECF8C2BBE9A41005EF39B /* TFRemoteEvent.swift in Sources */, 5240D0A92BB333B5004F1486 /* PointProp.swift in Sources */, 52DF5FE62BA33AF300135367 /* TFSpriteNode.swift in Sources */, diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index 3f6ab869..461b4699 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Override point for customization after application launch. /// Load the local storage and data - StorageManager.initializeData() + // StorageManager.initializeData() /// Connect to Firebase FirebaseApp.configure() diff --git a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift index 3ab816b8..41d4d5ba 100644 --- a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift +++ b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift @@ -27,21 +27,6 @@ import Foundation /// be called. class ObjectSet { - /// A dictionary of available Achievement types and closures that create full instances of them - static var fullStorableCreation: [TFStorableType: (UUID, TFStorableType, Double) -> any Storable] = [ - .totalKillsAchievement: { id, type, value in TotalKillsAchievement(id: id, name: type, value: value) }, - .totalGamesAchievement: { id, type, value in TotalGamesAchievement(id: id, name: type, value: value) } - ] - - static var fullStorageCreation: [TFStorageType: (Storage) -> Storage] = [ - .achievementStorage: { storage in AchievementStorage(objects: storage.storedObjects) } - ] - - static var defaultAchievementCreation: [TFAchievementType: () -> any TFOAchievement] = [ - .totalKillsAchievement: { TotalKillsAchievement() }, - .totalGamesAchievement: { TotalGamesAchievement() } - ] - static let availableEventTypes: [TFEvent.Type] = [ DamageEvent.self, diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/TFAchievement.swift similarity index 100% rename from TowerForge/TowerForge/Metrics/Achievements/Achievement.swift rename to TowerForge/TowerForge/Metrics/Achievements/TFAchievement.swift diff --git a/TowerForge/TowerForge/Storage/LocalDatabase.swift b/TowerForge/TowerForge/Storage/LocalDatabase.swift deleted file mode 100644 index 745ca11c..00000000 --- a/TowerForge/TowerForge/Storage/LocalDatabase.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Database.swift -// TowerForge -// -// Created by Rubesh on 31/3/24. -// - -import Foundation - -/// An Abstract data type to store a collection of Storages. -final class LocalDatabase: Codable { - - var storedData: [TFStorageType: Storage] = [:] - - init(storedData: [TFStorageType: Storage] = [:]) { - self.storedData = storedData - } - - static func generateStorageCollection(_ storageObjects: [Storage]) -> [TFStorageType: Storage] { - var storagesMap: [TFStorageType: Storage] = [:] - for storage in storageObjects { - storagesMap[storage.storageName] = storage - } - return storagesMap - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: StorageEnums.DatabaseCodingKeys.self) - var objectsContainer = container.nestedUnkeyedContainer(forKey: .storedData) - try storedData.values.forEach { try objectsContainer.encode($0) } - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: StorageEnums.DatabaseCodingKeys.self) - let storagesContainer = try container.nestedContainer(keyedBy: TFStorageType.self, forKey: .storedData) - var tempStoredData: [TFStorageType: Storage] = [:] - - for key in storagesContainer.allKeys { - let storage = try storagesContainer.decode(Storage.self, forKey: key) - let storageObject = ObjectSet.fullStorageCreation[key]?(storage) - tempStoredData[key] = storageObject - } - - self.storedData = tempStoredData - } -} diff --git a/TowerForge/TowerForge/Storage/Storable.swift b/TowerForge/TowerForge/Storage/Storable.swift deleted file mode 100644 index cf57dcfb..00000000 --- a/TowerForge/TowerForge/Storage/Storable.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Storable.swift -// TowerForge -// -// Created by Rubesh on 27/3/24. -// - -import Foundation - -/// The Storable protocol adds a layer between Swift's Codable and elements -/// in TowerForge that requires storage. Each Storable has a UUID base, a -/// storableName, and a storable Value. The storable value can differ based -/// on the value that is needed to be stored. -/// -/// All storableNames must be "registered" within the StorableNames enum inside -/// the StorageEnums class, this ensures that no arbitrary items are stored and -/// the things needed to be stored in the context of TowerForge will all be -/// pre-determined before runtime. -typealias TFStorableType = StorageEnums.StorableNameType -protocol Storable: Codable { - - var storableId: UUID { get set } - var storableName: TFStorableType { get set } - var storableValue: Double { get set } - - init(id: UUID, name: TFStorableType, value: Double) -} - -/// This extension adds a default implementation for Storables that do not -/// have any concrete extensions. -/// -/// Swift automatically synthesizes encoder and decoders for standard library -/// types such as String, Double and Id. However, it cannot always be ensured -/// that the types contained within a Storable will be of that particular type. -/// -/// Thus, this explicit declaration allows us to fine tune the values to be -/// encoded and decoded. -extension Storable { - func encode(to encoder: any Encoder) throws { - Logger.log("Storable Default encode function called", (any Storable).self) - var container = encoder.container(keyedBy: StorageEnums.StorableDefaultCodingKeys.self) - try container.encode(storableId, forKey: .storableId) - try container.encode(storableName, forKey: .storableName) - try container.encode(storableValue, forKey: .storableValue) - } - - init(from decoder: any Decoder) throws { - Logger.log("Storable default decoder init called", (any Storable).self) - let container = try decoder.container(keyedBy: StorageEnums.StorableDefaultCodingKeys.self) - let id = try container.decode(UUID.self, forKey: .storableId) - let name = try container.decode(TFStorableType.self, forKey: .storableName) - let value = try container.decode(Double.self, forKey: .storableValue) - - self.init(id: id, name: name, value: value) - } -} diff --git a/TowerForge/TowerForge/Storage/Storage.swift b/TowerForge/TowerForge/Storage/Storage.swift deleted file mode 100644 index 69130306..00000000 --- a/TowerForge/TowerForge/Storage/Storage.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// Storage.swift -// TowerForge -// -// Created by Rubesh on 27/3/24. -// - -import Foundation - -/// The Storage class encapsulates a collection of unique Storables -class Storage: Codable { - - var storageName: TFStorageType - var storedObjects: [TFStorableType: Storable] - - init(storageName: TFStorageType, objects: [TFStorableType: Storable] = [:]) { - self.storageName = storageName - self.storedObjects = objects - } - - /// Adds storable if it doesn't exists and updates it if it does - func addStorable(_ storable: Storable) { - storedObjects[storable.storableName] = storable - } - - /// Removes a storable value if it exists - func removeStorable(_ storable: Storable) { - storedObjects.removeValue(forKey: storable.storableName) - } - - func getStorable(_ storableType: TFStorableType) -> Storable? { - storedObjects[storableType] - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: StorageEnums.StorageCodingKeys.self) - try container.encode(storageName, forKey: .storageName) - var objectsContainer = container.nestedUnkeyedContainer(forKey: .storedObjects) - try storedObjects.values.forEach { try objectsContainer.encode($0) } - } - - required init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: StorageEnums.StorageCodingKeys.self) - let name = try container.decode(StorageEnums.StorageType.self, forKey: .storageName) - var elementsArrayForType = try container.nestedUnkeyedContainer(forKey: .storedObjects) - var elements: [Storable] = [] - - while !elementsArrayForType.isAtEnd { - let filesDict = try elementsArrayForType - .nestedContainer(keyedBy: StorageEnums.StorableDefaultCodingKeys.self) - - if let fileElement = try AchievementStorage.decodeElement(filesDict) { - elements.append(fileElement) - } - } - - let storedObjectsMap = Self.generateStoredObjectsCollection(elements) - - self.storageName = name - self.storedObjects = storedObjectsMap - } - - private static func decodeElement(_ filesDict: KeyedDecodingContainer) - throws -> (any Storable)? { - - let id = try filesDict.decode(UUID.self, forKey: .storableId) - let storableName = try filesDict.decode(TFStorableType.self, forKey: .storableName) - let storableValue = try filesDict.decode(Double.self, forKey: .storableValue) - - let storableObject = ObjectSet.fullStorableCreation[storableName]?(id, storableName, storableValue) - return storableObject - } - - private static func generateStoredObjectsCollection(_ storedObjects: [Storable]) -> [TFStorableType: Storable] { - var storedObjectsMap: [TFStorableType: Storable] = [:] - - for storable in storedObjects { - storedObjectsMap[storable.storableName] = storable - } - - return storedObjectsMap - } - -} diff --git a/TowerForge/TowerForge/Storage/StorageManager+Operations.swift b/TowerForge/TowerForge/Storage/StorageManager+Operations.swift deleted file mode 100644 index a6d05c8d..00000000 --- a/TowerForge/TowerForge/Storage/StorageManager+Operations.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// StorageManager+Operations.swift -// TowerForge -// -// Created by Rubesh on 1/4/24. -// - -import Foundation - -/// This class contains utility methods for the StorageManager to handle -/// loading and storing of values -extension StorageManager { - static func getStorage(for type: TFStorageType) -> Storage? { - StorageManager.shared.storedDatabase.storedData[type] - } - - func initializeDefaultAchievements() { - if storedDatabase.storedData[.achievementStorage] == nil { - let achievementStorage = AchievementStorage() - - // Iterate through all achievements and add them to the achievementStorage - for achievementType in TFAchievementType.allCases { - guard let defaultAchievement = ObjectSet.defaultAchievementCreation[achievementType]?() else { - continue - } - - achievementStorage.addStorable(defaultAchievement) - } - // Store the AchievementStorage in the Database - storedDatabase.storedData[.achievementStorage] = achievementStorage - Logger.log("Default achievements initialized", self) - } - } -} diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift deleted file mode 100644 index c1ae0e3e..00000000 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// Storage.swift -// TowerForge -// -// Created by Rubesh on 27/3/24. -// - -import Foundation - -/// The Storage Architecture of TowerForge consists of a few layers. From highest -/// to lowest, these are: -/// - StorageManager: Interface that allows application wide access to data persistence -/// - Database: A data structure that underlies storage manager, essentially a collection of Storages -/// - Storage: A collection of a specific type of items that can be written to file -/// - Storable: The lowest level of storage, represents a single unit of an item that can be written to file -/// -/// This allows for a nuanced, sequential and hierarchial approach to data persistence, -/// within a monolithic Storage Architecture, and thus, without having to fragment -/// Storage across TowerForge. This also allows for Storage Manager to transform into an adaptor -/// if the need arises to replace FileManager with some other form of persistence, like CloudKit or -/// Firebase or something else. -/// -/// Hypothetical Example: local copy of the TowerForge application may contain information -/// such as a list of Achievements and a list of user preferences. This would translate -/// to a "AchievementStorage: Storage" class and "UserPrefStorage: Storage" class being -/// stored within Database inside the StorageManager and loaded upon every launch of the application. -/// "Achievement: Storable" would be stored inside AchievementStorage and "UserPreferenece: Storable" -/// would be stored within the UserPrefStorage. -/// -/// A singular, universal StorageManager that allows for simultaneously storing and isolating -/// storable items of different types. -class StorageManager { - static let folderName = Constants.STORAGE_CONTAINER_NAME - static let fileName = Constants.TF_DATABASE_NAME - - internal static let shared = StorageManager() // Singleton instance (might need to consider) - internal var storedDatabase: LocalDatabase - - init(storedData: LocalDatabase = LocalDatabase()) { - self.storedDatabase = storedData - } - - /// 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 initializeData() { - if let loadedDatabase = StorageManager.shared.loadFromFile() { - StorageManager.shared.storedDatabase = loadedDatabase - Logger.log("Loaded existing database.", Self.self) - } else { - StorageManager.shared.storedDatabase = LocalDatabase() // Create empty database - StorageManager.shared.saveToFile() // Save the new empty database - Logger.log("Created and saved a new empty database.", Self.self) - } - - StorageManager.shared.initializeDefaultAchievements() - } - - /// Saves the current Database to file - func saveToFile() { - let fileNameCombined = Self.fileName + ".json" - let encoder = JSONEncoder() - - do { - let data = try encoder.encode(storedDatabase) - let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(fileNameCombined) - try data.write(to: fileURL) - Logger.log("Saved Storage at: \(fileURL.path)") - } catch { - Logger.log("Error saving Database: \(error)") - } - } - - /// Loads a database (with the class constant folderName and fileName) from file - func loadFromFile() -> LocalDatabase? { - 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(LocalDatabase.self, from: data) - } catch { - Logger.log("Error loading Storage: \(error)") - return nil - } - } - - /// Deletes the stored Database from file - func deleteDatabaseFromFile() { - 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)") - } - Logger.log("Database successfully deleted.") - } - - /// 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 for a given - 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 - } -} From 66b206235cf7b2198e0b7e313d05fc7bb961e7a8 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 03:15:53 +0800 Subject: [PATCH 08/54] Update file names --- TowerForge/TowerForge.xcodeproj/project.pbxproj | 8 ++++---- .../{TFAchievement.swift => Achievement.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename TowerForge/TowerForge/Metrics/Achievements/{TFAchievement.swift => Achievement.swift} (100%) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 2659b635..028db220 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -177,7 +177,7 @@ 9B8696552BAD759F0002377C /* Grid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8696542BAD759F0002377C /* Grid.swift */; }; 9BC60BC82BB9BE6D001A6737 /* DisabledEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BC60BC72BB9BE6D001A6737 /* DisabledEvent.swift */; }; 9BD669682BAFDE5E00DC8C4C /* GridDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */; }; - BA2F5ABE2BC80A8B00CBD8E9 /* TFAchievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */; }; + BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */; }; BA2F5AC12BC80BE500CBD8E9 /* Statistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */; }; BA2F5AC32BC813F200CBD8E9 /* AchievementDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */; }; BA2F5AC52BC8143E00CBD8E9 /* TotalKillsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */; }; @@ -406,7 +406,7 @@ 9B8696542BAD759F0002377C /* Grid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Grid.swift; sourceTree = ""; }; 9BC60BC72BB9BE6D001A6737 /* DisabledEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisabledEvent.swift; sourceTree = ""; }; 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridDelegate.swift; sourceTree = ""; }; - BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFAchievement.swift; sourceTree = ""; }; + BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Achievement.swift; sourceTree = ""; }; BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistic.swift; sourceTree = ""; }; BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementDelegate.swift; sourceTree = ""; }; BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalKillsStatistic.swift; sourceTree = ""; }; @@ -901,7 +901,7 @@ isa = PBXGroup; children = ( BA82C7512BC8A3CF000515A0 /* Implemented */, - BA2F5ABD2BC80A8B00CBD8E9 /* TFAchievement.swift */, + BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */, BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */, BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */, BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */, @@ -1518,7 +1518,7 @@ 3C3CBDF72BB81D970001B8A9 /* TFCameraNode.swift in Sources */, 3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */, BA82C7422BC86FE1000515A0 /* TotalGamesStatistic.swift in Sources */, - BA2F5ABE2BC80A8B00CBD8E9 /* TFAchievement.swift in Sources */, + BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */, 3CBECF8C2BBE9A41005EF39B /* TFRemoteEvent.swift in Sources */, 5240D0A92BB333B5004F1486 /* PointProp.swift in Sources */, 52DF5FE62BA33AF300135367 /* TFSpriteNode.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Achievements/TFAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift similarity index 100% rename from TowerForge/TowerForge/Metrics/Achievements/TFAchievement.swift rename to TowerForge/TowerForge/Metrics/Achievements/Achievement.swift From 422c0c9809f6fd413e55fb99ccda2ddd730e3792 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 03:37:55 +0800 Subject: [PATCH 09/54] Add skeleton achievements implementation --- .../TowerForge.xcodeproj/project.pbxproj | 16 ++++++----- .../Metrics/Achievements/Achievement.swift | 12 ++++++--- .../Achievements/AchievementDelegate.swift | 8 ------ .../Achievements/AchievementsDatabase.swift | 17 ++++++++++++ .../Achievements/AchievementsEngine.swift | 13 ++++++++- .../TowerForge/Metrics/InferenceEngine.swift | 12 +++++++++ .../Metrics/Statistics/StatisticsEngine.swift | 27 +++++++++---------- 7 files changed, 72 insertions(+), 33 deletions(-) delete mode 100644 TowerForge/TowerForge/Metrics/Achievements/AchievementDelegate.swift create mode 100644 TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift create mode 100644 TowerForge/TowerForge/Metrics/InferenceEngine.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 028db220..4a2b355f 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -179,7 +179,6 @@ 9BD669682BAFDE5E00DC8C4C /* GridDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */; }; BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */; }; BA2F5AC12BC80BE500CBD8E9 /* Statistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */; }; - BA2F5AC32BC813F200CBD8E9 /* AchievementDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */; }; BA2F5AC52BC8143E00CBD8E9 /* TotalKillsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */; }; BA2F5AC72BC8148C00CBD8E9 /* StatisticName.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC62BC8148C00CBD8E9 /* StatisticName.swift */; }; BA2F5AC92BC81BDB00CBD8E9 /* StatisticsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */; }; @@ -204,6 +203,8 @@ BA82C7552BC8A441000515A0 /* AchievementStatsLinkDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */; }; BA82C7582BCAB4C2000515A0 /* StatisticTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */; }; BA82C75A2BCB0DFD000515A0 /* AchievementTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */; }; + BA82C75D2BCB1451000515A0 /* InferenceEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */; }; + BA82C75F2BCB1528000515A0 /* AchievementsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C75E2BCB1528000515A0 /* AchievementsDatabase.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -408,7 +409,6 @@ 9BD669672BAFDE5E00DC8C4C /* GridDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridDelegate.swift; sourceTree = ""; }; BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Achievement.swift; sourceTree = ""; }; BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistic.swift; sourceTree = ""; }; - BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementDelegate.swift; sourceTree = ""; }; BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalKillsStatistic.swift; sourceTree = ""; }; BA2F5AC62BC8148C00CBD8E9 /* StatisticName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticName.swift; sourceTree = ""; }; BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsEngine.swift; sourceTree = ""; }; @@ -433,6 +433,8 @@ BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementStatsLinkDatabase.swift; sourceTree = ""; }; BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticTypeWrapper.swift; sourceTree = ""; }; BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementTypeWrapper.swift; sourceTree = ""; }; + BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngine.swift; sourceTree = ""; }; + BA82C75E2BCB1528000515A0 /* AchievementsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsDatabase.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -903,7 +905,7 @@ BA82C7512BC8A3CF000515A0 /* Implemented */, BA2F5ABD2BC80A8B00CBD8E9 /* Achievement.swift */, BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */, - BA2F5AC22BC813F200CBD8E9 /* AchievementDelegate.swift */, + BA82C75E2BCB1528000515A0 /* AchievementsDatabase.swift */, BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */, BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */, ); @@ -915,6 +917,7 @@ children = ( BA82C7562BCAB482000515A0 /* Statistics */, BA2F5ABC2BC80A2300CBD8E9 /* Achievements */, + BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */, ); path = Metrics; sourceTree = ""; @@ -982,9 +985,9 @@ BA82C73E2BC85D90000515A0 /* Implemented */, BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */, BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */, - BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, - BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */, BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */, + BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */, + BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, BA2F5ACA2BC82CCC00CBD8E9 /* EventStatisticLinkDatabase.swift */, BA2F5ACC2BC8313900CBD8E9 /* StatisticUpdateLinkDatabase.swift */, ); @@ -1510,8 +1513,8 @@ 527A07762BB3E4CF00CD9D08 /* GameState.swift in Sources */, 3C9955AD2BA483B100D33FA5 /* TFSystem.swift in Sources */, 3CAC4A672BB6975200A5D22E /* RenderStage.swift in Sources */, + BA82C75D2BCB1451000515A0 /* InferenceEngine.swift in Sources */, 3C9955BE2BA57E4B00D33FA5 /* EventManager.swift in Sources */, - BA2F5AC32BC813F200CBD8E9 /* AchievementDelegate.swift in Sources */, BAFFB9852BBDBA7D00D8301F /* MediaEnums.swift in Sources */, 527A07842BB3FD9A00CD9D08 /* TimerSystem.swift in Sources */, BA82C7462BC8797F000515A0 /* StatisticsDatabase.swift in Sources */, @@ -1564,6 +1567,7 @@ 5240D0A22BB33183004F1486 /* DeathMatchMode.swift in Sources */, BA2F5AC52BC8143E00CBD8E9 /* TotalKillsStatistic.swift in Sources */, 3CAC4A6F2BB6A4F200A5D22E /* LabelRenderStage.swift in Sources */, + BA82C75F2BCB1528000515A0 /* AchievementsDatabase.swift in Sources */, 3CD37A9F2BBEBFFB00222D8A /* TFRemoteEventPublisher.swift in Sources */, 3C0B608D2BB2B84000FFECB4 /* ContactComponent.swift in Sources */, 3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index 0d6c692a..a1538121 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -9,15 +9,21 @@ import Foundation /// The TFAchievement protocol specifies the requirements for all concrete /// achievements to conform to. +/// +/// Each achievement will correspond to a collection of statistics. protocol Achievement { var achievementName: String { get } var achievementDescription: String { get } var isComplete: Bool { get } - var relatedStatistics: [Statistic] { get } - func loadStatistic(for stat: Statistic, by value: Int) - func updateStatistic(for stat: Statistic, by value: Int) + var requiredValues: [StatisticTypeWrapper: Double] { get } + var currentValues: [StatisticTypeWrapper: Double] { get } + + var currentProgressRates: [StatisticTypeWrapper: Double] { get } + var dependentStatistics: [StatisticTypeWrapper: Statistic] { get } + + func update() } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementDelegate.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementDelegate.swift deleted file mode 100644 index cbd9137b..00000000 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementDelegate.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// TFAchievementDelegate.swift -// TowerForge -// -// Created by Rubesh on 11/4/24. -// - -import Foundation diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift new file mode 100644 index 00000000..cb756272 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift @@ -0,0 +1,17 @@ +// +// AchievementsDatabase.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +class AchievementsDatabase { + var achievements: [AchievementTypeWrapper: Achievement] = [:] + + init(achievements: [AchievementTypeWrapper: Achievement] = [:]) { + self.achievements = achievements + } + +} diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift index bc0d6bba..0fbe1222 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift @@ -7,6 +7,17 @@ import Foundation -class AchievementsEngine { +class AchievementsEngine: InferenceEngine { + + var statisticsDatabase: StatisticsDatabase + var achievementsDatabase = AchievementsDatabase() + + init(statisticsDatabase: StatisticsDatabase) { + self.statisticsDatabase = statisticsDatabase + } + + func updateOnReceive(stats: StatisticsDatabase) { + statisticsDatabase = stats + } } diff --git a/TowerForge/TowerForge/Metrics/InferenceEngine.swift b/TowerForge/TowerForge/Metrics/InferenceEngine.swift new file mode 100644 index 00000000..682c61be --- /dev/null +++ b/TowerForge/TowerForge/Metrics/InferenceEngine.swift @@ -0,0 +1,12 @@ +// +// InferenceEngine.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +protocol InferenceEngine: AnyObject { + func updateOnReceive(stats: StatisticsDatabase) +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 30218372..cf88ff21 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -7,31 +7,17 @@ import Foundation -protocol StatisticsUpdateDelegate: AnyObject { - func updateOnReceive(_ eventType: TFEventTypeWrapper) -} - class StatisticsEngine { /// Core storage of Statistics var statisticsDatabase = StatisticsDatabase() var eventStatisticLinks = EventStatisticLinkDatabase() + var inferenceEngines: [InferenceEngine] = [] init() { self.initializeStatistics() self.setUpLinks() } - /// Main update function - func updateStatisticsOnReceive(_ event: T) { - let eventType = TFEventTypeWrapper(type: T.self) - guard let stats = eventStatisticLinks.getStatisticLinks(for: eventType) else { - return - } - - stats.forEach { $0.update(for: eventType) } - saveStatistics() - } - /// Add statistics links func setUpLinks() { eventStatisticLinks.addStatisticLink(for: KillEvent.self, @@ -50,6 +36,17 @@ class StatisticsEngine { loadStatistics() } + /// Main update function + func updateStatisticsOnReceive(_ event: T) { + let eventType = TFEventTypeWrapper(type: T.self) + guard let stats = eventStatisticLinks.getStatisticLinks(for: eventType) else { + return + } + + stats.forEach { $0.update(for: eventType) } + saveStatistics() + } + private func saveStatistics() { statisticsDatabase.saveToFirebase() } From f15dcaa153264090962ad9f0f29d67e313715113 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 15:37:03 +0800 Subject: [PATCH 10/54] Add default achievement implementation and FiftyKillsAchievement --- .../TowerForge.xcodeproj/project.pbxproj | 8 ++++ .../Commons/Protocols/Double+Extensions.swift | 37 ++++++++++++++++++ .../Metrics/Achievements/Achievement.swift | 39 +++++++++++++++++-- .../Implemented/FiftyKillsAchievement.swift | 33 ++++++++++++++++ 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift create mode 100644 TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 4a2b355f..4fca4f9e 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -205,6 +205,8 @@ BA82C75A2BCB0DFD000515A0 /* AchievementTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */; }; BA82C75D2BCB1451000515A0 /* InferenceEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */; }; 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 */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -435,6 +437,8 @@ BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementTypeWrapper.swift; sourceTree = ""; }; BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngine.swift; sourceTree = ""; }; 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/Protocols/Double+Extensions.swift"; sourceTree = SOURCE_ROOT; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -975,6 +979,7 @@ BA82C7512BC8A3CF000515A0 /* Implemented */ = { isa = PBXGroup; children = ( + BA82C7602BCBBA8A000515A0 /* FiftyKillsAchievement.swift */, ); path = Implemented; sourceTree = ""; @@ -1163,6 +1168,7 @@ children = ( 3C3CBDFE2BB8708A0001B8A9 /* CGPoint+Extensions.swift */, 3C3CBE002BB870950001B8A9 /* CGVector+Extensions.swift */, + BA82C7622BCBBB2A000515A0 /* Double+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -1453,9 +1459,11 @@ 523E5C4C2BC53F70007444DA /* WaveSpawnEvent.swift in Sources */, 5240D0912BAF3453004F1486 /* Life.swift in Sources */, 52A794192BBC630F0083C976 /* RoomData.swift in Sources */, + BA82C7632BCBBB2A000515A0 /* Double+Extensions.swift in Sources */, 52DF5FAE2BA32B2300135367 /* GameScene.swift in Sources */, 52578B822BA61AAF00B4D76C /* PositionComponent.swift in Sources */, 9BC60BC82BB9BE6D001A6737 /* DisabledEvent.swift in Sources */, + BA82C7612BCBBA8A000515A0 /* FiftyKillsAchievement.swift in Sources */, 52A794022BBC2EB30083C976 /* FirebaseReference.swift in Sources */, BA82C74A2BC88FE5000515A0 /* StatisticSystem.swift in Sources */, 3C9955A32BA47DBB00D33FA5 /* BaseUnit.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift b/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift new file mode 100644 index 00000000..cd268ddd --- /dev/null +++ b/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift @@ -0,0 +1,37 @@ +// +// Double+Extensions.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +extension Double { + static var unit: Double { 1.0 } + var half: Double { self * 0.5 } + var twice: Double { self * 2.0 } + var oneHalf: Double { self * 1.5 } + var square: Double { pow(self, 2) } + var sqroot: Double { sqrt(self) } +} + +extension Int { + static var unit: Int { 1 } + static var zero: Int { 0 } + static var negativeUnit: Int { -1 } +} + +extension CGFloat { + static var unit: Double { Double.unit } + var half: Double { Double(self).half } + var twice: Double { Double(self).twice } + var square: Double { Double(self).square } + var sqroot: Double { Double(self).sqroot } +} + +extension CGPoint { + var half: CGPoint { + CGPoint(x: self.x / 2.0, y: self.y / 2.0) + } +} diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index a1538121..7696304f 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -11,22 +11,53 @@ import Foundation /// achievements to conform to. /// /// Each achievement will correspond to a collection of statistics. -protocol Achievement { +protocol Achievement: AnyObject { var achievementName: String { get } var achievementDescription: String { get } - var isComplete: Bool { get } + var dependentStatistics: [StatisticTypeWrapper: Statistic] { get set } + var currentValues: [StatisticTypeWrapper: Double] { get } var requiredValues: [StatisticTypeWrapper: Double] { get } - var currentValues: [StatisticTypeWrapper: Double] { get } var currentProgressRates: [StatisticTypeWrapper: Double] { get } - var dependentStatistics: [StatisticTypeWrapper: Statistic] { get } + var isComplete: Bool { get } + func loadStatistic(_ stat: Statistic) func update() } extension Achievement { + + func loadStatistic(_ stat: any Statistic) { + dependentStatistics[stat.statisticName] = stat + } + + var currentValues: [StatisticTypeWrapper: Double] { + var values: [StatisticTypeWrapper: Double] = [:] + dependentStatistics.keys.forEach { key in + if let currentStatistic = dependentStatistics[key] { + values[key] = currentStatistic.permanentValue + } + } + + return values + } + + var currentProgressRates: [StatisticTypeWrapper: Double] { + var rates: [StatisticTypeWrapper: Double] = [:] + requiredValues.keys.forEach { key in + if let requiredValue = requiredValues[key], let currentValue = currentValues[key] { + rates[key] = currentValue / requiredValue + } + } + + return rates + } + + var isComplete: Bool { + currentProgressRates.values.allSatisfy { !$0.isLess(than: .unit) } + } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift new file mode 100644 index 00000000..f371789e --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -0,0 +1,33 @@ +// +// 50KillsAchievement.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +class FiftyKillsAchievement: Achievement { + + var achievementName: String = "50 Kills" + var achievementDescription: String = "Attain 50 total kills in TowerForge" + + var dependentStatistics: [StatisticTypeWrapper : any Statistic] = [:] + + var requiredValues: [StatisticTypeWrapper : Double] { + [ + TotalKillsStatistic.asType : 100.0 + ] + } + + init(dependentStatistics: [Statistic]) { + var stats: [StatisticTypeWrapper : any Statistic] + dependentStatistics.forEach { stats[$0.statisticName] = $0 } + self.dependentStatistics = stats + } + + func update() { + <#code#> + } + +} From 04b8b3b57e69aa24722067c15553c58872e6a002 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 16:04:31 +0800 Subject: [PATCH 11/54] Update achievements --- .../Commons/Protocols/Double+Extensions.swift | 1 + .../Metrics/Achievements/Achievement.swift | 12 ++++++++++- .../Implemented/FiftyKillsAchievement.swift | 20 +++++++++---------- .../Metrics/Statistics/StatisticsEngine.swift | 4 ++++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift b/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift index cd268ddd..51a0a281 100644 --- a/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift +++ b/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift @@ -14,6 +14,7 @@ extension Double { var oneHalf: Double { self * 1.5 } var square: Double { pow(self, 2) } var sqroot: Double { sqrt(self) } + func divide(by value: Double) -> Double { self / value } } extension Int { diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index 7696304f..7881075b 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -21,6 +21,7 @@ protocol Achievement: AnyObject { var requiredValues: [StatisticTypeWrapper: Double] { get } var currentProgressRates: [StatisticTypeWrapper: Double] { get } + var overallProgressRate: Double { get } var isComplete: Bool { get } func loadStatistic(_ stat: Statistic) @@ -29,7 +30,7 @@ protocol Achievement: AnyObject { } extension Achievement { - + func loadStatistic(_ stat: any Statistic) { dependentStatistics[stat.statisticName] = stat } @@ -56,6 +57,15 @@ extension Achievement { return rates } + var overallProgressRate: Double { + currentProgressRates.values.reduce(into: .zero) { $0 += $1 } + .divide(by: Double(currentProgressRates.values.count)) + } + + var overallProgressRateRounded: Double { + overallProgressRate.rounded() + } + var isComplete: Bool { currentProgressRates.values.allSatisfy { !$0.isLess(than: .unit) } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift index f371789e..57b8a9f6 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -11,23 +11,23 @@ class FiftyKillsAchievement: Achievement { var achievementName: String = "50 Kills" var achievementDescription: String = "Attain 50 total kills in TowerForge" - - var dependentStatistics: [StatisticTypeWrapper : any Statistic] = [:] - - var requiredValues: [StatisticTypeWrapper : Double] { + + var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] + + var requiredValues: [StatisticTypeWrapper: Double] { [ - TotalKillsStatistic.asType : 100.0 + TotalKillsStatistic.asType: 50.0 ] } - + init(dependentStatistics: [Statistic]) { - var stats: [StatisticTypeWrapper : any Statistic] + var stats: [StatisticTypeWrapper: any Statistic] = [:] dependentStatistics.forEach { stats[$0.statisticName] = $0 } self.dependentStatistics = stats } - + func update() { - <#code#> + } - + } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index cf88ff21..a229b75a 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -47,6 +47,10 @@ class StatisticsEngine { saveStatistics() } + func addInferenceEngine(_ engine: InferenceEngine) { + inferenceEngines.append(engine) + } + private func saveStatistics() { statisticsDatabase.saveToFirebase() } From 6aa8ad3a23a35425d04ecdd88dd99809ac31604c Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 16:16:22 +0800 Subject: [PATCH 12/54] Add basic AchievementsEngine --- .../Systems/BaseSystems/StatisticSystem.swift | 2 +- .../Metrics/Achievements/AchievementsEngine.swift | 5 +++++ .../Metrics/Statistics/StatisticsEngine.swift | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift b/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift index 7bfe39b7..e4b562a4 100644 --- a/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/BaseSystems/StatisticSystem.swift @@ -24,7 +24,7 @@ class StatisticSystem: TFSystem { } func notify(for event: T) { - statsEngine.updateStatisticsOnReceive(event) + statsEngine.update(with: event) } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift index 0fbe1222..36f3d728 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift @@ -7,6 +7,11 @@ import Foundation +/// The AchievementsEngine is an InferenceEngine that interprets permanent +/// information received from the Statistics component. +/// +/// It contains a Database of Achievements, and when notified by the StatisticsEngine, +/// will update all Achievements therein contained. class AchievementsEngine: InferenceEngine { var statisticsDatabase: StatisticsDatabase diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index a229b75a..3ad58332 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -37,7 +37,13 @@ class StatisticsEngine { } /// Main update function - func updateStatisticsOnReceive(_ event: T) { + func update(with event: T) { + self.updateStatisticsOnReceive(event) + self.notifyInferenceEngines() + } + + /// Main update function + private func updateStatisticsOnReceive(_ event: T) { let eventType = TFEventTypeWrapper(type: T.self) guard let stats = eventStatisticLinks.getStatisticLinks(for: eventType) else { return @@ -51,6 +57,13 @@ class StatisticsEngine { inferenceEngines.append(engine) } + /// 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.forEach { $0.updateOnReceive(stats: statisticsDatabase) } + } + private func saveStatistics() { statisticsDatabase.saveToFirebase() } From d4cc6fe88cb9dee884a13d0c94ecb2696f81ee5d Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 16:56:11 +0800 Subject: [PATCH 13/54] Add LocalStorageManager to load and save StatisticsDatabase objects --- .../TowerForge.xcodeproj/project.pbxproj | 8 ++ .../Commons/Constants/Constants.swift | 4 +- .../Commons/Enums/StorageEnums.swift | 20 +--- .../StatisticsDatabase+Codable.swift | 66 +++++++++++++ .../Statistics/StatisticsDatabase.swift | 9 +- .../Storage/LocalStorageManager.swift | 93 +++++++++++++++++++ 6 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift create mode 100644 TowerForge/TowerForge/Storage/LocalStorageManager.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 4fca4f9e..3c84e60f 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -207,6 +207,8 @@ 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 */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -439,6 +441,8 @@ 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/Protocols/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 = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -991,6 +995,7 @@ BA2F5AC02BC80BE500CBD8E9 /* Statistic.swift */, BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */, BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */, + BA82C7662BCBCB00000515A0 /* StatisticsDatabase+Codable.swift */, BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */, BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, BA2F5ACA2BC82CCC00CBD8E9 /* EventStatisticLinkDatabase.swift */, @@ -1240,6 +1245,7 @@ BAFFB9512BB342E200D8301F /* Storage */ = { isa = PBXGroup; children = ( + BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, ); path = Storage; sourceTree = ""; @@ -1471,6 +1477,7 @@ BA2F5AC72BC8148C00CBD8E9 /* StatisticName.swift in Sources */, 527A07782BB3F31100CD9D08 /* Death.swift in Sources */, 3C9955BA2BA5637200D33FA5 /* DamageEvent.swift in Sources */, + BA82C7672BCBCB00000515A0 /* StatisticsDatabase+Codable.swift in Sources */, 3CD37AA52BBEC10700222D8A /* FirebaseRemoteEventSubscriber.swift in Sources */, 52A794172BBC4F690083C976 /* GamePlayer.swift in Sources */, 3CE951632BAE037C008B2785 /* AiSystem.swift in Sources */, @@ -1485,6 +1492,7 @@ 3CE9514B2BAC83FA008B2785 /* SpawnableEntities.swift in Sources */, 5299D1302BC31002003EF746 /* AuthenticationProvider.swift in Sources */, 3C3CBDF92BB821500001B8A9 /* TFScene.swift in Sources */, + BA82C7652BCBC868000515A0 /* LocalStorageManager.swift in Sources */, 9B8696552BAD759F0002377C /* Grid.swift in Sources */, 527A077E2BB3F75700CD9D08 /* Timer.swift in Sources */, 52578B872BA6209700B4D76C /* DamageComponent.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Constants/Constants.swift b/TowerForge/TowerForge/Commons/Constants/Constants.swift index 6824ce59..46b8c490 100644 --- a/TowerForge/TowerForge/Commons/Constants/Constants.swift +++ b/TowerForge/TowerForge/Commons/Constants/Constants.swift @@ -12,10 +12,10 @@ class Constants { static let DATABASE_URL = "https://towerforge-d5ba7-default-rtdb.asia-southeast1.firebasedatabase.app" /// The name of the folder in which information is stored locally - static let STORAGE_CONTAINER_NAME = "TowerForge" + static let LOCAL_STORAGE_CONTAINER_NAME = "TowerForge" /// The name of the file that contains TowerForge data locally - static let TF_DATABASE_NAME = "TowerForgeDatabase" + static let LOCAL_STORAGE_FILE_NAME = "TowerForgeLocalStorage" /// Universal setting to enable or disable sound effects static var SOUND_EFFECTS_ENABLED = true diff --git a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift index ed8141cc..9c2d4c57 100644 --- a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift +++ b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift @@ -7,8 +7,9 @@ import Foundation -typealias TFStorageType = StorageEnums.StorageType typealias TFAchievementType = StorageEnums.StorableAchievementNameType +typealias StatisticsDatabaseCodingKeys = StorageEnums.StatisticsDatabaseCodingKeys +typealias StatisticsDefaultCodingKeys = StorageEnums.StatisticsDefaultCodingKeys class StorageEnums { /// An enum for the names of every Storable that can be stored. @@ -35,24 +36,13 @@ class StorageEnums { } /// Used in StorageManager class - enum StorageType: String, CodingKey, Codable { - case achievementStorage + enum StatisticsDatabaseCodingKeys: String, CodingKey, Codable { + case statistics } - /// Used in Storage class - enum StorageCodingKeys: String, CodingKey, Codable { - case storageName - case storedObjects - } - - enum DatabaseCodingKeys: String, CodingKey, Codable { - case storedData - } - - enum StatisticDefaultCodingKeys: String, CodingKey, Codable { + enum StatisticsDefaultCodingKeys: String, CodingKey, Codable { case statisticName case permanentValue case currentValue } - } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift new file mode 100644 index 00000000..3dc45812 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift @@ -0,0 +1,66 @@ +// +// StatisticsDatabase+Codable.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +extension StatisticsDatabase: Codable { + + private static func generateStatisticsCollection(_ statsArray: [Statistic]) -> [StatisticTypeWrapper: Statistic] { + var statisticsMap: [StatisticTypeWrapper: Statistic] = [:] + + for stat in statsArray { + statisticsMap[stat.statisticName] = stat + } + + return statisticsMap + } + + func encode(to encoder: Encoder) throws { + Logger.log("StatisticsDatabase encoder called") + var container = encoder.container(keyedBy: StatisticsDatabaseCodingKeys.self) + var objectsContainer = container.nestedUnkeyedContainer(forKey: .statistics) + try statistics.values.forEach { try objectsContainer.encode($0) } + } + + convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StatisticsDatabaseCodingKeys.self) + var objectsArrayForType = try container.nestedUnkeyedContainer(forKey: .statistics) + var objects: [Statistic] = [] + + while !objectsArrayForType.isAtEnd { + let statObjectDict = try objectsArrayForType.nestedContainer(keyedBy: StatisticsDefaultCodingKeys.self) + if let statObject = try Self.decodeObject(statObjectDict) { + objects.append(statObject) + } + } + + Logger.log("Loaded Statistics Database with \(objects.count)") + let statObjectsMap = Self.generateStatisticsCollection(objects) + + self.init(statObjectsMap) + } + + private static func decodeObject(_ statObjectDict: KeyedDecodingContainer) + throws -> (any Statistic)? { + + let type = try statObjectDict.decode(StatisticTypeWrapper.self, forKey: .statisticName) + let typeName = String(describing: type) + let permanentValue = try statObjectDict.decode(Double.self, forKey: .permanentValue) + let currentValue = try statObjectDict.decode(Double.self, forKey: .currentValue) + + guard let instance = StatisticsFactory.createInstance(of: typeName, + permanentValue: permanentValue, + currentValue: currentValue) else { + + throw DecodingError.dataCorruptedError(forKey: .statisticName, + in: statObjectDict, + debugDescription: "Cannot instantiate Statistic of type \(typeName)") + } + + return instance + } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift index ec40df8c..b5934594 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift @@ -8,12 +8,13 @@ import Foundation import FirebaseDatabaseInternal -class StatisticsDatabase { +final class StatisticsDatabase { var statistics: [StatisticTypeWrapper: Statistic] = [:] - init() { - self.loadFromFirebase() - Logger.log("Current killcount is \(String(describing: self.statistics[TotalKillsStatistic.asType]))", self) + init(_ stats: [StatisticTypeWrapper: Statistic] = [:]) { + self.statistics = stats + // self.loadFromFirebase() + // Logger.log("Current killcount is \(String(describing: self.statistics[TotalKillsStatistic.asType]))", self) } func addStatistic(for statName: StatisticTypeWrapper) { diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift new file mode 100644 index 00000000..9211c12d --- /dev/null +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -0,0 +1,93 @@ +// +// 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 + + /// 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 initializeLocalData() { + if let loadedLocalStorage = Self.loadDatabaseFromLocalStorage() { + Logger.log("Loaded existing database.", Self.self) + } else { + Self.saveDatabaseToLocalStorage(StatisticsFactory.getDefaultStatisticsDatabase()) + Logger.log("Created and saved a new empty database.", Self.self) + } + } + + /// Saves the input statistics database to file + static func saveDatabaseToLocalStorage(_ stats: StatisticsDatabase) { + let fileNameCombined = Self.fileName + ".json" + let encoder = JSONEncoder() + + do { + let data = try encoder.encode(stats) + let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) + let fileURL = folderURL.appendingPathComponent(fileNameCombined) + try data.write(to: fileURL) + Logger.log("Saved Statistics Database at: \(fileURL.path)", self) + } catch { + Logger.log("Error saving statistics Database: \(error)", self) + } + } + + /// 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) + } + + /// 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 + } +} From 3bc56ba4ad21c395fdfd6a231380734b47ce36ae Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 18:11:12 +0800 Subject: [PATCH 14/54] Add merge ability to StatisticsDatabase --- .../TowerForge.xcodeproj/project.pbxproj | 16 +++++ .../Commons/Constants/Constants.swift | 5 +- .../Metrics/Statistics/Statistic.swift | 18 +++++ .../Statistics/StatisticsDatabase+Merge.swift | 48 ++++++++++++++ .../Storage/LocalStorageManager.swift | 65 +++++++++++++++++-- TowerForge/TowerForge/Storage/Metadata.swift | 29 +++++++++ .../Storage/RemoteStorageManager.swift | 18 +++++ .../TowerForge/Storage/StorageManager.swift | 14 ++++ 8 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift create mode 100644 TowerForge/TowerForge/Storage/Metadata.swift create mode 100644 TowerForge/TowerForge/Storage/RemoteStorageManager.swift create mode 100644 TowerForge/TowerForge/Storage/StorageManager.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 3c84e60f..8eaaa279 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -209,6 +209,10 @@ 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 */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -443,6 +447,10 @@ BA82C7622BCBBB2A000515A0 /* Double+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Double+Extensions.swift"; path = "TowerForge/Commons/Protocols/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 = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -996,6 +1004,7 @@ BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */, BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */, BA82C7662BCBCB00000515A0 /* StatisticsDatabase+Codable.swift */, + BA82C76C2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift */, BA2F5AC82BC81BDB00CBD8E9 /* StatisticsEngine.swift */, BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, BA2F5ACA2BC82CCC00CBD8E9 /* EventStatisticLinkDatabase.swift */, @@ -1245,7 +1254,10 @@ BAFFB9512BB342E200D8301F /* Storage */ = { isa = PBXGroup; children = ( + BA82C76A2BCBD682000515A0 /* StorageManager.swift */, + BA82C76E2BCBDE91000515A0 /* Metadata.swift */, BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, + BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */, ); path = Storage; sourceTree = ""; @@ -1535,6 +1547,7 @@ 527A07842BB3FD9A00CD9D08 /* TimerSystem.swift in Sources */, BA82C7462BC8797F000515A0 /* StatisticsDatabase.swift in Sources */, 3C3CBDF72BB81D970001B8A9 /* TFCameraNode.swift in Sources */, + BA82C76D2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift in Sources */, 3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */, BA82C7422BC86FE1000515A0 /* TotalGamesStatistic.swift in Sources */, BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */, @@ -1566,9 +1579,12 @@ 52A794062BBC32A10083C976 /* FirebaseRepositoryProtocol.swift in Sources */, BA82C7482BC87DB9000515A0 /* DefaultStatistic.swift in Sources */, BA82C7442BC86FFE000515A0 /* GameStartEvent.swift in Sources */, + BA82C7692BCBD21F000515A0 /* RemoteStorageManager.swift in Sources */, 9B0406102BB879990026E903 /* InvulnerabilityPowerUp.swift in Sources */, 5299D1412BC3AA3A003EF746 /* GameRankData.swift in Sources */, 52F930E72BC63F7F003D11B5 /* LeaderboardSelectionViewController.swift in Sources */, + BA82C76F2BCBDE91000515A0 /* Metadata.swift in Sources */, + BA82C76B2BCBD682000515A0 /* StorageManager.swift in Sources */, 9BD669682BAFDE5E00DC8C4C /* GridDelegate.swift in Sources */, 52DF5FEB2BA3400C00135367 /* TFAnimatableNode.swift in Sources */, 3C3CBDFF2BB8708A0001B8A9 /* CGPoint+Extensions.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Constants/Constants.swift b/TowerForge/TowerForge/Commons/Constants/Constants.swift index 46b8c490..77da373d 100644 --- a/TowerForge/TowerForge/Commons/Constants/Constants.swift +++ b/TowerForge/TowerForge/Commons/Constants/Constants.swift @@ -15,7 +15,10 @@ class Constants { static let LOCAL_STORAGE_CONTAINER_NAME = "TowerForge" /// The name of the file that contains TowerForge data locally - static let LOCAL_STORAGE_FILE_NAME = "TowerForgeLocalStorage" + static let LOCAL_STORAGE_FILE_NAME = "TowerForgeLocalStorage.json" + + /// The name of the file that contains metadata about local storage + static let METADATA_NAME = "TowerForgeMetadata.json" /// Universal setting to enable or disable sound effects static var SOUND_EFFECTS_ENABLED = true diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index aa809ada..ace40a06 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -33,6 +33,24 @@ protocol Statistic: AnyObject, Codable { } +extension Statistic { + static func == (lhs: Self, rhs: Self) -> Bool { + (lhs.statisticName == rhs.statisticName) && + (lhs.permanentValue == rhs.permanentValue) + } + + /// Compares two Statistic objects of the same type and returns that whose permanent value is + /// larger + static func maximum(lhs: Self, rhs: Self) -> Self { + Double.maximumMagnitude(lhs.permanentValue, rhs.permanentValue) == lhs.permanentValue ? lhs : rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(statisticName) + hasher.combine(permanentValue) + } +} + /// This extension adds default utility functions such as generic increments /// and decrements of values. extension Statistic { diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift new file mode 100644 index 00000000..7fba4329 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -0,0 +1,48 @@ +// +// StatisticsDatabase+Merge.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +/// This extension adds merging abilities to the StatisticsDatabase. +/// +/// Represen +extension StatisticsDatabase { + + /// Compares two StatisticsDatabase instances and outputs a merged version + /// + /// Merge Invariants: + /// - 1. The final database must have all keys in both databases. + /// - 2. Retain the value that has the greater magnitude in the final database for duplicate keys + /// - 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(lhs: StatisticsDatabase, rhs: StatisticsDatabase) -> StatisticsDatabase { + let mergedStats = StatisticsDatabase() + + // 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 { + mergedStats.statistics[key] = rhsStat + } + // If they are equal, lhsStat is already set, so do nothing. + } else { + + // If lhs does not have the key, simply add the rhs stat. + mergedStats.statistics[key] = rhsStat + } + } + + return mergedStats + } +} diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift index 9211c12d..5cd10990 100644 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -15,11 +15,12 @@ import Foundation class LocalStorageManager { static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME static let fileName = Constants.LOCAL_STORAGE_FILE_NAME + static let metadataName = Constants.METADATA_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 initializeLocalData() { - if let loadedLocalStorage = Self.loadDatabaseFromLocalStorage() { + static func initializeLocalStatisticsDatabase() { + if Self.loadDatabaseFromLocalStorage() != nil { Logger.log("Loaded existing database.", Self.self) } else { Self.saveDatabaseToLocalStorage(StatisticsFactory.getDefaultStatisticsDatabase()) @@ -29,13 +30,12 @@ class LocalStorageManager { /// Saves the input statistics database to file static func saveDatabaseToLocalStorage(_ stats: StatisticsDatabase) { - let fileNameCombined = Self.fileName + ".json" let encoder = JSONEncoder() do { let data = try encoder.encode(stats) let folderURL = try Self.createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(fileNameCombined) + let fileURL = folderURL.appendingPathComponent(Self.fileName) try data.write(to: fileURL) Logger.log("Saved Statistics Database at: \(fileURL.path)", self) } catch { @@ -69,6 +69,58 @@ class LocalStorageManager { Logger.log("Database successfully deleted.", self) } + static func saveMetadataToLocalStorage() { + let dateFormatter = ISO8601DateFormatter() + let currentTimeString = dateFormatter.string(from: Date()) + + do { + let folderURL = try createFolderIfNeeded(folderName: folderName) + let fileURL = folderURL.appendingPathComponent(metadataName) + try currentTimeString.write(to: fileURL, atomically: true, encoding: .utf8) + Logger.log("Saved metadata at: \(fileURL.path)", self) + } catch { + Logger.log("Error saving metadata: \(error)", self) + } + } + + static func loadMetadataFromLocalStorage() -> Date? { + do { + let folderURL = try createFolderIfNeeded(folderName: folderName) + let fileURL = folderURL.appendingPathComponent(metadataName) + let dateString = try String(contentsOf: fileURL, encoding: .utf8) + let dateFormatter = ISO8601DateFormatter() + return dateFormatter.date(from: dateString) + } catch { + Logger.log("Error loading metadata: \(error)", self) + return nil + } + } + + /// Deletes the stored database from file + static func deleteMetadataFromLocalStorage() { + 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 metadata provided by iOS for a given filename in the document directory. + static func getFileMetadata(for filename: String) -> [FileAttributeKey: Any]? { + do { + let folderURL = try createFolderIfNeeded(folderName: folderName) + let fileURL = folderURL.appendingPathComponent(filename) + let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path) + return attributes + } catch { + Logger.log("Error retrieving file metadata: \(error)", self) + return nil + } + } + /// Helper function to construct a FileURL static func fileURL(for directory: FileManager.SearchPathDirectory, withName name: String) throws -> URL { let fileManager = FileManager.default @@ -90,4 +142,9 @@ class LocalStorageManager { } return folderURL } + + static func compareTimes(_ time1: Date, _ time2: Date) -> Bool { + time1 > time2 + } + } diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift new file mode 100644 index 00000000..ad6ccf52 --- /dev/null +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -0,0 +1,29 @@ +// +// Metadata.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +/// The metadata class is used to encapsulate meta-information about storage +class Metadata: Codable, Comparable, Equatable { + let lastUpdated: Date + + init(lastUpdated: Date) { + self.lastUpdated = lastUpdated + } + + static func == (lhs: Metadata, rhs: Metadata) -> Bool { + lhs.lastUpdated == rhs.lastUpdated + } + + static func < (lhs: Metadata, rhs: Metadata) -> Bool { + lhs.lastUpdated < rhs.lastUpdated + } + + static func > (lhs: Metadata, rhs: Metadata) -> Bool { + lhs.lastUpdated > rhs.lastUpdated + } +} diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift new file mode 100644 index 00000000..9683e8f4 --- /dev/null +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -0,0 +1,18 @@ +// +// 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 { + +} diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift new file mode 100644 index 00000000..35f115cc --- /dev/null +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -0,0 +1,14 @@ +// +// StorageManager.swift +// TowerForge +// +// Created by Rubesh on 14/4/24. +// + +import Foundation + +/// The class responsible for providing application wide Storage access and +/// synchronizing between +class StorageManager { + +} From 8004a000eec0211409b8b31f56fb3f218999c5d7 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 18:27:58 +0800 Subject: [PATCH 15/54] Update LocalStorageManager --- .../TowerForge.xcodeproj/project.pbxproj | 4 + .../Statistics/StatisticsDatabase+Merge.swift | 2 +- .../LocalStorageManager+Metadata.swift | 74 +++++++++++++++++++ .../Storage/LocalStorageManager.swift | 52 ------------- TowerForge/TowerForge/Storage/Metadata.swift | 3 +- 5 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 8eaaa279..b765ef7a 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -213,6 +213,7 @@ 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 */; }; + BA82C7712BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7702BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -451,6 +452,7 @@ 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 = ""; }; + BA82C7702BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorageManager+Metadata.swift"; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -1257,6 +1259,7 @@ BA82C76A2BCBD682000515A0 /* StorageManager.swift */, BA82C76E2BCBDE91000515A0 /* Metadata.swift */, BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, + BA82C7702BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift */, BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */, ); path = Storage; @@ -1499,6 +1502,7 @@ 3CD37A9D2BBEBD1600222D8A /* RemoteSpawnable.swift in Sources */, 3C9955CA2BA5888F00D33FA5 /* SpawnEvent.swift in Sources */, 3CE951582BAD724D008B2785 /* TFContact.swift in Sources */, + BA82C7712BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift in Sources */, 5295A2132BAAEA16005018A8 /* UnitNode.swift in Sources */, 52DF5FF32BA351E100135367 /* SpriteComponent.swift in Sources */, 3CE9514B2BAC83FA008B2785 /* SpawnableEntities.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index 7fba4329..9272c16e 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -42,7 +42,7 @@ extension StatisticsDatabase { mergedStats.statistics[key] = rhsStat } } - + return mergedStats } } diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift b/TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift new file mode 100644 index 00000000..491523c2 --- /dev/null +++ b/TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift @@ -0,0 +1,74 @@ +// +// LocalStorageManager+Metadata.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. +extension LocalStorageManager { + + static func saveMetadataToLocalStorage() { + let metadata = Metadata(lastUpdated: Date()) + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + + do { + let folderURL = try createFolderIfNeeded(folderName: Self.folderName) + let fileURL = folderURL.appendingPathComponent(Self.metadataName) + let data = try encoder.encode(metadata) + try data.write(to: fileURL) + Logger.log("Saved metadata at: \(fileURL.path)", self) + } catch { + Logger.log("Error saving metadata: \(error)", self) + } + } + + static func loadMetadataFromLocalStorage() -> Metadata? { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + do { + let folderURL = try createFolderIfNeeded(folderName: Self.folderName) + let fileURL = folderURL.appendingPathComponent(Self.metadataName) + let data = try Data(contentsOf: fileURL) + let metadata = try decoder.decode(Metadata.self, from: data) + return metadata + } catch { + Logger.log("Error loading metadata: \(error)", self) + return nil + } + } + + /// Deletes the stored metadata from file + static func deleteMetadataFromLocalStorage() { + do { + let folderURL = try createFolderIfNeeded(folderName: Self.folderName) + let fileURL = folderURL.appendingPathComponent(Self.metadataName) + try FileManager.default.removeItem(at: fileURL) + Logger.log("Deleted metadata at: \(fileURL.path)", self) + } catch { + Logger.log("Error deleting metadata: \(error)", self) + } + } + + /// Retrieves file metadata provided by iOS for a given filename in the document directory. + static func getFileManagerMetadata(for filename: String) -> [FileAttributeKey: Any]? { + do { + let folderURL = try createFolderIfNeeded(folderName: folderName) + let fileURL = folderURL.appendingPathComponent(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/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift index 5cd10990..c7e6dcb2 100644 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -69,58 +69,6 @@ class LocalStorageManager { Logger.log("Database successfully deleted.", self) } - static func saveMetadataToLocalStorage() { - let dateFormatter = ISO8601DateFormatter() - let currentTimeString = dateFormatter.string(from: Date()) - - do { - let folderURL = try createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(metadataName) - try currentTimeString.write(to: fileURL, atomically: true, encoding: .utf8) - Logger.log("Saved metadata at: \(fileURL.path)", self) - } catch { - Logger.log("Error saving metadata: \(error)", self) - } - } - - static func loadMetadataFromLocalStorage() -> Date? { - do { - let folderURL = try createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(metadataName) - let dateString = try String(contentsOf: fileURL, encoding: .utf8) - let dateFormatter = ISO8601DateFormatter() - return dateFormatter.date(from: dateString) - } catch { - Logger.log("Error loading metadata: \(error)", self) - return nil - } - } - - /// Deletes the stored database from file - static func deleteMetadataFromLocalStorage() { - 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 metadata provided by iOS for a given filename in the document directory. - static func getFileMetadata(for filename: String) -> [FileAttributeKey: Any]? { - do { - let folderURL = try createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(filename) - let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path) - return attributes - } catch { - Logger.log("Error retrieving file metadata: \(error)", self) - return nil - } - } - /// Helper function to construct a FileURL static func fileURL(for directory: FileManager.SearchPathDirectory, withName name: String) throws -> URL { let fileManager = FileManager.default diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index ad6ccf52..a078809f 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -7,7 +7,8 @@ import Foundation -/// The metadata class is used to encapsulate meta-information about storage +/// The metadata class is used to encapsulate meta-information about files +/// stored locally, possibly for use with conflict resolution. class Metadata: Codable, Comparable, Equatable { let lastUpdated: Date From edfaa27be55e232cbead29a72327bad337a3d92d Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 19:15:09 +0800 Subject: [PATCH 16/54] Update RemoteStorageManager with static load and store methods --- .../TowerForge.xcodeproj/project.pbxproj | 4 - .../Commons/Constants/Constants.swift | 7 ++ .../EventStatisticLinkDatabase.swift | 6 +- .../Implemented/DefaultStatistic.swift | 29 ------- .../Metrics/Statistics/Statistic.swift | 4 + .../Statistics/StatisticTypeWrapper.swift | 2 +- .../Statistics/StatisticsDatabase.swift | 86 +------------------ .../Metrics/Statistics/StatisticsEngine.swift | 18 ++-- .../Statistics/StatisticsFactory.swift | 22 ++++- .../Storage/LocalStorageManager.swift | 55 ++++++------ .../Storage/RemoteStorageManager.swift | 68 +++++++++++++++ .../TowerForge/Storage/StorageManager.swift | 11 ++- 12 files changed, 156 insertions(+), 156 deletions(-) delete mode 100644 TowerForge/TowerForge/Metrics/Statistics/Implemented/DefaultStatistic.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index b765ef7a..ed8d5334 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -195,7 +195,6 @@ BA82C7422BC86FE1000515A0 /* TotalGamesStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */; }; BA82C7442BC86FFE000515A0 /* GameStartEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7432BC86FFE000515A0 /* GameStartEvent.swift */; }; BA82C7462BC8797F000515A0 /* StatisticsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */; }; - BA82C7482BC87DB9000515A0 /* DefaultStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7472BC87DB9000515A0 /* DefaultStatistic.swift */; }; BA82C74A2BC88FE5000515A0 /* StatisticSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7492BC88FE5000515A0 /* StatisticSystem.swift */; }; BA82C74E2BC8A024000515A0 /* DeathEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C74D2BC8A024000515A0 /* DeathEvent.swift */; }; BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */; }; @@ -434,7 +433,6 @@ BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalGamesStatistic.swift; sourceTree = ""; }; BA82C7432BC86FFE000515A0 /* GameStartEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStartEvent.swift; sourceTree = ""; }; BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsDatabase.swift; sourceTree = ""; }; - BA82C7472BC87DB9000515A0 /* DefaultStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultStatistic.swift; sourceTree = ""; }; BA82C7492BC88FE5000515A0 /* StatisticSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticSystem.swift; sourceTree = ""; }; BA82C74D2BC8A024000515A0 /* DeathEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeathEvent.swift; sourceTree = ""; }; BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDeathsStatistic.swift; sourceTree = ""; }; @@ -966,7 +964,6 @@ BA82C73E2BC85D90000515A0 /* Implemented */ = { isa = PBXGroup; children = ( - BA82C7472BC87DB9000515A0 /* DefaultStatistic.swift */, BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */, BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */, BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */, @@ -1581,7 +1578,6 @@ 527A07802BB3F81C00CD9D08 /* TimerComponent.swift in Sources */, 3C769A722BA58DE700F454F9 /* MovementSystem.swift in Sources */, 52A794062BBC32A10083C976 /* FirebaseRepositoryProtocol.swift in Sources */, - BA82C7482BC87DB9000515A0 /* DefaultStatistic.swift in Sources */, BA82C7442BC86FFE000515A0 /* GameStartEvent.swift in Sources */, BA82C7692BCBD21F000515A0 /* RemoteStorageManager.swift in Sources */, 9B0406102BB879990026E903 /* InvulnerabilityPowerUp.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Constants/Constants.swift b/TowerForge/TowerForge/Commons/Constants/Constants.swift index 77da373d..c6f909cc 100644 --- a/TowerForge/TowerForge/Commons/Constants/Constants.swift +++ b/TowerForge/TowerForge/Commons/Constants/Constants.swift @@ -20,6 +20,13 @@ class Constants { /// The name of the file that contains metadata about local storage static let METADATA_NAME = "TowerForgeMetadata.json" + /// The name of the player currently logged in. + /// By default, this is set to the default id associated with the device + static var CURRENT_PLAYER_ID = "" + + /// The default id associated with the device + static var CURRENT_DEVICE_ID = "" + /// Universal setting to enable or disable sound effects static var SOUND_EFFECTS_ENABLED = true diff --git a/TowerForge/TowerForge/Metrics/Statistics/EventStatisticLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/EventStatisticLinkDatabase.swift index 7e79b043..b9698065 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/EventStatisticLinkDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/EventStatisticLinkDatabase.swift @@ -29,9 +29,11 @@ class EventStatisticLinkDatabase { } func addStatisticLink(for eventType: T.Type, - with statistic: Statistic) { + with statistic: Statistic?) { let wrappedValue = TFEventTypeWrapper(type: eventType) - eventLinks[wrappedValue]?.append(statistic) + if let stat = statistic { + eventLinks[wrappedValue]?.append(stat) + } } func getStatisticLinks(for eventType: TFEventTypeWrapper) -> [Statistic]? { diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/DefaultStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/DefaultStatistic.swift deleted file mode 100644 index 2cc3bc0e..00000000 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/DefaultStatistic.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// DefaultStatistic.swift -// TowerForge -// -// Created by Rubesh on 12/4/24. -// - -import Foundation - -final class DefaultStatistic: Statistic { - var permanentValue: Double = .zero - var currentValue: Double = .zero - - init(permanentValue: Double = .zero, - currentValue: Double = .zero) { - self.permanentValue = permanentValue - self.currentValue = currentValue - } - - func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { - let eventType = TFEventTypeWrapper(type: DisabledEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } - let eventUpdateDictionary = [eventType: updateActor] - let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) - - return statsLink - } - -} diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index ace40a06..5f84841a 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -34,6 +34,10 @@ protocol Statistic: AnyObject, Codable { } extension Statistic { + init() { + self.init(permanentValue: .zero, currentValue: .zero) + } + static func == (lhs: Self, rhs: Self) -> Bool { (lhs.statisticName == rhs.statisticName) && (lhs.permanentValue == rhs.permanentValue) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift index 7796c143..874e419b 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift @@ -13,7 +13,7 @@ import Foundation struct StatisticTypeWrapper: Equatable, Hashable { let type: Statistic.Type - var toString: String { + var asString: String { String(describing: type) } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift index b5934594..0c9ec8df 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift @@ -18,90 +18,10 @@ final class StatisticsDatabase { } func addStatistic(for statName: StatisticTypeWrapper) { - statistics[statName] = defaultStatisticGenerator[statName]?() + statistics[statName] = statName.type.init() } - func getStatistic(for statName: StatisticTypeWrapper) -> Statistic { - statistics[statName] ?? DefaultStatistic() + func getStatistic(for statName: StatisticTypeWrapper) -> Statistic? { + statistics[statName] } - - private var defaultStatisticDecoder: [StatisticTypeWrapper: (JSONDecoder, Data) throws -> Statistic] { - [ - TotalKillsStatistic.asType: { decoder, data in try decoder.decode(TotalKillsStatistic.self, from: data) }, - TotalGamesStatistic.asType: { decoder, data in try decoder.decode(TotalGamesStatistic.self, from: data) }, - TotalDeathsStatistic.asType: { decoder, data in try decoder.decode(TotalDeathsStatistic.self, from: data) } - ] - } - - private var defaultStatisticGenerator: [StatisticTypeWrapper: () -> Statistic] { - [ - TotalKillsStatistic.asType: { TotalKillsStatistic() }, - TotalGamesStatistic.asType: { TotalGamesStatistic() }, - TotalDeathsStatistic.asType: { TotalDeathsStatistic() } - ] - } - - /// TODO: Maybe can change this to FirebaseRepository - func loadFromFirebase() { - let databaseReference = FirebaseDatabaseReference(.Statistics) - databaseReference.child("statistics").getData(completion: { error, snapshot in - guard error == nil else { - Logger.log(error!.localizedDescription) - return - } - - if let value = snapshot?.value as? [String: Any] { - for (key, statisticValue) in value { - guard let statisticDict = statisticValue as? [String: Any], - let statisticData = try? JSONSerialization - .data(withJSONObject: statisticDict, options: []) else { - continue - } - - do { - guard let statisticType = NSClassFromString("TowerForge.\(key)") as? Statistic.Type else { - Logger.log("NSClassFromString call failed for \(key)", self) - continue - } - let statisticName = statisticType.asType - let decoder = JSONDecoder() - - let statistic: Statistic? - statistic = try self.defaultStatisticDecoder[statisticName]?(decoder, statisticData) - - if let stat = statistic { - self.statistics[statisticName] = stat - } - - } catch { - Logger.log("Error decoding \(key):, \(error)") - } - } - } - }) - } - - func saveToFirebase() { - let databaseReference = FirebaseDatabaseReference(.Statistics) - var statisticsDictionary = [String: Any]() - - for (name, statistic) in statistics { - do { - let data = try JSONEncoder().encode(statistic) - let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) - statisticsDictionary[name.toString] = dictionary - } catch { - Logger.log("Error encoding statistic \(name): \(error)") - } - } - - databaseReference.child("statistics").setValue(statisticsDictionary) { error, _ in - if let error = error { - Logger.log("Data could not be saved: \(error).") - } else { - Logger.log("StorageDatabase saved successfully!", self) - } - } - } - } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 3ad58332..a8804b9e 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -9,7 +9,7 @@ import Foundation class StatisticsEngine { /// Core storage of Statistics - var statisticsDatabase = StatisticsDatabase() + var statistics = StatisticsDatabase() var eventStatisticLinks = EventStatisticLinkDatabase() var inferenceEngines: [InferenceEngine] = [] @@ -21,18 +21,18 @@ class StatisticsEngine { /// Add statistics links func setUpLinks() { eventStatisticLinks.addStatisticLink(for: KillEvent.self, - with: statisticsDatabase.getStatistic(for: TotalKillsStatistic.asType)) + with: statistics.getStatistic(for: TotalKillsStatistic.asType)) eventStatisticLinks.addStatisticLink(for: GameStartEvent.self, - with: statisticsDatabase.getStatistic(for: TotalGamesStatistic.asType)) + with: statistics.getStatistic(for: TotalGamesStatistic.asType)) eventStatisticLinks.addStatisticLink(for: DeathEvent.self, - with: statisticsDatabase.getStatistic(for: TotalDeathsStatistic.asType)) + with: statistics.getStatistic(for: TotalDeathsStatistic.asType)) } private func initializeStatistics() { eventStatisticLinks = StatisticsFactory.getDefaultEventLinkDatabase() - statisticsDatabase = StatisticsFactory.getDefaultStatisticsDatabase() + statistics = StatisticsFactory.getDefaultStatisticsDatabase() loadStatistics() } @@ -61,15 +61,17 @@ class StatisticsEngine { /// to follow delegate pattern and have unowned statsEngine/db variables inside /// InferenceEngines func notifyInferenceEngines() { - inferenceEngines.forEach { $0.updateOnReceive(stats: statisticsDatabase) } + inferenceEngines.forEach { $0.updateOnReceive(stats: statistics) } } private func saveStatistics() { - statisticsDatabase.saveToFirebase() + LocalStorageManager.saveDatabaseToLocalStorage(statistics) } private func loadStatistics() { - statisticsDatabase.loadFromFirebase() + if let stats = LocalStorageManager.loadDatabaseFromLocalStorage() { + statistics = stats + } } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift index 597bb412..39e00738 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift @@ -9,13 +9,33 @@ import Foundation class StatisticsFactory { - static let availableStatisticsTypes: [String: Statistic.Type] = + static var availableStatisticsTypes: [String: Statistic.Type] = [ String(describing: TotalKillsStatistic.self): TotalKillsStatistic.self, String(describing: TotalGamesStatistic.self): TotalGamesStatistic.self, String(describing: TotalDeathsStatistic.self): TotalDeathsStatistic.self ] + static var defaultStatisticDecoder: [StatisticTypeWrapper: (JSONDecoder, Data) throws -> Statistic] = + [ + TotalKillsStatistic.asType: { decoder, data in try decoder.decode(TotalKillsStatistic.self, from: data) }, + TotalGamesStatistic.asType: { decoder, data in try decoder.decode(TotalGamesStatistic.self, from: data) }, + TotalDeathsStatistic.asType: { decoder, data in try decoder.decode(TotalDeathsStatistic.self, from: data) } + ] + + static var defaultStatisticGenerator: [StatisticTypeWrapper: () -> Statistic] = + [ + TotalKillsStatistic.asType: { TotalKillsStatistic() }, + TotalGamesStatistic.asType: { TotalGamesStatistic() }, + TotalDeathsStatistic.asType: { TotalDeathsStatistic() } + ] + + static func registerStatisticType(_ stat: T) { + availableStatisticsTypes[String(describing: T.self)] = T.self + defaultStatisticDecoder[T.asType] = { decoder, data in try decoder.decode(T.self, from: data) } + defaultStatisticGenerator[T.asType] = { T() } + } + static func getDefaultEventLinkDatabase() -> EventStatisticLinkDatabase { let eventStatLinkDatabase = EventStatisticLinkDatabase() ObjectSet.availableEventTypes.forEach { eventStatLinkDatabase.registerEmptyEventType(for: $0) } diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift index c7e6dcb2..251ba75e 100644 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -28,6 +28,35 @@ class LocalStorageManager { } } + /// 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() @@ -69,30 +98,4 @@ class LocalStorageManager { 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 - } - - static func compareTimes(_ time1: Date, _ time2: Date) -> Bool { - time1 > time2 - } - } diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 9683e8f4..7fd9af9d 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -14,5 +14,73 @@ import FirebaseDatabaseInternal /// 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 func loadFromFirebase(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] else { + completion(nil, nil) + return + } + + var statistics = [StatisticTypeWrapper: Statistic]() + + for (key, statisticValue) in value { + guard let statisticDict = statisticValue as? [String: Any], + let statisticData = try? JSONSerialization.data(withJSONObject: statisticDict, options: []) else { + continue + } + + do { + guard let statisticType = NSClassFromString("TowerForge.\(key)") as? Statistic.Type else { + Logger.log("NSClassFromString call failed for \(key)", StatisticsDatabase.self) + continue + } + let statisticName = statisticType.asType + let decoder = JSONDecoder() + + let statistic: Statistic? = try StatisticsFactory + .defaultStatisticDecoder[statisticName]?(decoder, statisticData) + if let stat = statistic { statistics[statisticName] = stat } + } catch { + Logger.log("Error decoding \(key):, \(error)", StatisticsDatabase.self) + } + } + + let statsDatabase = StatisticsDatabase(statistics) + completion(statsDatabase, nil) + }) + } + + static func saveToFirebase(_ stats: StatisticsDatabase, completion: @escaping (Error?) -> Void) { + let databaseReference = FirebaseDatabaseReference(.Statistics) + var statisticsDictionary = [String: Any]() + + for (name, statistic) in stats.statistics { + do { + let data = try JSONEncoder().encode(statistic) + let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) + statisticsDictionary[name.asString] = dictionary + } catch { + Logger.log("Error encoding statistic \(name): \(error)", StatisticsDatabase.self) + } + } + + databaseReference.child(currentPlayer).setValue(statisticsDictionary) { error, _ in + if let error = error { + Logger.log("Data could not be saved: \(error).", StatisticsDatabase.self) + } else { + Logger.log("StorageDatabase saved successfully!", StatisticsDatabase.self) + } + completion(error) + } + } } diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift index 35f115cc..8c851112 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -8,7 +8,14 @@ import Foundation /// The class responsible for providing application wide Storage access and -/// synchronizing between -class StorageManager { +/// synchronizing between Local Storage and Remote Storage +class StorageManager: AuthenticationDelegate { + func onLogout() { + + } + + func onLogin() { + + } } From c89772188b4c6de2b089839b6edf76f69a113c38 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 20:01:19 +0800 Subject: [PATCH 17/54] Add MetadataManager and ability to update current player --- .../TowerForge.xcodeproj/project.pbxproj | 10 +-- .../AppMain/Application/AppDelegate.swift | 4 +- .../Commons/Constants/Constants.swift | 2 +- .../Metrics/Statistics/Statistic.swift | 1 + .../Statistics/StatisticsDatabase.swift | 2 - .../LocalStorageManager+Metadata.swift | 74 ----------------- .../Storage/LocalStorageManager.swift | 15 +++- TowerForge/TowerForge/Storage/Metadata.swift | 19 +++-- .../TowerForge/Storage/MetadataManager.swift | 81 +++++++++++++++++++ .../Storage/RemoteStorageManager.swift | 16 ++++ .../TowerForge/Storage/StorageManager.swift | 20 +++++ 11 files changed, 154 insertions(+), 90 deletions(-) delete mode 100644 TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift create mode 100644 TowerForge/TowerForge/Storage/MetadataManager.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index ed8d5334..d129115f 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -212,7 +212,7 @@ 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 */; }; - BA82C7712BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7702BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift */; }; + BA82C7732BCBF657000515A0 /* MetadataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7722BCBF657000515A0 /* MetadataManager.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -450,7 +450,7 @@ 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 = ""; }; - BA82C7702BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorageManager+Metadata.swift"; sourceTree = ""; }; + BA82C7722BCBF657000515A0 /* MetadataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataManager.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -1253,10 +1253,10 @@ BAFFB9512BB342E200D8301F /* Storage */ = { isa = PBXGroup; children = ( - BA82C76A2BCBD682000515A0 /* StorageManager.swift */, BA82C76E2BCBDE91000515A0 /* Metadata.swift */, + BA82C7722BCBF657000515A0 /* MetadataManager.swift */, + BA82C76A2BCBD682000515A0 /* StorageManager.swift */, BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, - BA82C7702BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift */, BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */, ); path = Storage; @@ -1499,7 +1499,6 @@ 3CD37A9D2BBEBD1600222D8A /* RemoteSpawnable.swift in Sources */, 3C9955CA2BA5888F00D33FA5 /* SpawnEvent.swift in Sources */, 3CE951582BAD724D008B2785 /* TFContact.swift in Sources */, - BA82C7712BCBE3EB000515A0 /* LocalStorageManager+Metadata.swift in Sources */, 5295A2132BAAEA16005018A8 /* UnitNode.swift in Sources */, 52DF5FF32BA351E100135367 /* SpriteComponent.swift in Sources */, 3CE9514B2BAC83FA008B2785 /* SpawnableEntities.swift in Sources */, @@ -1616,6 +1615,7 @@ BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */, 3CD37AA32BBEC0F900222D8A /* FirebaseRemoteEventPublisher.swift in Sources */, 3CAC4A6B2BB6992F00A5D22E /* TFNode.swift in Sources */, + BA82C7732BCBF657000515A0 /* MetadataManager.swift in Sources */, 3CAC4A6D2BB6A13B00A5D22E /* PositionRenderStage.swift in Sources */, 3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */, 529190E32BBFB59B001D8821 /* StatePopupNode.swift in Sources */, diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index 461b4699..55471f4e 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -16,8 +16,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - /// Load the local storage and data - // StorageManager.initializeData() + /// Initialize local metadata for current user + MetadataManager.initializeUserIdentifier() /// Connect to Firebase FirebaseApp.configure() diff --git a/TowerForge/TowerForge/Commons/Constants/Constants.swift b/TowerForge/TowerForge/Commons/Constants/Constants.swift index c6f909cc..004279aa 100644 --- a/TowerForge/TowerForge/Commons/Constants/Constants.swift +++ b/TowerForge/TowerForge/Commons/Constants/Constants.swift @@ -18,7 +18,7 @@ class Constants { static let LOCAL_STORAGE_FILE_NAME = "TowerForgeLocalStorage.json" /// The name of the file that contains metadata about local storage - static let METADATA_NAME = "TowerForgeMetadata.json" + static let METADATA_FILE_NAME = "TowerForgeMetadata.json" /// The name of the player currently logged in. /// By default, this is set to the default id associated with the device diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 5f84841a..d6741ceb 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -34,6 +34,7 @@ protocol Statistic: AnyObject, Codable { } extension Statistic { + init() { self.init(permanentValue: .zero, currentValue: .zero) } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift index 0c9ec8df..71fa5609 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift @@ -13,8 +13,6 @@ final class StatisticsDatabase { init(_ stats: [StatisticTypeWrapper: Statistic] = [:]) { self.statistics = stats - // self.loadFromFirebase() - // Logger.log("Current killcount is \(String(describing: self.statistics[TotalKillsStatistic.asType]))", self) } func addStatistic(for statName: StatisticTypeWrapper) { diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift b/TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift deleted file mode 100644 index 491523c2..00000000 --- a/TowerForge/TowerForge/Storage/LocalStorageManager+Metadata.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// LocalStorageManager+Metadata.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. -extension LocalStorageManager { - - static func saveMetadataToLocalStorage() { - let metadata = Metadata(lastUpdated: Date()) - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - - do { - let folderURL = try createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(Self.metadataName) - let data = try encoder.encode(metadata) - try data.write(to: fileURL) - Logger.log("Saved metadata at: \(fileURL.path)", self) - } catch { - Logger.log("Error saving metadata: \(error)", self) - } - } - - static func loadMetadataFromLocalStorage() -> Metadata? { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - do { - let folderURL = try createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(Self.metadataName) - let data = try Data(contentsOf: fileURL) - let metadata = try decoder.decode(Metadata.self, from: data) - return metadata - } catch { - Logger.log("Error loading metadata: \(error)", self) - return nil - } - } - - /// Deletes the stored metadata from file - static func deleteMetadataFromLocalStorage() { - do { - let folderURL = try createFolderIfNeeded(folderName: Self.folderName) - let fileURL = folderURL.appendingPathComponent(Self.metadataName) - try FileManager.default.removeItem(at: fileURL) - Logger.log("Deleted metadata at: \(fileURL.path)", self) - } catch { - Logger.log("Error deleting metadata: \(error)", self) - } - } - - /// Retrieves file metadata provided by iOS for a given filename in the document directory. - static func getFileManagerMetadata(for filename: String) -> [FileAttributeKey: Any]? { - do { - let folderURL = try createFolderIfNeeded(folderName: folderName) - let fileURL = folderURL.appendingPathComponent(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/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift index 251ba75e..6e01f1f1 100644 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -15,7 +15,7 @@ import Foundation class LocalStorageManager { static let folderName = Constants.LOCAL_STORAGE_CONTAINER_NAME static let fileName = Constants.LOCAL_STORAGE_FILE_NAME - static let metadataName = Constants.METADATA_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. @@ -98,4 +98,17 @@ extension LocalStorageManager { 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/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index a078809f..f095f4ab 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -7,17 +7,26 @@ import Foundation -/// The metadata class is used to encapsulate meta-information about files -/// stored locally, possibly for use with conflict resolution. +/// The metadata class is used to encapsulate +/// +/// - 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 lastUpdated: Date + let uniqueIdentifier: String + var lastUpdated: Date - init(lastUpdated: Date) { + init(lastUpdated: Date, uniqueIdentifier: String) { self.lastUpdated = lastUpdated + self.uniqueIdentifier = uniqueIdentifier + } + + required init() { + self.lastUpdated = Date() + self.uniqueIdentifier = UUID().uuidString } static func == (lhs: Metadata, rhs: Metadata) -> Bool { - lhs.lastUpdated == rhs.lastUpdated + lhs.lastUpdated == rhs.lastUpdated && lhs.uniqueIdentifier == rhs.uniqueIdentifier } static func < (lhs: Metadata, rhs: Metadata) -> Bool { diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift new file mode 100644 index 00000000..6d1e4af1 --- /dev/null +++ b/TowerForge/TowerForge/Storage/MetadataManager.swift @@ -0,0 +1,81 @@ +// +// MetadataManager.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 MetadataManager { + + 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 = MetadataManager.checkAndCreateMetadata() + Constants.CURRENT_PLAYER_ID = metadata.uniqueIdentifier + } + + 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() { + var metadata = loadMetadataFromLocalStorage() ?? Metadata() + metadata.lastUpdated = Date() + saveMetadataToLocalStorage(metadata) + Logger.log("Metadata updated at: \(metadata.lastUpdated)", self) + } + + 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/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 7fd9af9d..718608c2 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -83,4 +83,20 @@ class RemoteStorageManager { completion(error) } } + + /// Deletes the player's statistics database from Firebase + static func deleteFromFirebase(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("Data for player \(currentPlayer) successfully deleted from Firebase.", self) + completion(nil) + } + } } diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift index 8c851112..adf9d6c5 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -18,4 +18,24 @@ class StorageManager: AuthenticationDelegate { } + static func resetAllStorage() { + Self.deleteAllRemoteStorage() + Self.deleteAllLocalStorage() + } + + static func deleteAllLocalStorage() { + LocalStorageManager.deleteDatabaseFromLocalStorage() + MetadataManager.deleteMetadataFromLocalStorage() + } + + static func deleteAllRemoteStorage() { + RemoteStorageManager.deleteFromFirebase { error in + if let error = error { + Logger.log("Failed to delete user data: \(error)", self) + } else { + Logger.log("User data deleted, proceeding with logout", self) + } + } + } + } From d27f88ca2994b7c4a51b8e2a6d1daaf61aaf9ddb Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 20:04:01 +0800 Subject: [PATCH 18/54] Fix style --- TowerForge/TowerForge/Storage/LocalStorageManager.swift | 2 +- TowerForge/TowerForge/Storage/MetadataManager.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift index 6e01f1f1..8ca193ff 100644 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -21,7 +21,7 @@ class LocalStorageManager { /// Called by the AppDelegate when the application is run. static func initializeLocalStatisticsDatabase() { if Self.loadDatabaseFromLocalStorage() != nil { - Logger.log("Loaded existing database.", Self.self) + Logger.log("Database exists locally", Self.self) } else { Self.saveDatabaseToLocalStorage(StatisticsFactory.getDefaultStatisticsDatabase()) Logger.log("Created and saved a new empty database.", Self.self) diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift index 6d1e4af1..c11a4f4b 100644 --- a/TowerForge/TowerForge/Storage/MetadataManager.swift +++ b/TowerForge/TowerForge/Storage/MetadataManager.swift @@ -37,7 +37,7 @@ class MetadataManager { } static func updateMetadataInLocalStorage() { - var metadata = loadMetadataFromLocalStorage() ?? Metadata() + let metadata = loadMetadataFromLocalStorage() ?? Metadata() metadata.lastUpdated = Date() saveMetadataToLocalStorage(metadata) Logger.log("Metadata updated at: \(metadata.lastUpdated)", self) From 075d72ca1c465d59ba536c5b14ed1f69e1525bf2 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Sun, 14 Apr 2024 21:19:19 +0800 Subject: [PATCH 19/54] Add local-remote synchronized storage functionality --- .../Metrics/Statistics/Statistic.swift | 2 +- .../StatisticsDatabase+Codable.swift | 2 + .../Statistics/StatisticsDatabase+Merge.swift | 13 +++- .../Statistics/StatisticsDatabase.swift | 4 + .../Metrics/Statistics/StatisticsEngine.swift | 6 +- .../Storage/RemoteStorageManager.swift | 19 +++++ .../TowerForge/Storage/StorageManager.swift | 74 ++++++++++++++++++- 7 files changed, 111 insertions(+), 9 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index d6741ceb..8dcca564 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -39,7 +39,7 @@ extension Statistic { self.init(permanentValue: .zero, currentValue: .zero) } - static func == (lhs: Self, rhs: Self) -> Bool { + static func equals(lhs: Self, rhs: Self) -> Bool { (lhs.statisticName == rhs.statisticName) && (lhs.permanentValue == rhs.permanentValue) } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift index 3dc45812..c8c464c0 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift @@ -7,6 +7,8 @@ import Foundation +/// This extension adds encoding and decoding functionality to +/// the Statistics Database to allow for storing and loading from file. extension StatisticsDatabase: Codable { private static func generateStatisticsCollection(_ statsArray: [Statistic]) -> [StatisticTypeWrapper: Statistic] { diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index 9272c16e..8b2a8ee1 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -10,7 +10,18 @@ import Foundation /// This extension adds merging abilities to the StatisticsDatabase. /// /// Represen -extension StatisticsDatabase { +extension StatisticsDatabase: Equatable { + + static func == (lhs: StatisticsDatabase, rhs: StatisticsDatabase) -> Bool { + guard lhs.statistics.count == rhs.statistics.count else { + return false + } + + return lhs.statistics.keys.allSatisfy { + (lhs.statistics[$0]?.statisticName == rhs.statistics[$0]?.statisticName) && + (lhs.statistics[$0]?.permanentValue == rhs.statistics[$0]?.permanentValue) + } + } /// Compares two StatisticsDatabase instances and outputs a merged version /// diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift index 71fa5609..c6c156cc 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift @@ -22,4 +22,8 @@ final class StatisticsDatabase { func getStatistic(for statName: StatisticTypeWrapper) -> Statistic? { statistics[statName] } + + func setToDefault() { + statistics = StatisticsFactory.getDefaultStatisticsDatabase().statistics + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index a8804b9e..8b02ed52 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -65,13 +65,11 @@ class StatisticsEngine { } private func saveStatistics() { - LocalStorageManager.saveDatabaseToLocalStorage(statistics) + _ = StorageManager.saveUniversally(statistics) } private func loadStatistics() { - if let stats = LocalStorageManager.loadDatabaseFromLocalStorage() { - statistics = stats - } + statistics = StorageManager.loadUniversally() } } diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 718608c2..38b887af 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -16,6 +16,25 @@ import FirebaseDatabaseInternal class RemoteStorageManager { static var currentPlayer: String = Constants.CURRENT_PLAYER_ID + /// 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 let exists = snapshot?.exists(), let value = snapshot?.value { + completion(true) + } else { + completion(false) + } + }) + } + static func loadFromFirebase(completion: @escaping (StatisticsDatabase?, Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(.Statistics) diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift index adf9d6c5..751ef1f9 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -8,8 +8,17 @@ import Foundation /// The class responsible for providing application wide Storage access and -/// synchronizing between Local Storage and Remote Storage +/// synchronizing between Local Storage and Remote Storage. The application interacts only +/// with StorageManager which handles all storage operations. class StorageManager: AuthenticationDelegate { + 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.") + } + } + func onLogout() { } @@ -31,11 +40,70 @@ class StorageManager: AuthenticationDelegate { static func deleteAllRemoteStorage() { RemoteStorageManager.deleteFromFirebase { error in if let error = error { - Logger.log("Failed to delete user data: \(error)", self) + 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) + } + } + + /// 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 + var localStorage: StatisticsDatabase + var remoteStorage = StatisticsDatabase() + + if let localStats = LocalStorageManager.loadDatabaseFromLocalStorage() { + localStorage = localStats + } else { + LocalStorageManager.initializeLocalStatisticsDatabase() + localStorage = StatisticsFactory.getDefaultStatisticsDatabase() + } + + RemoteStorageManager.loadFromFirebase { 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 { - Logger.log("User data deleted, proceeding with logout", self) + // 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 = StatisticsDatabase() } } + + let finalStorage = StatisticsDatabase.merge(lhs: localStorage, rhs: remoteStorage) + RemoteStorageManager.saveToFirebase(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 } } From 4cae924189c93939585387c3517192f79aa797d9 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 01:40:29 +0800 Subject: [PATCH 20/54] Update storage methods to use refined codable conformance --- .../AppMain/Application/AppDelegate.swift | 18 ++++ .../Commons/Constants/Constants.swift | 5 +- .../Commons/Protocols/Double+Extensions.swift | 15 +++ .../Implemented/TotalDeathsStatistic.swift | 9 ++ .../Implemented/TotalGamesStatistic.swift | 9 ++ .../Implemented/TotalKillsStatistic.swift | 8 ++ .../Metrics/Statistics/Statistic.swift | 35 +------ .../Statistics/StatisticTypeWrapper.swift | 26 ++--- .../StatisticsDatabase+Codable.swift | 10 +- .../TowerForge/Storage/MetadataManager.swift | 1 + .../Storage/RemoteStorageManager.swift | 95 ++++++++++++++----- 11 files changed, 158 insertions(+), 73 deletions(-) diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index 55471f4e..5b991734 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -24,6 +24,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate { /// Prepare audio player to begin playing music AudioManager.shared.setupAllAudioPlayers() + + /// Temporary tests + Logger.log("StatisticTypeWrapper: \(TotalGamesStatistic.asType)", self) + Logger.log("Wrapper.asString: \(TotalGamesStatistic.asType.asString)", self) + Logger.log("Wrapper.type: \(TotalGamesStatistic.asType.type)", self) + + let string = "TotalGamesStatistic" + + Logger.log("TotalGamesStatistics as represented by NSClass is " + + "\(String(describing: NSClassFromString("TowerForge.TotalGamesStatistic")))", self) + + guard let type = string.asTFClassFromString as? Statistic.Type else { + Logger.log("Failed", self) + return true + } + + Logger.log("Success: \(type)", self) + Logger.log(Bundle.main.projectName) return true } diff --git a/TowerForge/TowerForge/Commons/Constants/Constants.swift b/TowerForge/TowerForge/Commons/Constants/Constants.swift index 004279aa..01402f9e 100644 --- a/TowerForge/TowerForge/Commons/Constants/Constants.swift +++ b/TowerForge/TowerForge/Commons/Constants/Constants.swift @@ -14,6 +14,9 @@ class Constants { /// The name of the folder in which information is stored locally static let LOCAL_STORAGE_CONTAINER_NAME = "TowerForge" + /// The name of the TowerForge project to prefix + static let PROJECT_NAME_PREFIX = "TowerForge" + /// The name of the file that contains TowerForge data locally static let LOCAL_STORAGE_FILE_NAME = "TowerForgeLocalStorage.json" @@ -28,7 +31,7 @@ class Constants { static var CURRENT_DEVICE_ID = "" /// Universal setting to enable or disable sound effects - static var SOUND_EFFECTS_ENABLED = true + static var SOUND_EFFECTS_ENABLED = false /// Universal background audio soundtrack to play during game modes static let GAME_BACKGROUND_AUDIO: String = BackgroundMusic.gameMode.rawValue diff --git a/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift b/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift index 51a0a281..2e4ff8b2 100644 --- a/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift +++ b/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift @@ -36,3 +36,18 @@ extension CGPoint { CGPoint(x: self.x / 2.0, y: self.y / 2.0) } } + +extension Bundle { + /// A convenience extension to safely retrieve the bundle name + var projectName: String { + object(forInfoDictionaryKey: "CFBundleName") as? String ?? Constants.PROJECT_NAME_PREFIX + } +} + +extension String { + /// The NSClass name of a given class within the TowerForge Module represented in its full + /// form + var asTFClassFromString: AnyClass? { + NSClassFromString(Bundle.main.projectName + "." + self) + } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 571ad322..97856904 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -30,4 +30,13 @@ final class TotalDeathsStatistic: Statistic { return statsLink } + convenience init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: StatisticsDefaultCodingKeys.self) + _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) + let value = try container.decode(Double.self, forKey: .permanentValue) + let current = try container.decode(Double.self, forKey: .currentValue) + + self.init(permanentValue: value, currentValue: current) + } + } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index 7f756f96..fd66b042 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -30,4 +30,13 @@ final class TotalGamesStatistic: Statistic { return statsLink } + convenience init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: StatisticsDefaultCodingKeys.self) + _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) + let value = try container.decode(Double.self, forKey: .permanentValue) + let current = try container.decode(Double.self, forKey: .currentValue) + + self.init(permanentValue: value, currentValue: current) + } + } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index b775412a..c5055c15 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -30,4 +30,12 @@ final class TotalKillsStatistic: Statistic { return statsLink } + convenience init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: StatisticsDefaultCodingKeys.self) + _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) + let value = try container.decode(Double.self, forKey: .permanentValue) + let current = try container.decode(Double.self, forKey: .currentValue) + + self.init(permanentValue: value, currentValue: current) + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 8dcca564..0bc1fb8d 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -121,39 +121,10 @@ extension Statistic { /// This extension adds default implementations for encoding and decoding a statistic extension Statistic { - /*func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: StatisticsDefaultCodingKeys.self) try container.encode(statisticName, forKey: .statisticName) try container.encode(permanentValue, forKey: .permanentValue) try container.encode(currentValue, forKey: .currentValue) - }*/ - - /*init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) - let type = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) - let value = try container.decode(Double.self, forKey: .permanentValue) - let current = try container.decode(Double.self, forKey: .currentValue) - - type.type.init(permanentValue: value, currentValue: current) - self.init(permanentValue: value, currentValue: current) - }*/ - - /*init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: StorageEnums.StatisticDefaultCodingKeys.self) - let type = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) - let typeName = String(describing: type) - let permanentValue = try container.decode(Double.self, forKey: .permanentValue) - let currentValue = try container.decode(Double.self, forKey: .currentValue) - - guard let instance = StatisticsFactory.createInstance(of: typeName, - permanentValue: permanentValue, - currentValue: currentValue) else { - - throw DecodingError.dataCorruptedError(forKey: .statisticName, - in: container, - debugDescription: "Cannot instantiate Statistic of type \(typeName)") - } - - self = instance - }*/ + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift index 874e419b..39516c3c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticTypeWrapper.swift @@ -29,26 +29,26 @@ struct StatisticTypeWrapper: Equatable, Hashable { /// This extension allows the wrapped type to be written to and read from file extension StatisticTypeWrapper: Codable { - enum CodingKeys: String, CodingKey { - case typeName - } func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - let typeName = String(describing: type) - try container.encode(typeName, forKey: .typeName) + var container = encoder.singleValueContainer() + let typeName = self.asString + try container.encode(typeName) } init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let typeName = try container.decode(String.self, forKey: .typeName) + let container = try decoder.singleValueContainer() + let typeName = try container.decode(String.self) + + guard let statType = typeName.asTFClassFromString as? Statistic.Type else { + Logger.log("Error at decoding StatisticType", Self.self) + + let context = DecodingError.Context(codingPath: container.codingPath, + debugDescription: "Cannot decode \(typeName) as Statistic.Type") - guard let type = NSClassFromString(typeName) as? Statistic.Type else { - throw DecodingError.dataCorruptedError(forKey: .typeName, - in: container, - debugDescription: "Cannot decode Statistic.Type from \(typeName)") + throw DecodingError.typeMismatch(Statistic.Type.self, context) } - self.type = type + self.type = statType } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift index c8c464c0..ea5ea5d4 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift @@ -22,7 +22,7 @@ extension StatisticsDatabase: Codable { } func encode(to encoder: Encoder) throws { - Logger.log("StatisticsDatabase encoder called") + Logger.log("StatisticsDatabase encoder called", self) var container = encoder.container(keyedBy: StatisticsDatabaseCodingKeys.self) var objectsContainer = container.nestedUnkeyedContainer(forKey: .statistics) try statistics.values.forEach { try objectsContainer.encode($0) } @@ -49,20 +49,20 @@ extension StatisticsDatabase: Codable { private static func decodeObject(_ statObjectDict: KeyedDecodingContainer) throws -> (any Statistic)? { - let type = try statObjectDict.decode(StatisticTypeWrapper.self, forKey: .statisticName) - let typeName = String(describing: type) + let type = try statObjectDict.decode(String.self, forKey: .statisticName) let permanentValue = try statObjectDict.decode(Double.self, forKey: .permanentValue) let currentValue = try statObjectDict.decode(Double.self, forKey: .currentValue) - guard let instance = StatisticsFactory.createInstance(of: typeName, + guard let instance = StatisticsFactory.createInstance(of: type, permanentValue: permanentValue, currentValue: currentValue) else { throw DecodingError.dataCorruptedError(forKey: .statisticName, in: statObjectDict, - debugDescription: "Cannot instantiate Statistic of type \(typeName)") + debugDescription: "Cannot instantiate Statistic of type \(type)") } + Logger.log("Object decoding success for \(instance)", self) return instance } } diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift index c11a4f4b..1704a1a4 100644 --- a/TowerForge/TowerForge/Storage/MetadataManager.swift +++ b/TowerForge/TowerForge/Storage/MetadataManager.swift @@ -22,6 +22,7 @@ class MetadataManager { static func initializeUserIdentifier() { let metadata = MetadataManager.checkAndCreateMetadata() Constants.CURRENT_PLAYER_ID = metadata.uniqueIdentifier + Logger.log("Current player set to \(Constants.CURRENT_PLAYER_ID)", self) } static func checkAndCreateMetadata() -> Metadata { diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 38b887af..11fe6441 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -27,7 +27,7 @@ class RemoteStorageManager { return } - if let exists = snapshot?.exists(), let value = snapshot?.value { + if snapshot?.exists() != nil && snapshot?.value != nil { completion(true) } else { completion(false) @@ -38,6 +38,74 @@ class RemoteStorageManager { static func loadFromFirebase(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 saveToFirebase(_ 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("Data 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) + } + } + + /// Deletes the player's statistics database from Firebase + static func deleteFromFirebase(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("Data for player \(currentPlayer) successfully deleted from Firebase.", self) + completion(nil) + } + } +} + +extension RemoteStorageManager { + static func loadFromFirebaseOld(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) @@ -59,8 +127,7 @@ class RemoteStorageManager { } do { - guard let statisticType = NSClassFromString("TowerForge.\(key)") as? Statistic.Type else { - Logger.log("NSClassFromString call failed for \(key)", StatisticsDatabase.self) + guard let statisticType = key.asTFClassFromString as? Statistic.Type else { continue } let statisticName = statisticType.asType @@ -70,7 +137,7 @@ class RemoteStorageManager { .defaultStatisticDecoder[statisticName]?(decoder, statisticData) if let stat = statistic { statistics[statisticName] = stat } } catch { - Logger.log("Error decoding \(key):, \(error)", StatisticsDatabase.self) + Logger.log("Error decoding from Firebase \(key):, \(error)", self) } } @@ -79,7 +146,7 @@ class RemoteStorageManager { }) } - static func saveToFirebase(_ stats: StatisticsDatabase, completion: @escaping (Error?) -> Void) { + static func saveToFirebaseOld(_ stats: StatisticsDatabase, completion: @escaping (Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(.Statistics) var statisticsDictionary = [String: Any]() @@ -97,25 +164,9 @@ class RemoteStorageManager { if let error = error { Logger.log("Data could not be saved: \(error).", StatisticsDatabase.self) } else { - Logger.log("StorageDatabase saved successfully!", StatisticsDatabase.self) + Logger.log("StatisticsDatabase saved to Firebase successfully!", StatisticsDatabase.self) } completion(error) } } - - /// Deletes the player's statistics database from Firebase - static func deleteFromFirebase(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("Data for player \(currentPlayer) successfully deleted from Firebase.", self) - completion(nil) - } - } } From 198476b1355369a3f0a8ee8bc36444ca6c8ac9fc Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 01:55:29 +0800 Subject: [PATCH 21/54] Update merge resolution method to include current values --- .../Statistics/StatisticsDatabase+Codable.swift | 2 +- .../Statistics/StatisticsDatabase+Merge.swift | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift index ea5ea5d4..681331a5 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift @@ -40,7 +40,7 @@ extension StatisticsDatabase: Codable { } } - Logger.log("Loaded Statistics Database with \(objects.count)") + Logger.log("Loaded Statistics Database with \(objects.count)", self) let statObjectsMap = Self.generateStatisticsCollection(objects) self.init(statObjectsMap) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index 8b2a8ee1..e19bb329 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]?.permanentValue == rhs.statistics[$0]?.permanentValue) && + (lhs.statistics[$0]?.currentValue == rhs.statistics[$0]?.currentValue) } } @@ -43,8 +44,13 @@ extension StatisticsDatabase: Equatable { 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 { - mergedStats.statistics[key] = rhsStat + 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) } // If they are equal, lhsStat is already set, so do nothing. } else { From 56d303565315aa4487117a473d34702d755fb6e6 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 02:26:52 +0800 Subject: [PATCH 22/54] Add more refined Storage methods --- .../Commons/Enums/StorageEnums.swift | 43 ++++++++----------- .../Implemented/TotalDeathsStatistic.swift | 2 +- .../Implemented/TotalGamesStatistic.swift | 2 +- .../Implemented/TotalKillsStatistic.swift | 2 +- .../Metrics/Statistics/Statistic.swift | 14 +++++- .../StatisticsDatabase+Codable.swift | 23 ++++++++-- .../Statistics/StatisticsFactory.swift | 7 +++ 7 files changed, 60 insertions(+), 33 deletions(-) diff --git a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift index 9c2d4c57..431b84e2 100644 --- a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift +++ b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift @@ -7,35 +7,11 @@ import Foundation -typealias TFAchievementType = StorageEnums.StorableAchievementNameType typealias StatisticsDatabaseCodingKeys = StorageEnums.StatisticsDatabaseCodingKeys -typealias StatisticsDefaultCodingKeys = StorageEnums.StatisticsDefaultCodingKeys +typealias StatisticCodingKeys = StorageEnums.StatisticsDefaultCodingKeys +typealias DynamicCodingKeys = StorageEnums.DynamicCodingKeys class StorageEnums { - /// An enum for the names of every Storable that can be stored. - /// Adds an implicit "CheckRep", malicious actors cannot load - /// random storables perhaps using obj-c's dynamic runtime. - enum StorableNameType: String, CodingKey, Codable, CaseIterable { - case dummyStorable // Temp dummy case to replace later - case totalKillsAchievement - case totalGamesAchievement - } - - /// For achievements only. - /// Rep-invariant: All cases must also be contained within StorableNameType - enum StorableAchievementNameType: String, CodingKey, Codable, CaseIterable { - case totalKillsAchievement - case totalGamesAchievement - } - - /// Used in the default implementation of Storage - enum StorableDefaultCodingKeys: String, CodingKey { - case storableId - case storableName - case storableValue - } - - /// Used in StorageManager class enum StatisticsDatabaseCodingKeys: String, CodingKey, Codable { case statistics } @@ -45,4 +21,19 @@ class StorageEnums { case permanentValue case currentValue } + + struct DynamicCodingKeys: CodingKey { + var stringValue: String + var intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init?(intValue: Int) { + self.stringValue = String(intValue) + self.intValue = intValue + } + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 97856904..0c9e3d7f 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -31,7 +31,7 @@ final class TotalDeathsStatistic: Statistic { } convenience init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: StatisticsDefaultCodingKeys.self) + let container = try decoder.container(keyedBy: StatisticCodingKeys.self) _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) let value = try container.decode(Double.self, forKey: .permanentValue) let current = try container.decode(Double.self, forKey: .currentValue) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index fd66b042..827f67ea 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -31,7 +31,7 @@ final class TotalGamesStatistic: Statistic { } convenience init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: StatisticsDefaultCodingKeys.self) + let container = try decoder.container(keyedBy: StatisticCodingKeys.self) _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) let value = try container.decode(Double.self, forKey: .permanentValue) let current = try container.decode(Double.self, forKey: .currentValue) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index c5055c15..9eb165c5 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -31,7 +31,7 @@ final class TotalKillsStatistic: Statistic { } convenience init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: StatisticsDefaultCodingKeys.self) + let container = try decoder.container(keyedBy: StatisticCodingKeys.self) _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) let value = try container.decode(Double.self, forKey: .permanentValue) let current = try container.decode(Double.self, forKey: .currentValue) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 0bc1fb8d..33f1d9e7 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -122,9 +122,21 @@ extension Statistic { /// This extension adds default implementations for encoding and decoding a statistic extension Statistic { func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: StatisticsDefaultCodingKeys.self) + var container = encoder.container(keyedBy: StatisticCodingKeys.self) try container.encode(statisticName, forKey: .statisticName) try container.encode(permanentValue, forKey: .permanentValue) try container.encode(currentValue, forKey: .currentValue) } + + /* TODO: Fix new implementation + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StorageEnums.DynamicCodingKeys.self) + guard let statKey = DynamicCodingKeys(stringValue: statisticName.asString) else { + Logger.log("Encoding statistic failed", self) + return + } + var nestedContainer = container.nestedContainer(keyedBy: StatisticCodingKeys.self, forKey: statKey) + try nestedContainer.encode(permanentValue, forKey: .permanentValue) + try nestedContainer.encode(currentValue, forKey: .currentValue) + }*/ } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift index 681331a5..2a1404e0 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift @@ -34,19 +34,36 @@ extension StatisticsDatabase: Codable { var objects: [Statistic] = [] while !objectsArrayForType.isAtEnd { - let statObjectDict = try objectsArrayForType.nestedContainer(keyedBy: StatisticsDefaultCodingKeys.self) + let statObjectDict = try objectsArrayForType.nestedContainer(keyedBy: StatisticCodingKeys.self) if let statObject = try Self.decodeObject(statObjectDict) { objects.append(statObject) } } - Logger.log("Loaded Statistics Database with \(objects.count)", self) + Logger.log("Loaded Statistics Database with \(objects.count)", Self.self) let statObjectsMap = Self.generateStatisticsCollection(objects) self.init(statObjectsMap) } - private static func decodeObject(_ statObjectDict: KeyedDecodingContainer) + /*convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StatisticsDatabaseCodingKeys.self) + var statistics = [StatisticTypeWrapper: Statistic]() + let statsContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: .statistics) + + for key in statsContainer.allKeys { + let statDecoder = try statsContainer.superDecoder(forKey: key) + if let statistic = try StatisticsFactory.statisticDecoder[key.stringValue]?(statDecoder) { + if let statType = key.stringValue.asTFClassFromString as? Statistic.Type { + statistics[statType.asType] = statistic + } + } + } + + self.init(statistics) + }*/ + + private static func decodeObject(_ statObjectDict: KeyedDecodingContainer) throws -> (any Statistic)? { let type = try statObjectDict.decode(String.self, forKey: .statisticName) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift index 39e00738..f8586b06 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift @@ -23,6 +23,13 @@ class StatisticsFactory { TotalDeathsStatistic.asType: { decoder, data in try decoder.decode(TotalDeathsStatistic.self, from: data) } ] + static var statisticDecoder: [String: (Decoder) throws -> Statistic] = + [ + TotalKillsStatistic.asType.asString: { decoder in try TotalKillsStatistic(from: decoder) }, + TotalGamesStatistic.asType.asString: { decoder in try TotalGamesStatistic(from: decoder) }, + TotalDeathsStatistic.asType.asString: { decoder in try TotalDeathsStatistic(from: decoder) } + ] + static var defaultStatisticGenerator: [StatisticTypeWrapper: () -> Statistic] = [ TotalKillsStatistic.asType: { TotalKillsStatistic() }, From 3eef3e8cfc1868c1cbd9947aa1677fdcc6f9e848 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 02:34:21 +0800 Subject: [PATCH 23/54] Move Double extensions to correct folder --- TowerForge/TowerForge.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index d129115f..caf2198d 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -1179,9 +1179,9 @@ BAFFB93E2BB0A67000D8301F /* Extensions */ = { isa = PBXGroup; children = ( + BA82C7622BCBBB2A000515A0 /* Double+Extensions.swift */, 3C3CBDFE2BB8708A0001B8A9 /* CGPoint+Extensions.swift */, 3C3CBE002BB870950001B8A9 /* CGVector+Extensions.swift */, - BA82C7622BCBBB2A000515A0 /* Double+Extensions.swift */, ); path = Extensions; sourceTree = ""; From 78cc78ab7658e2c6e7e2dca1548d53d964f69452 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 02:41:19 +0800 Subject: [PATCH 24/54] Fix minor code issues --- TowerForge/TowerForge/GameViewController.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/TowerForge/TowerForge/GameViewController.swift b/TowerForge/TowerForge/GameViewController.swift index 27fa9bf3..d037976e 100644 --- a/TowerForge/TowerForge/GameViewController.swift +++ b/TowerForge/TowerForge/GameViewController.swift @@ -26,10 +26,8 @@ class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.navigationController?.isNavigationBarHidden = true - AchievementManager.incrementTotalGamesStarted() AudioManager.shared.playBackground() showGameLevelScene() - let auth = AuthenticationProvider() if auth.isUserLoggedIn() { auth.getUserDetails { data, _ in From 40c08767f8265111489c893479553fce186fad66 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 03:25:52 +0800 Subject: [PATCH 25/54] Add finalize method to StatisticsEngine inside GameWorld --- TowerForge/TowerForge/GameModule/GameWorld.swift | 3 ++- .../TowerForge/Metrics/Statistics/StatisticsEngine.swift | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TowerForge/TowerForge/GameModule/GameWorld.swift b/TowerForge/TowerForge/GameModule/GameWorld.swift index d49f511e..8ffbb577 100644 --- a/TowerForge/TowerForge/GameModule/GameWorld.swift +++ b/TowerForge/TowerForge/GameModule/GameWorld.swift @@ -44,8 +44,9 @@ class GameWorld { gameMode.updateGame(deltaTime: deltaTime) if checkGameEnded() { renderer?.renderMessage("You win") - print(gameMode.gameState) + Logger.log("\(gameMode.gameState)", self) delegate?.showGameOverScene(isWin: gameMode.gameState == .WIN, results: gameMode.getGameResults()) + statisticsEngine.finalize() } selectionNode.update() renderer?.render() diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 8b02ed52..2e3a04d1 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -32,7 +32,6 @@ class StatisticsEngine { private func initializeStatistics() { eventStatisticLinks = StatisticsFactory.getDefaultEventLinkDatabase() - statistics = StatisticsFactory.getDefaultStatisticsDatabase() loadStatistics() } @@ -42,6 +41,12 @@ class StatisticsEngine { self.notifyInferenceEngines() } + /// Transfers over all transient metrics within statistics to permanent value. + func finalize() { + statistics.statistics.values.forEach { $0.finalizeStatistic() } + saveStatistics() + } + /// Main update function private func updateStatisticsOnReceive(_ event: T) { let eventType = TFEventTypeWrapper(type: T.self) From 34c0cee8e60fec0c6cedd0131450d7ed4acad78d Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 03:30:56 +0800 Subject: [PATCH 26/54] Fix some documentation --- .../TowerForge/Metrics/Statistics/StatisticsEngine.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 2e3a04d1..a83bbaed 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -18,7 +18,8 @@ class StatisticsEngine { self.setUpLinks() } - /// Add statistics links + /// Add statistics links manually + /// TODO: Consider a more elegant way to do this func setUpLinks() { eventStatisticLinks.addStatisticLink(for: KillEvent.self, with: statistics.getStatistic(for: TotalKillsStatistic.asType)) From 631d4cf4346959f6eb5efc23c09aa85706a4c321 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 04:03:30 +0800 Subject: [PATCH 27/54] Add new achievements and make achievements factory --- .../TowerForge.xcodeproj/project.pbxproj | 12 +++++++ .../Metrics/Achievements/Achievement.swift | 14 +++++++- .../Achievements/AchievementTypeWrapper.swift | 4 +++ .../Achievements/AchievementsDatabase.swift | 16 +++++++++ .../Achievements/AchievementsEngine.swift | 11 +++--- .../Achievements/AchievementsFactory.swift | 35 +++++++++++++++++++ .../Implemented/CenturionAchievement.swift | 30 ++++++++++++++++ .../Implemented/FiftyKillsAchievement.swift | 12 +++---- .../Implemented/HundredKillsAchievement.swift | 29 +++++++++++++++ .../TowerForge/Metrics/InferenceEngine.swift | 3 +- 10 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift create mode 100644 TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift create mode 100644 TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index af266cf9..d7c561d1 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -216,6 +216,9 @@ 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 /* MetadataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7722BCBF657000515A0 /* MetadataManager.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 */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -457,6 +460,9 @@ 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 /* MetadataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataManager.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 = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -932,6 +938,7 @@ BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */, BA82C75E2BCB1528000515A0 /* AchievementsDatabase.swift */, BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */, + BA82C7742BCC689F000515A0 /* AchievementsFactory.swift */, BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */, ); path = Achievements; @@ -1000,6 +1007,8 @@ isa = PBXGroup; children = ( BA82C7602BCBBA8A000515A0 /* FiftyKillsAchievement.swift */, + BA82C7762BCC6913000515A0 /* HundredKillsAchievement.swift */, + BA82C7782BCC6943000515A0 /* CenturionAchievement.swift */, ); path = Implemented; sourceTree = ""; @@ -1501,6 +1510,7 @@ 3C9955BA2BA5637200D33FA5 /* DamageEvent.swift in Sources */, BA82C7672BCBCB00000515A0 /* StatisticsDatabase+Codable.swift in Sources */, 3CD37AA52BBEC10700222D8A /* FirebaseRemoteEventSubscriber.swift in Sources */, + BA82C7792BCC6943000515A0 /* CenturionAchievement.swift in Sources */, 52A794172BBC4F690083C976 /* GamePlayer.swift in Sources */, 3CE951632BAE037C008B2785 /* AiSystem.swift in Sources */, 3CE9515F2BADE2C5008B2785 /* ShootingSystem.swift in Sources */, @@ -1560,6 +1570,7 @@ BA82C76D2BCBD8F7000515A0 /* StatisticsDatabase+Merge.swift in Sources */, 3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */, BA82C7422BC86FE1000515A0 /* TotalGamesStatistic.swift in Sources */, + BA82C7772BCC6913000515A0 /* HundredKillsAchievement.swift in Sources */, BA2F5ABE2BC80A8B00CBD8E9 /* Achievement.swift in Sources */, 3CBECF8C2BBE9A41005EF39B /* TFRemoteEvent.swift in Sources */, 5240D0A92BB333B5004F1486 /* PointProp.swift in Sources */, @@ -1594,6 +1605,7 @@ 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 */, diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index 7881075b..0910e4ba 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -25,16 +25,28 @@ protocol Achievement: AnyObject { var isComplete: Bool { get } func loadStatistic(_ stat: Statistic) - func update() + func update(with stats: StatisticsDatabase) + + init(dependentStatistics: [Statistic]) } extension Achievement { + static var asType: AchievementTypeWrapper { + AchievementTypeWrapper(type: Self.self) + } + func loadStatistic(_ stat: any Statistic) { dependentStatistics[stat.statisticName] = stat } + func update(with stats: StatisticsDatabase) { + dependentStatistics.keys.forEach { + self.dependentStatistics[$0] = stats.statistics[$0] + } + } + var currentValues: [StatisticTypeWrapper: Double] { var values: [StatisticTypeWrapper: Double] = [:] dependentStatistics.keys.forEach { key in diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift index 8788cb1a..d8fb44f4 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementTypeWrapper.swift @@ -10,6 +10,10 @@ import Foundation struct AchievementTypeWrapper: Equatable, Hashable { let type: Achievement.Type + var asString: String { + String(describing: type) + } + static func == (lhs: Self, rhs: Self) -> Bool { lhs.type == rhs.type } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift index cb756272..9bb88794 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift @@ -14,4 +14,20 @@ class AchievementsDatabase { self.achievements = achievements } + func addAchievement(for name: AchievementTypeWrapper) { + achievements[name] = name.type + } + + func getAchievement(for name: AchievementTypeWrapper) -> Achievement? { + achievements[name] + } + + func setToDefault() { + achievements = StatisticsFactory.getDefaultStatisticsDatabase().statistics + } + + func updateAll(with stats: StatisticsDatabase) { + achievements.values.forEach { $0.update(with: stats) } + } + } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift index 36f3d728..6103b78d 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift @@ -13,16 +13,15 @@ import Foundation /// It contains a Database of Achievements, and when notified by the StatisticsEngine, /// will update all Achievements therein contained. class AchievementsEngine: InferenceEngine { - - var statisticsDatabase: StatisticsDatabase + unowned var statisticsEngine: StatisticsEngine var achievementsDatabase = AchievementsDatabase() - init(statisticsDatabase: StatisticsDatabase) { - self.statisticsDatabase = statisticsDatabase + init(_ statisticsEngine: StatisticsEngine) { + self.statisticsEngine = statisticsEngine } - func updateOnReceive(stats: StatisticsDatabase) { - statisticsDatabase = stats + func updateOnReceive() { + achievementsDatabase.updateAll(with: statisticsEngine.statistics) } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift new file mode 100644 index 00000000..199a568b --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift @@ -0,0 +1,35 @@ +// +// AchievementsFactory.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +class AchievementsFactory { + + static var availableAchievementTypes: [String: Achievement.Type] = + [ + String(describing: FiftyKillsAchievement.self): FiftyKillsAchievement.self, + String(describing: HundredKillsAchievement.self): HundredKillsAchievement.self, + String(describing: CenturionAchievement.self): CenturionAchievement.self + ] + + static func registerAchievementType(_ stat: T) { + availableAchievementTypes[String(describing: T.self)] = T.self + } + + static func getDefaultAchievementsDatabase() -> AchievementsDatabase { + let achievementsDatabase = AchievementsDatabase() + availableAchievementTypes.values.forEach { achievementsDatabase.addAchievement(for: $0.asType) } + return achievementsDatabase + } + + static func createDefaultInstance(of typeName: String, with db: StatisticsDatabase) -> Achievement? { + guard let type = availableAchievementTypes[typeName] else { + return nil + } + return type.init(permanentValue: permanentValue, currentValue: currentValue) + } +} diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift new file mode 100644 index 00000000..8dd59e6d --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift @@ -0,0 +1,30 @@ +// +// CenturionAchievement.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +final class CenturionAchievement: Achievement { + + var achievementName: String = "Centurion" + var achievementDescription: String = "Kill 100 enemies and die 100 times!" + + var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] + + var requiredValues: [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.dependentStatistics = stats + } + +} diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift index 57b8a9f6..094d6793 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -7,12 +7,16 @@ import Foundation -class FiftyKillsAchievement: Achievement { +final class FiftyKillsAchievement: Achievement { var achievementName: String = "50 Kills" var achievementDescription: String = "Attain 50 total kills in TowerForge" - var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] + var dependentStatistics: [StatisticTypeWrapper: any Statistic] { + [ + TotalKillsStatistic.asType: TotalKillsStatistic() + ] + } var requiredValues: [StatisticTypeWrapper: Double] { [ @@ -26,8 +30,4 @@ class FiftyKillsAchievement: Achievement { self.dependentStatistics = stats } - func update() { - - } - } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift new file mode 100644 index 00000000..a96c1c4a --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift @@ -0,0 +1,29 @@ +// +// HundredKillsAchievement.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +final class HundredKillsAchievement: Achievement { + + var achievementName: String = "100 Kills" + var achievementDescription: String = "Attain 100 total kills in TowerForge" + + var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] + + var requiredValues: [StatisticTypeWrapper: Double] { + [ + TotalKillsStatistic.asType: 100.0 + ] + } + + init(dependentStatistics: [Statistic]) { + var stats: [StatisticTypeWrapper: any Statistic] = [:] + dependentStatistics.forEach { stats[$0.statisticName] = $0 } + self.dependentStatistics = stats + } + +} diff --git a/TowerForge/TowerForge/Metrics/InferenceEngine.swift b/TowerForge/TowerForge/Metrics/InferenceEngine.swift index 682c61be..b2629397 100644 --- a/TowerForge/TowerForge/Metrics/InferenceEngine.swift +++ b/TowerForge/TowerForge/Metrics/InferenceEngine.swift @@ -8,5 +8,6 @@ import Foundation protocol InferenceEngine: AnyObject { - func updateOnReceive(stats: StatisticsDatabase) + var statisticsEngine: StatisticsEngine + func updateOnReceive() } From dca2eb5b5886f3bf8df7b5a49b3f9014a9496762 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 08:46:04 +0800 Subject: [PATCH 28/54] Add achievements data delegate --- .../Metrics/Achievements/Achievement.swift | 2 +- .../Metrics/Achievements/AchievementsDatabase.swift | 7 +++++-- .../Metrics/Achievements/AchievementsEngine.swift | 13 +++++++++++-- .../Metrics/Achievements/AchievementsFactory.swift | 12 ++++++++++-- .../Implemented/CenturionAchievement.swift | 7 +++++++ .../Implemented/FiftyKillsAchievement.swift | 9 ++++----- .../Implemented/HundredKillsAchievement.swift | 6 +++++- TowerForge/TowerForge/Metrics/InferenceEngine.swift | 2 +- .../Metrics/Statistics/StatisticsEngine.swift | 2 +- TowerForge/TowerForge/Storage/Metadata.swift | 4 ++++ TowerForge/TowerForge/Storage/MetadataManager.swift | 2 +- 11 files changed, 50 insertions(+), 16 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index 0910e4ba..cc0d305d 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -15,9 +15,9 @@ protocol Achievement: AnyObject { var achievementName: String { get } var achievementDescription: String { get } + static var dependentStatisticsTypes: [StatisticTypeWrapper] { get } var dependentStatistics: [StatisticTypeWrapper: Statistic] { get set } var currentValues: [StatisticTypeWrapper: Double] { get } - var requiredValues: [StatisticTypeWrapper: Double] { get } var currentProgressRates: [StatisticTypeWrapper: Double] { get } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift index 9bb88794..58b4548a 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift @@ -8,6 +8,7 @@ import Foundation class AchievementsDatabase { + weak var achievementsDataDelegate: AchievementsDataDelegate? var achievements: [AchievementTypeWrapper: Achievement] = [:] init(achievements: [AchievementTypeWrapper: Achievement] = [:]) { @@ -15,7 +16,9 @@ class AchievementsDatabase { } func addAchievement(for name: AchievementTypeWrapper) { - achievements[name] = name.type + if let stats = achievementsDataDelegate?.statisticsDatabase { + achievements[name] = AchievementsFactory.createDefaultInstance(of: name.asString, with: stats) + } } func getAchievement(for name: AchievementTypeWrapper) -> Achievement? { @@ -23,7 +26,7 @@ class AchievementsDatabase { } func setToDefault() { - achievements = StatisticsFactory.getDefaultStatisticsDatabase().statistics + achievements = AchievementsFactory.getDefaultAchievementsDatabase(achievementsDataDelegate).achievements } func updateAll(with stats: StatisticsDatabase) { diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift index 6103b78d..4cf61af9 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift @@ -7,17 +7,26 @@ import Foundation +protocol AchievementsDataDelegate: AnyObject { + var statisticsDatabase: StatisticsDatabase { get } +} + /// The AchievementsEngine is an InferenceEngine that interprets permanent /// information received from the Statistics component. /// /// It contains a Database of Achievements, and when notified by the StatisticsEngine, /// will update all Achievements therein contained. -class AchievementsEngine: InferenceEngine { +class AchievementsEngine: InferenceEngine, AchievementsDataDelegate { unowned var statisticsEngine: StatisticsEngine - var achievementsDatabase = AchievementsDatabase() + var achievementsDatabase: AchievementsDatabase + var statisticsDatabase: StatisticsDatabase { + statisticsEngine.statistics + } init(_ statisticsEngine: StatisticsEngine) { self.statisticsEngine = statisticsEngine + self.achievementsDatabase = AchievementsDatabase() + achievementsDatabase.achievementsDataDelegate = self } func updateOnReceive() { diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift index 199a568b..0182ea0c 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift @@ -20,8 +20,9 @@ class AchievementsFactory { availableAchievementTypes[String(describing: T.self)] = T.self } - static func getDefaultAchievementsDatabase() -> AchievementsDatabase { + static func getDefaultAchievementsDatabase(_ data: AchievementsDataDelegate?) -> AchievementsDatabase { let achievementsDatabase = AchievementsDatabase() + achievementsDatabase.achievementsDataDelegate = data availableAchievementTypes.values.forEach { achievementsDatabase.addAchievement(for: $0.asType) } return achievementsDatabase } @@ -30,6 +31,13 @@ class AchievementsFactory { guard let type = availableAchievementTypes[typeName] else { return nil } - return type.init(permanentValue: permanentValue, currentValue: currentValue) + + let stats: [Statistic] = db.statistics.values.filter { key in + type.dependentStatisticsTypes.contains { + $0 == key.statisticName + } + } + + return type.init(dependentStatistics: stats) } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift index 8dd59e6d..5b3f97c8 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift @@ -12,6 +12,13 @@ final class CenturionAchievement: Achievement { var achievementName: String = "Centurion" var achievementDescription: String = "Kill 100 enemies and die 100 times!" + static var dependentStatisticsTypes: [StatisticTypeWrapper] { + [ + TotalKillsStatistic.asType, + TotalDeathsStatistic.asType + ] + } + var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] var requiredValues: [StatisticTypeWrapper: Double] { diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift index 094d6793..035a1258 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -8,16 +8,15 @@ import Foundation final class FiftyKillsAchievement: Achievement { - var achievementName: String = "50 Kills" var achievementDescription: String = "Attain 50 total kills in TowerForge" - var dependentStatistics: [StatisticTypeWrapper: any Statistic] { - [ - TotalKillsStatistic.asType: TotalKillsStatistic() - ] + static var dependentStatisticsTypes: [StatisticTypeWrapper] { + [TotalKillsStatistic.asType] } + var dependentStatistics: [StatisticTypeWrapper: any Statistic] + var requiredValues: [StatisticTypeWrapper: Double] { [ TotalKillsStatistic.asType: 50.0 diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift index a96c1c4a..b52baf3c 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift @@ -8,10 +8,14 @@ import Foundation final class HundredKillsAchievement: Achievement { - var achievementName: String = "100 Kills" var achievementDescription: String = "Attain 100 total kills in TowerForge" + static var dependentStatisticsTypes: [StatisticTypeWrapper] = + [ + TotalKillsStatistic.asType + ] + var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] var requiredValues: [StatisticTypeWrapper: Double] { diff --git a/TowerForge/TowerForge/Metrics/InferenceEngine.swift b/TowerForge/TowerForge/Metrics/InferenceEngine.swift index b2629397..10b19ef6 100644 --- a/TowerForge/TowerForge/Metrics/InferenceEngine.swift +++ b/TowerForge/TowerForge/Metrics/InferenceEngine.swift @@ -8,6 +8,6 @@ import Foundation protocol InferenceEngine: AnyObject { - var statisticsEngine: StatisticsEngine + var statisticsEngine: StatisticsEngine { get set } func updateOnReceive() } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index a83bbaed..f869f18a 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -67,7 +67,7 @@ class StatisticsEngine { /// to follow delegate pattern and have unowned statsEngine/db variables inside /// InferenceEngines func notifyInferenceEngines() { - inferenceEngines.forEach { $0.updateOnReceive(stats: statistics) } + inferenceEngines.forEach { $0.updateOnReceive() } } private func saveStatistics() { diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index f095f4ab..13f4cced 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -36,4 +36,8 @@ class Metadata: Codable, Comparable, Equatable { static func > (lhs: Metadata, rhs: Metadata) -> Bool { lhs.lastUpdated > rhs.lastUpdated } + + static func latest(lhs: Metadata, rhs: Metadata) -> Metadata { + rhs.lastUpdated > lhs.lastUpdated ? rhs : lhs + } } diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift index 1704a1a4..68d43f10 100644 --- a/TowerForge/TowerForge/Storage/MetadataManager.swift +++ b/TowerForge/TowerForge/Storage/MetadataManager.swift @@ -39,7 +39,7 @@ class MetadataManager { static func updateMetadataInLocalStorage() { let metadata = loadMetadataFromLocalStorage() ?? Metadata() - metadata.lastUpdated = Date() + metadata.lastUpdated = Date.now saveMetadataToLocalStorage(metadata) Logger.log("Metadata updated at: \(metadata.lastUpdated)", self) } From 7c1883227131a552e117b90dfaf4987d747291fc Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 08:53:17 +0800 Subject: [PATCH 29/54] Fix failing achievements by adding definedParameters --- .../Metrics/Achievements/Achievement.swift | 19 +++++++++------ .../Achievements/AchievementsFactory.swift | 2 +- .../Implemented/CenturionAchievement.swift | 23 ++++++------------- .../Implemented/FiftyKillsAchievement.swift | 12 ++++------ .../Implemented/HundredKillsAchievement.swift | 15 ++++-------- 5 files changed, 29 insertions(+), 42 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index cc0d305d..80f9bcd0 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -15,8 +15,9 @@ protocol Achievement: AnyObject { var achievementName: String { get } var achievementDescription: String { get } - static var dependentStatisticsTypes: [StatisticTypeWrapper] { get } - var dependentStatistics: [StatisticTypeWrapper: Statistic] { get set } + static var definedParameters: [StatisticTypeWrapper: Double] { get } + var currentParameters: [StatisticTypeWrapper: Statistic] { get set } + var currentValues: [StatisticTypeWrapper: Double] { get } var requiredValues: [StatisticTypeWrapper: Double] { get } @@ -37,20 +38,24 @@ extension Achievement { AchievementTypeWrapper(type: Self.self) } + var requiredValues: [StatisticTypeWrapper: Double] { + Self.definedParameters + } + func loadStatistic(_ stat: any Statistic) { - dependentStatistics[stat.statisticName] = stat + currentParameters[stat.statisticName] = stat } func update(with stats: StatisticsDatabase) { - dependentStatistics.keys.forEach { - self.dependentStatistics[$0] = stats.statistics[$0] + currentParameters.keys.forEach { + self.currentParameters[$0] = stats.statistics[$0] } } var currentValues: [StatisticTypeWrapper: Double] { var values: [StatisticTypeWrapper: Double] = [:] - dependentStatistics.keys.forEach { key in - if let currentStatistic = dependentStatistics[key] { + currentParameters.keys.forEach { key in + if let currentStatistic = currentParameters[key] { values[key] = currentStatistic.permanentValue } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift index 0182ea0c..31f46e48 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift @@ -33,7 +33,7 @@ class AchievementsFactory { } let stats: [Statistic] = db.statistics.values.filter { key in - type.dependentStatisticsTypes.contains { + type.definedParameters.keys.contains { $0 == key.statisticName } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift index 5b3f97c8..86fa1f8f 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift @@ -11,27 +11,18 @@ final class CenturionAchievement: Achievement { var achievementName: String = "Centurion" var achievementDescription: String = "Kill 100 enemies and die 100 times!" + var currentParameters: [StatisticTypeWrapper: any Statistic] - static var dependentStatisticsTypes: [StatisticTypeWrapper] { - [ - TotalKillsStatistic.asType, - TotalDeathsStatistic.asType - ] - } - - var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] - - var requiredValues: [StatisticTypeWrapper: Double] { - [ - TotalKillsStatistic.asType: 100.0, - TotalDeathsStatistic.asType: 100.0 - ] - } + 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.dependentStatistics = stats + self.currentParameters = stats } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift index 035a1258..c5be7adc 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -11,22 +11,18 @@ final class FiftyKillsAchievement: Achievement { var achievementName: String = "50 Kills" var achievementDescription: String = "Attain 50 total kills in TowerForge" - static var dependentStatisticsTypes: [StatisticTypeWrapper] { - [TotalKillsStatistic.asType] - } - - var dependentStatistics: [StatisticTypeWrapper: any Statistic] - - var requiredValues: [StatisticTypeWrapper: Double] { + static var definedParameters: [StatisticTypeWrapper: Double] { [ TotalKillsStatistic.asType: 50.0 ] } + var currentParameters: [StatisticTypeWrapper: any Statistic] + init(dependentStatistics: [Statistic]) { var stats: [StatisticTypeWrapper: any Statistic] = [:] dependentStatistics.forEach { stats[$0.statisticName] = $0 } - self.dependentStatistics = stats + self.currentParameters = stats } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift index b52baf3c..5ab5f97a 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift @@ -8,26 +8,21 @@ import Foundation final class HundredKillsAchievement: Achievement { + var achievementName: String = "100 Kills" var achievementDescription: String = "Attain 100 total kills in TowerForge" - static var dependentStatisticsTypes: [StatisticTypeWrapper] = + static var definedParameters: [StatisticTypeWrapper: Double] = [ - TotalKillsStatistic.asType + TotalKillsStatistic.asType: 100.0 ] - var dependentStatistics: [StatisticTypeWrapper: any Statistic] = [:] - - var requiredValues: [StatisticTypeWrapper: Double] { - [ - TotalKillsStatistic.asType: 100.0 - ] - } + var currentParameters: [StatisticTypeWrapper: any Statistic] = [:] init(dependentStatistics: [Statistic]) { var stats: [StatisticTypeWrapper: any Statistic] = [:] dependentStatistics.forEach { stats[$0.statisticName] = $0 } - self.dependentStatistics = stats + self.currentParameters = stats } } From e228f260d09faac2d01b974faef374a29c398f43 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 09:09:17 +0800 Subject: [PATCH 30/54] Update EventStatisticLink --- .../GameModule/Events/TFEvent.swift | 6 ++ .../Metrics/Statistics/StatisticsEngine.swift | 18 +++-- .../Statistics/StatisticsFactory.swift | 16 ++--- .../Storage/RemoteStorageManager.swift | 69 ------------------- 4 files changed, 22 insertions(+), 87 deletions(-) diff --git a/TowerForge/TowerForge/GameModule/Events/TFEvent.swift b/TowerForge/TowerForge/GameModule/Events/TFEvent.swift index 7c493bc2..9e7c1666 100644 --- a/TowerForge/TowerForge/GameModule/Events/TFEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/TFEvent.swift @@ -34,3 +34,9 @@ extension TFEvent { ConcurrentEvent(self, otherEvent) } } + +extension TFEvent { + static var asType: TFEventTypeWrapper { + TFEventTypeWrapper(type: Self.self) + } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index f869f18a..700fd50c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -19,16 +19,14 @@ class StatisticsEngine { } /// Add statistics links manually - /// TODO: Consider a more elegant way to do this - func setUpLinks() { - eventStatisticLinks.addStatisticLink(for: KillEvent.self, - with: statistics.getStatistic(for: TotalKillsStatistic.asType)) - - eventStatisticLinks.addStatisticLink(for: GameStartEvent.self, - with: statistics.getStatistic(for: TotalGamesStatistic.asType)) - - eventStatisticLinks.addStatisticLink(for: DeathEvent.self, - with: statistics.getStatistic(for: TotalDeathsStatistic.asType)) + 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)) + } + } } private func initializeStatistics() { diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift index f8586b06..aac091a4 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift @@ -9,6 +9,13 @@ import Foundation class StatisticsFactory { + static var eventStatisticLinks: [TFEventTypeWrapper: [StatisticTypeWrapper]] = + [ + KillEvent.asType: [TotalKillsStatistic.asType], + GameStartEvent.asType: [TotalGamesStatistic.asType], + DeathEvent.asType: [TotalDeathsStatistic.asType] + ] + static var availableStatisticsTypes: [String: Statistic.Type] = [ String(describing: TotalKillsStatistic.self): TotalKillsStatistic.self, @@ -16,13 +23,6 @@ class StatisticsFactory { String(describing: TotalDeathsStatistic.self): TotalDeathsStatistic.self ] - static var defaultStatisticDecoder: [StatisticTypeWrapper: (JSONDecoder, Data) throws -> Statistic] = - [ - TotalKillsStatistic.asType: { decoder, data in try decoder.decode(TotalKillsStatistic.self, from: data) }, - TotalGamesStatistic.asType: { decoder, data in try decoder.decode(TotalGamesStatistic.self, from: data) }, - TotalDeathsStatistic.asType: { decoder, data in try decoder.decode(TotalDeathsStatistic.self, from: data) } - ] - static var statisticDecoder: [String: (Decoder) throws -> Statistic] = [ TotalKillsStatistic.asType.asString: { decoder in try TotalKillsStatistic(from: decoder) }, @@ -39,7 +39,7 @@ class StatisticsFactory { static func registerStatisticType(_ stat: T) { availableStatisticsTypes[String(describing: T.self)] = T.self - defaultStatisticDecoder[T.asType] = { decoder, data in try decoder.decode(T.self, from: data) } + statisticDecoder[T.asType.asString] = { decoder in try T(from: decoder) } defaultStatisticGenerator[T.asType] = { T() } } diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 11fe6441..fa51cf2b 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -101,72 +101,3 @@ class RemoteStorageManager { } } } - -extension RemoteStorageManager { - static func loadFromFirebaseOld(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] else { - completion(nil, nil) - return - } - - var statistics = [StatisticTypeWrapper: Statistic]() - - for (key, statisticValue) in value { - guard let statisticDict = statisticValue as? [String: Any], - let statisticData = try? JSONSerialization.data(withJSONObject: statisticDict, options: []) else { - continue - } - - do { - guard let statisticType = key.asTFClassFromString as? Statistic.Type else { - continue - } - let statisticName = statisticType.asType - let decoder = JSONDecoder() - - let statistic: Statistic? = try StatisticsFactory - .defaultStatisticDecoder[statisticName]?(decoder, statisticData) - if let stat = statistic { statistics[statisticName] = stat } - } catch { - Logger.log("Error decoding from Firebase \(key):, \(error)", self) - } - } - - let statsDatabase = StatisticsDatabase(statistics) - completion(statsDatabase, nil) - }) - } - - static func saveToFirebaseOld(_ stats: StatisticsDatabase, completion: @escaping (Error?) -> Void) { - let databaseReference = FirebaseDatabaseReference(.Statistics) - var statisticsDictionary = [String: Any]() - - for (name, statistic) in stats.statistics { - do { - let data = try JSONEncoder().encode(statistic) - let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) - statisticsDictionary[name.asString] = dictionary - } catch { - Logger.log("Error encoding statistic \(name): \(error)", StatisticsDatabase.self) - } - } - - databaseReference.child(currentPlayer).setValue(statisticsDictionary) { error, _ in - if let error = error { - Logger.log("Data could not be saved: \(error).", StatisticsDatabase.self) - } else { - Logger.log("StatisticsDatabase saved to Firebase successfully!", StatisticsDatabase.self) - } - completion(error) - } - } -} From 00bf75371c7ab5e7999ecf9af5a205a6607a18f7 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 15:05:10 +0800 Subject: [PATCH 31/54] Add Metadata manager --- .../TowerForge.xcodeproj/project.pbxproj | 16 ++- .../AppMain/Application/AppDelegate.swift | 2 +- .../AuthenticationManager.swift | 2 + .../Firebase/FirebaseReference.swift | 1 + .../Statistics/StatisticsDatabase+Merge.swift | 17 +++- .../Metrics/Statistics/StatisticsEngine.swift | 4 +- .../Storage/LocalMetadataManager.swift | 83 ++++++++++++++++ .../Storage/LocalStorageManager.swift | 2 + .../TowerForge/Storage/MetadataManager.swift | 76 +------------- .../Storage/RemoteMetadataManager.swift | 99 +++++++++++++++++++ .../Storage/RemoteStorageManager.swift | 10 +- .../TowerForge/Storage/StorageManager.swift | 60 ++++++++--- 12 files changed, 272 insertions(+), 100 deletions(-) create mode 100644 TowerForge/TowerForge/Storage/LocalMetadataManager.swift create mode 100644 TowerForge/TowerForge/Storage/RemoteMetadataManager.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index d7c561d1..784f33a4 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -215,10 +215,12 @@ 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 /* MetadataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7722BCBF657000515A0 /* MetadataManager.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 */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -459,10 +461,12 @@ 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 /* MetadataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataManager.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 = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -1272,7 +1276,9 @@ isa = PBXGroup; children = ( BA82C76E2BCBDE91000515A0 /* Metadata.swift */, - BA82C7722BCBF657000515A0 /* MetadataManager.swift */, + BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */, + BA82C7722BCBF657000515A0 /* LocalMetadataManager.swift */, + BA82C77C2BCD07F4000515A0 /* RemoteMetadataManager.swift */, BA82C76A2BCBD682000515A0 /* StorageManager.swift */, BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */, @@ -1493,6 +1499,7 @@ 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 */, @@ -1560,6 +1567,7 @@ BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */, 527A07762BB3E4CF00CD9D08 /* GameState.swift in Sources */, 3C9955AD2BA483B100D33FA5 /* TFSystem.swift in Sources */, + BA82C77B2BCD05DC000515A0 /* MetadataManager.swift in Sources */, 3CAC4A672BB6975200A5D22E /* RenderStage.swift in Sources */, BA82C75D2BCB1451000515A0 /* InferenceEngine.swift in Sources */, 3C9955BE2BA57E4B00D33FA5 /* EventManager.swift in Sources */, @@ -1638,7 +1646,7 @@ BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */, 3CD37AA32BBEC0F900222D8A /* FirebaseRemoteEventPublisher.swift in Sources */, 3CAC4A6B2BB6992F00A5D22E /* TFNode.swift in Sources */, - BA82C7732BCBF657000515A0 /* MetadataManager.swift in Sources */, + BA82C7732BCBF657000515A0 /* LocalMetadataManager.swift in Sources */, 3CBE73012BC8D69A00CC446A /* RemoteLifeEvent.swift in Sources */, 3CAC4A6D2BB6A13B00A5D22E /* PositionRenderStage.swift in Sources */, 3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */, diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index 5b991734..6128ffc3 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Override point for customization after application launch. /// Initialize local metadata for current user - MetadataManager.initializeUserIdentifier() + LocalMetadataManager.initializeUserIdentifier() /// Connect to Firebase FirebaseApp.configure() diff --git a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift index df709c72..d5c9cfd9 100644 --- a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift +++ b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift @@ -85,6 +85,7 @@ 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 self.delegate?.onLogin() completion(userData, nil) } @@ -94,6 +95,7 @@ class AuthenticationManager: AuthenticationProtocol { do { try Auth.auth().signOut() self.delegate?.onLogout() + Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID completion(nil) } catch let error as NSError { completion(error) diff --git a/TowerForge/TowerForge/Firebase/FirebaseReference.swift b/TowerForge/TowerForge/Firebase/FirebaseReference.swift index 511b575c..74ca1efc 100644 --- a/TowerForge/TowerForge/Firebase/FirebaseReference.swift +++ b/TowerForge/TowerForge/Firebase/FirebaseReference.swift @@ -14,6 +14,7 @@ enum FirebaseReference: String { case Players case Ranks case Statistics + case Metadata } func FirebaseDatabaseReference(_ reference: FirebaseReference) -> DatabaseReference { diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index e19bb329..30644a9a 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -31,7 +31,22 @@ extension StatisticsDatabase: Equatable { /// - 2. Retain the value that has the greater magnitude in the final database for duplicate keys /// - 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(lhs: StatisticsDatabase, rhs: StatisticsDatabase) -> StatisticsDatabase { + static func merge(this: StatisticsDatabase?, that: StatisticsDatabase?) -> StatisticsDatabase? { + guard this != nil || that != nil else { + return nil + } + + var lhs = StatisticsDatabase() + var rhs = StatisticsDatabase() + + if let this = this { + lhs = this + } + + if let that = that { + rhs = that + } + let mergedStats = StatisticsDatabase() // Merge lhs statistics diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 700fd50c..935ff10c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -73,7 +73,9 @@ class StatisticsEngine { } private func loadStatistics() { - statistics = StorageManager.loadUniversally() + if let loadedStats = StorageManager.loadUniversally() { + statistics = loadedStats + } } } diff --git a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift new file mode 100644 index 00000000..1409a254 --- /dev/null +++ b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift @@ -0,0 +1,83 @@ +// +// 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) + } + + 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) + } + + 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 index 8ca193ff..40111e89 100644 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -70,6 +70,8 @@ extension LocalStorageManager { } catch { Logger.log("Error saving statistics Database: \(error)", self) } + + LocalMetadataManager.updateMetadataInLocalStorage() } /// Loads a database (with the class constant folderName and fileName) from file diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift index 68d43f10..26e3978e 100644 --- a/TowerForge/TowerForge/Storage/MetadataManager.swift +++ b/TowerForge/TowerForge/Storage/MetadataManager.swift @@ -2,81 +2,7 @@ // MetadataManager.swift // TowerForge // -// Created by Rubesh on 14/4/24. +// Created by Rubesh on 15/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 MetadataManager { - - 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 = MetadataManager.checkAndCreateMetadata() - Constants.CURRENT_PLAYER_ID = metadata.uniqueIdentifier - Logger.log("Current player set to \(Constants.CURRENT_PLAYER_ID)", self) - } - - 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) - } - - 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/RemoteMetadataManager.swift b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift new file mode 100644 index 00000000..11a40967 --- /dev/null +++ b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift @@ -0,0 +1,99 @@ +// +// 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 storage 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 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) + } + } + + /// Deletes the player's metadat from Firebase + 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 index fa51cf2b..49176347 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -35,7 +35,7 @@ class RemoteStorageManager { }) } - static func loadFromFirebase(completion: @escaping (StatisticsDatabase?, Error?) -> Void) { + static func loadDatabaseFromFirebase(completion: @escaping (StatisticsDatabase?, Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(.Statistics) databaseReference.child(currentPlayer).getData(completion: { error, snapshot in @@ -62,7 +62,7 @@ class RemoteStorageManager { }) } - static func saveToFirebase(_ stats: StatisticsDatabase, completion: @escaping (Error?) -> Void) { + static func saveDatabaseToFirebase(_ stats: StatisticsDatabase, completion: @escaping (Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(.Statistics) do { @@ -72,7 +72,7 @@ class RemoteStorageManager { databaseReference.child(currentPlayer).setValue(dictionary) { error, _ in if let error = error { - Logger.log("Data could not be saved: \(error).", StatisticsDatabase.self) + Logger.log("StatisticsDatabase could not be saved: \(error).", StatisticsDatabase.self) completion(error) } else { Logger.log("StatisticsDatabase saved to Firebase successfully!", StatisticsDatabase.self) @@ -86,7 +86,7 @@ class RemoteStorageManager { } /// Deletes the player's statistics database from Firebase - static func deleteFromFirebase(completion: @escaping (Error?) -> Void) { + static func deleteDatabaseFromFirebase(completion: @escaping (Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(.Statistics) // Remove the data at the specific currentPlayer node @@ -96,7 +96,7 @@ class RemoteStorageManager { completion(error) return } - Logger.log("Data for player \(currentPlayer) successfully deleted from Firebase.", self) + 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 index 751ef1f9..40d546d4 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -10,7 +10,7 @@ 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: AuthenticationDelegate { +class StorageManager { static var defaultErrorClosure: (Error?) -> Void = { error in if let error = error { Logger.log("Generic error message invoked: \(error)") @@ -19,12 +19,43 @@ class StorageManager: AuthenticationDelegate { } } - func onLogout() { + static func onLogin(with userId: String) { + var localStorage = LocalStorageManager.loadDatabaseFromLocalStorage() - } + if let localStorage = localStorage { + _ = Self.saveUniversally(localStorage) + } else { + var defaultStorage = StatisticsFactory.getDefaultStatisticsDatabase() + _ = Self.saveUniversally(defaultStorage) + localStorage = defaultStorage + } + + Constants.CURRENT_PLAYER_ID = userId + 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() + } + } - func onLogin() { + var finalStorage = StatisticsDatabase.merge(this: localStorage, that: remoteStorage) + if let finalStorage = finalStorage { + _ = Self.saveUniversally(finalStorage) + } + + } + + static func onLogout() { + Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID } static func resetAllStorage() { @@ -34,11 +65,11 @@ class StorageManager: AuthenticationDelegate { static func deleteAllLocalStorage() { LocalStorageManager.deleteDatabaseFromLocalStorage() - MetadataManager.deleteMetadataFromLocalStorage() + LocalMetadataManager.deleteMetadataFromLocalStorage() } static func deleteAllRemoteStorage() { - RemoteStorageManager.deleteFromFirebase { error in + RemoteStorageManager.deleteDatabaseFromFirebase { error in if let error = error { Logger.log("Deletion of all remote storage failed by StorageManager: \(error)", self) } else { @@ -47,13 +78,13 @@ class StorageManager: AuthenticationDelegate { } } - static func saveUniversally(_ statistics: StatisticsDatabase) -> StatisticsDatabase { + static func saveUniversally(_ statistics: StatisticsDatabase) -> StatisticsDatabase? { LocalStorageManager.saveDatabaseToLocalStorage(statistics) return Self.pushToRemote() } - static func loadUniversally() -> StatisticsDatabase { + static func loadUniversally() -> StatisticsDatabase? { if let stats = LocalStorageManager.loadDatabaseFromLocalStorage() { return stats } else { @@ -68,7 +99,7 @@ class StorageManager: AuthenticationDelegate { /// - 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 { + private static func pushToRemote() -> StatisticsDatabase? { // Explicitly load storage to ensure that uploaded data is var localStorage: StatisticsDatabase var remoteStorage = StatisticsDatabase() @@ -80,7 +111,7 @@ class StorageManager: AuthenticationDelegate { localStorage = StatisticsFactory.getDefaultStatisticsDatabase() } - RemoteStorageManager.loadFromFirebase { statisticsDatabase, error in + RemoteStorageManager.loadDatabaseFromFirebase { statisticsDatabase, error in if let error = error { Logger.log("Error loading data: \(error)", self) } else if let statisticsDatabase = statisticsDatabase { @@ -89,12 +120,15 @@ class StorageManager: AuthenticationDelegate { } 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 = StatisticsDatabase() + remoteStorage = StatisticsFactory.getDefaultStatisticsDatabase() } } - let finalStorage = StatisticsDatabase.merge(lhs: localStorage, rhs: remoteStorage) - RemoteStorageManager.saveToFirebase(finalStorage) { error in + guard let finalStorage = StatisticsDatabase.merge(this: localStorage, that: remoteStorage) else { + return nil + } + + RemoteStorageManager.saveDatabaseToFirebase(finalStorage) { error in if let error = error { Logger.log("Saving to firebase error: \(error)", self) } else { From 1fbaf6fe37562a3d359e0228834f172d2a40ceb9 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 15:55:32 +0800 Subject: [PATCH 32/54] Add initialization method to StorageManager --- .../TowerForge/Storage/MetadataManager.swift | 4 +++ .../Storage/RemoteMetadataManager.swift | 3 +-- .../Storage/RemoteStorageManager.swift | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift index 26e3978e..08f2d46d 100644 --- a/TowerForge/TowerForge/Storage/MetadataManager.swift +++ b/TowerForge/TowerForge/Storage/MetadataManager.swift @@ -6,3 +6,7 @@ // import Foundation + +class MetadataManager { + +} diff --git a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift index 11a40967..4b98df4b 100644 --- a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift @@ -12,7 +12,7 @@ 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 storage exists for the current player + /// 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) @@ -81,7 +81,6 @@ class RemoteMetadataManager { } } - /// Deletes the player's metadat from Firebase static func deleteMetadataFromFirebase(completion: @escaping (Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(.Metadata) diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 49176347..22eeebb3 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -15,6 +15,33 @@ import FirebaseDatabaseInternal /// 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 and empty database, new one will be created", self) + var 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) { From afa05cfe43a339db82867eceec1480f3d0fc2e60 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 17:01:33 +0800 Subject: [PATCH 33/54] Add dual conflict resolution methods --- .../TowerForge.xcodeproj/project.pbxproj | 2 +- .../Commons/Constants/Constants.swift | 3 + .../Commons/Enums/StorageEnums.swift | 13 ++++ .../AuthenticationManager.swift | 2 +- .../StatisticUpdateLinkDatabase.swift | 2 +- .../Storage/LocalMetadataManager.swift | 2 + .../Storage/LocalStorageManager.swift | 2 + TowerForge/TowerForge/Storage/Metadata.swift | 3 +- .../TowerForge/Storage/MetadataManager.swift | 52 ++++++++++++++++ .../Storage/RemoteMetadataManager.swift | 55 +++++++++++++++++ .../Storage/RemoteStorageManager.swift | 4 +- .../TowerForge/Storage/StorageManager.swift | 61 +++++++++++++++---- 12 files changed, 183 insertions(+), 18 deletions(-) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 784f33a4..37479fca 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -1277,9 +1277,9 @@ children = ( BA82C76E2BCBDE91000515A0 /* Metadata.swift */, BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */, + BA82C76A2BCBD682000515A0 /* StorageManager.swift */, BA82C7722BCBF657000515A0 /* LocalMetadataManager.swift */, BA82C77C2BCD07F4000515A0 /* RemoteMetadataManager.swift */, - BA82C76A2BCBD682000515A0 /* StorageManager.swift */, BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */, ); diff --git a/TowerForge/TowerForge/Commons/Constants/Constants.swift b/TowerForge/TowerForge/Commons/Constants/Constants.swift index 01402f9e..98584253 100644 --- a/TowerForge/TowerForge/Commons/Constants/Constants.swift +++ b/TowerForge/TowerForge/Commons/Constants/Constants.swift @@ -30,6 +30,9 @@ class Constants { /// The default id associated with the device static var CURRENT_DEVICE_ID = "" + /// The universally declared conflict resolution method + static var CONFLICT_RESOLTION: StorageConflictResolution = .MERGE + /// Universal setting to enable or disable sound effects static var SOUND_EFFECTS_ENABLED = false diff --git a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift index 431b84e2..fc8d453c 100644 --- a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift +++ b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift @@ -10,6 +10,9 @@ import Foundation typealias StatisticsDatabaseCodingKeys = StorageEnums.StatisticsDatabaseCodingKeys typealias StatisticCodingKeys = StorageEnums.StatisticsDefaultCodingKeys typealias DynamicCodingKeys = StorageEnums.DynamicCodingKeys +typealias StorageLocation = StorageEnums.StorageLocation +typealias StorageConflictResolution = StorageEnums.StorageConflictResolution + class StorageEnums { enum StatisticsDatabaseCodingKeys: String, CodingKey, Codable { @@ -22,6 +25,16 @@ class StorageEnums { case currentValue } + enum StorageLocation: String, Codable { + case Local + case Remote + } + + enum StorageConflictResolution: String { + case MERGE + case KEEP_LATEST_ONLY + } + struct DynamicCodingKeys: CodingKey { var stringValue: String var intValue: Int? diff --git a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift index d5c9cfd9..a6b2b47b 100644 --- a/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift +++ b/TowerForge/TowerForge/Firebase/Authentication/AuthenticationManager.swift @@ -95,7 +95,7 @@ class AuthenticationManager: AuthenticationProtocol { do { try Auth.auth().signOut() self.delegate?.onLogout() - Constants.CURRENT_PLAYER_ID = Constants.CURRENT_DEVICE_ID + StorageManager.onLogout() completion(nil) } catch let error as NSError { completion(error) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift index 5d6cb45a..f47f1c1c 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift @@ -33,7 +33,7 @@ class StatisticUpdateLinkDatabase { } func addStatisticUpdateActor(for eventType: T.Type, - with statisticUpdateActor: StatisticUpdateActor) { + with statisticUpdateActor: StatisticUpdateActor) { let wrappedEvent = TFEventTypeWrapper(type: eventType) statisticUpdateLinks[wrappedEvent] = statisticUpdateActor } diff --git a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift index 1409a254..44e402f1 100644 --- a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift @@ -24,6 +24,7 @@ class LocalMetadataManager { 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 { @@ -43,6 +44,7 @@ class LocalMetadataManager { metadata.lastUpdated = Date.now saveMetadataToLocalStorage(metadata) Logger.log("Metadata updated at: \(metadata.lastUpdated)", self) + RemoteMetadataManager.updateMetadataInFirebase() } static func saveMetadataToLocalStorage(_ metadata: Metadata) { diff --git a/TowerForge/TowerForge/Storage/LocalStorageManager.swift b/TowerForge/TowerForge/Storage/LocalStorageManager.swift index 40111e89..eeca5936 100644 --- a/TowerForge/TowerForge/Storage/LocalStorageManager.swift +++ b/TowerForge/TowerForge/Storage/LocalStorageManager.swift @@ -26,6 +26,8 @@ class LocalStorageManager { Self.saveDatabaseToLocalStorage(StatisticsFactory.getDefaultStatisticsDatabase()) Logger.log("Created and saved a new empty database.", Self.self) } + + RemoteStorageManager.initializeRemoteStatisticsDatabase() } /// Helper function to construct a FileURL diff --git a/TowerForge/TowerForge/Storage/Metadata.swift b/TowerForge/TowerForge/Storage/Metadata.swift index 13f4cced..abeca6e7 100644 --- a/TowerForge/TowerForge/Storage/Metadata.swift +++ b/TowerForge/TowerForge/Storage/Metadata.swift @@ -15,7 +15,8 @@ class Metadata: Codable, Comparable, Equatable { let uniqueIdentifier: String var lastUpdated: Date - init(lastUpdated: Date, uniqueIdentifier: String) { + init(lastUpdated: Date, + uniqueIdentifier: String = Constants.CURRENT_PLAYER_ID) { self.lastUpdated = lastUpdated self.uniqueIdentifier = uniqueIdentifier } diff --git a/TowerForge/TowerForge/Storage/MetadataManager.swift b/TowerForge/TowerForge/Storage/MetadataManager.swift index 08f2d46d..4537d774 100644 --- a/TowerForge/TowerForge/Storage/MetadataManager.swift +++ b/TowerForge/TowerForge/Storage/MetadataManager.swift @@ -9,4 +9,56 @@ 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 index 4b98df4b..4e9b02e0 100644 --- a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift @@ -31,6 +31,61 @@ class RemoteMetadataManager { }) } + static func initializeRemoteMetadata() { + Self.loadMetadataFromFirebase { metadata, error in + if let error = error { + Logger.log("Error initializing metadata: \(error)", self) + return + } + + if metadata != nil { + Logger.log("Metadata database already initialized.", 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? + + Self.loadMetadataFromFirebase { metadata, error in + if error != nil { + Logger.log("Error occured while loading metadata from firebase for update", self) + return + } + + existingRemoteMetadata = metadata + } + + existingRemoteMetadata?.lastUpdated = Date.now + Logger.log("Metadata updated at: \(String(describing: existingRemoteMetadata?.lastUpdated))", self) + + guard let existingRemoteMetadata = existingRemoteMetadata else { + Logger.log("Error occured while updating metadata", self) + return + } + + Self.saveMetadataToFirebase(existingRemoteMetadata) { error in + if error != nil { + Logger.log("Error occured while saving metadata to firebase for update", self) + return + } + } + + } + static func loadMetadataFromFirebase(completion: @escaping (Metadata?, Error?) -> Void) { let databaseReference = FirebaseDatabaseReference(.Metadata) diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 22eeebb3..5ee496c8 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -31,8 +31,8 @@ class RemoteStorageManager { } // No error but no database implies that database is empty, thus initialize new one - Logger.log("No error and empty database, new one will be created", self) - var remoteStorage = StatisticsFactory.getDefaultStatisticsDatabase() + 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 { diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift index 40d546d4..c898eaed 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -11,6 +11,13 @@ import Foundation /// 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() { + LocalStorageManager.initializeLocalStatisticsDatabase() + LocalMetadataManager.initializeUserIdentifier() + } + static var defaultErrorClosure: (Error?) -> Void = { error in if let error = error { Logger.log("Generic error message invoked: \(error)") @@ -20,15 +27,10 @@ class StorageManager { } static func onLogin(with userId: String) { - var localStorage = LocalStorageManager.loadDatabaseFromLocalStorage() + let localStorage = LocalStorageManager.loadDatabaseFromLocalStorage() + ?? StatisticsFactory.getDefaultStatisticsDatabase() - if let localStorage = localStorage { - _ = Self.saveUniversally(localStorage) - } else { - var defaultStorage = StatisticsFactory.getDefaultStatisticsDatabase() - _ = Self.saveUniversally(defaultStorage) - localStorage = defaultStorage - } + _ = Self.saveUniversally(localStorage) Constants.CURRENT_PLAYER_ID = userId var remoteStorage = StatisticsDatabase() @@ -46,9 +48,7 @@ class StorageManager { } } - var finalStorage = StatisticsDatabase.merge(this: localStorage, that: remoteStorage) - - if let finalStorage = finalStorage { + if let finalStorage = Self.resolveConflict(this: localStorage, that: remoteStorage) { _ = Self.saveUniversally(finalStorage) } @@ -81,7 +81,6 @@ class StorageManager { static func saveUniversally(_ statistics: StatisticsDatabase) -> StatisticsDatabase? { LocalStorageManager.saveDatabaseToLocalStorage(statistics) return Self.pushToRemote() - } static func loadUniversally() -> StatisticsDatabase? { @@ -93,6 +92,44 @@ class StorageManager { } } + /// 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() + } + + 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) From 60e8b3ebac15c0a5dbadc0dc97f19a8d6cc6954d Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 17:21:33 +0800 Subject: [PATCH 34/54] Improve InferenceEngine API --- TowerForge/TowerForge.xcodeproj/project.pbxproj | 14 +++++++++++++- .../Implemented/CenturionAchievement.swift | 1 - .../Implemented/FiftyKillsAchievement.swift | 3 +-- .../Implemented/HundredKillsAchievement.swift | 4 +--- .../Metrics/{ => Inference}/InferenceEngine.swift | 0 .../Inference/InferenceEngineFactory.swift | 15 +++++++++++++++ .../Metrics/Statistics/StatisticsEngine.swift | 5 +++++ 7 files changed, 35 insertions(+), 7 deletions(-) rename TowerForge/TowerForge/Metrics/{ => Inference}/InferenceEngine.swift (100%) create mode 100644 TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 37479fca..77b6bf0d 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -221,6 +221,7 @@ 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 */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -467,6 +468,7 @@ 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 = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -951,9 +953,9 @@ BA2F5ABF2BC80BD200CBD8E9 /* Metrics */ = { isa = PBXGroup; children = ( + BA82C77E2BCD2652000515A0 /* Inference */, BA82C7562BCAB482000515A0 /* Statistics */, BA2F5ABC2BC80A2300CBD8E9 /* Achievements */, - BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */, ); path = Metrics; sourceTree = ""; @@ -1034,6 +1036,15 @@ path = Statistics; sourceTree = ""; }; + BA82C77E2BCD2652000515A0 /* Inference */ = { + isa = PBXGroup; + children = ( + BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */, + BA82C77F2BCD284D000515A0 /* InferenceEngineFactory.swift */, + ); + path = Inference; + sourceTree = ""; + }; BAFFB9272BB09E0E00D8301F /* Storyboards */ = { isa = PBXGroup; children = ( @@ -1539,6 +1550,7 @@ 3CD37AA12BBEC00C00222D8A /* TFRemoteEventSubscriber.swift in Sources */, 52A794152BBC4EF50083C976 /* GameRoomDelegate.swift in Sources */, 52DF5FB02BA32B2300135367 /* GameViewController.swift in Sources */, + BA82C7802BCD284D000515A0 /* InferenceEngineFactory.swift in Sources */, 3CE951512BAC8955008B2785 /* Renderable.swift in Sources */, 3CAC4A712BB6AB3100A5D22E /* TFLabelNode.swift in Sources */, 529F91882BA6D7A7009551D9 /* SoldierUnit.swift in Sources */, diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift index 86fa1f8f..92125e4d 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift @@ -8,7 +8,6 @@ import Foundation final class CenturionAchievement: Achievement { - var achievementName: String = "Centurion" var achievementDescription: String = "Kill 100 enemies and die 100 times!" var currentParameters: [StatisticTypeWrapper: any Statistic] diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift index c5be7adc..196f200f 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -10,6 +10,7 @@ import Foundation final class FiftyKillsAchievement: Achievement { var achievementName: String = "50 Kills" var achievementDescription: String = "Attain 50 total kills in TowerForge" + var currentParameters: [StatisticTypeWrapper: any Statistic] static var definedParameters: [StatisticTypeWrapper: Double] { [ @@ -17,8 +18,6 @@ final class FiftyKillsAchievement: Achievement { ] } - var currentParameters: [StatisticTypeWrapper: any Statistic] - init(dependentStatistics: [Statistic]) { var stats: [StatisticTypeWrapper: any Statistic] = [:] dependentStatistics.forEach { stats[$0.statisticName] = $0 } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift index 5ab5f97a..4ca667a1 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift @@ -8,17 +8,15 @@ import Foundation final class HundredKillsAchievement: Achievement { - var achievementName: String = "100 Kills" var achievementDescription: String = "Attain 100 total kills in TowerForge" + var currentParameters: [StatisticTypeWrapper: any Statistic] = [:] static var definedParameters: [StatisticTypeWrapper: Double] = [ TotalKillsStatistic.asType: 100.0 ] - var currentParameters: [StatisticTypeWrapper: any Statistic] = [:] - init(dependentStatistics: [Statistic]) { var stats: [StatisticTypeWrapper: any Statistic] = [:] dependentStatistics.forEach { stats[$0.statisticName] = $0 } diff --git a/TowerForge/TowerForge/Metrics/InferenceEngine.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngine.swift similarity index 100% rename from TowerForge/TowerForge/Metrics/InferenceEngine.swift rename to TowerForge/TowerForge/Metrics/Inference/InferenceEngine.swift diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift new file mode 100644 index 00000000..8edce498 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift @@ -0,0 +1,15 @@ +// +// InferenceEngineFactory.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +class InferenceEngineFactory { + + static var availableInferenceEngines: [(StatisticsEngine) -> any InferenceEngine] = + [ { stats in AchievementsEngine(stats) } + ] +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 935ff10c..aef7b5c8 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -16,6 +16,7 @@ class StatisticsEngine { init() { self.initializeStatistics() self.setUpLinks() + self.setUpInferenceEngines() } /// Add statistics links manually @@ -34,6 +35,10 @@ class StatisticsEngine { loadStatistics() } + private func setUpInferenceEngines() { + InferenceEngineFactory.availableInferenceEngines.forEach { self.inferenceEngines.append($0(self)) } + } + /// Main update function func update(with event: T) { self.updateStatisticsOnReceive(event) From 6894af47818705380818a06d15d4ee029e6e0df7 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 17:27:28 +0800 Subject: [PATCH 35/54] Fix error with closure return values --- .../AppMain/Application/AppDelegate.swift | 4 ++-- .../Metrics/Statistics/StatisticsEngine.swift | 10 +++++----- .../Storage/RemoteMetadataManager.swift | 18 +++++++++--------- .../Storage/RemoteStorageManager.swift | 18 +++++++++--------- .../TowerForge/Storage/StorageManager.swift | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index 6128ffc3..76d76ae0 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -16,8 +16,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - /// Initialize local metadata for current user - LocalMetadataManager.initializeUserIdentifier() + /// Initialize all local storage + StorageManager.initializeAllStorage() /// Connect to Firebase FirebaseApp.configure() diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index aef7b5c8..139492a3 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -36,7 +36,11 @@ class StatisticsEngine { } private func setUpInferenceEngines() { - InferenceEngineFactory.availableInferenceEngines.forEach { self.inferenceEngines.append($0(self)) } + InferenceEngineFactory.availableInferenceEngines.forEach { self.addInferenceEngine($0(self)) } + } + + func addInferenceEngine(_ engine: InferenceEngine) { + inferenceEngines.append(engine) } /// Main update function @@ -62,10 +66,6 @@ class StatisticsEngine { saveStatistics() } - func addInferenceEngine(_ engine: InferenceEngine) { - inferenceEngines.append(engine) - } - /// TODO: Consider if passing the stats database directly is better or /// to follow delegate pattern and have unowned statsEngine/db variables inside /// InferenceEngines diff --git a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift index 4e9b02e0..b487212b 100644 --- a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift @@ -42,17 +42,17 @@ class RemoteMetadataManager { Logger.log("Metadata database already initialized.", 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) + // 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) + 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) + } } } } diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index 5ee496c8..a3556fab 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -28,17 +28,17 @@ class RemoteStorageManager { 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() + // 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) + 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) + } } } } diff --git a/TowerForge/TowerForge/Storage/StorageManager.swift b/TowerForge/TowerForge/Storage/StorageManager.swift index c898eaed..0695f803 100644 --- a/TowerForge/TowerForge/Storage/StorageManager.swift +++ b/TowerForge/TowerForge/Storage/StorageManager.swift @@ -14,8 +14,8 @@ class StorageManager { static var CONFLICT_RESOLUTION = Constants.CONFLICT_RESOLTION static func initializeAllStorage() { - LocalStorageManager.initializeLocalStatisticsDatabase() LocalMetadataManager.initializeUserIdentifier() + LocalStorageManager.initializeLocalStatisticsDatabase() } static var defaultErrorClosure: (Error?) -> Void = { error in From 1e20aef9c40582e9803441fcc3da7a3703717f48 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Mon, 15 Apr 2024 21:35:44 +0800 Subject: [PATCH 36/54] Add the most robust implementation of StatisticsUpdateActor yet --- .../TowerForge.xcodeproj/project.pbxproj | 48 +++++++++- .../Events/GameEvents/DamageEvent.swift | 7 ++ .../Events/GameEvents/KillEvent.swift | 6 +- .../AchievementStatsLinkDatabase.swift | 13 --- .../Achievements/AchievementsDatabase.swift | 2 +- .../Achievements/AchievementsEngine.swift | 6 +- .../Achievements/AchievementsFactory.swift | 2 +- .../Implemented/FiftyKillsAchievement.swift | 1 - .../Implemented/GrandDamageMission.swift | 28 ++++++ .../TowerForge/Metrics/Missions/Mission.swift | 93 +++++++++++++++++++ .../Metrics/Missions/MissionTypeWrapper.swift | 24 +++++ .../Metrics/Missions/MissionsDatabase.swift | 36 +++++++ .../Metrics/Missions/MissionsEngine.swift | 27 ++++++ .../Metrics/Missions/MissionsFactory.swift | 41 ++++++++ .../TotalDamageDealtStatistic.swift | 61 ++++++++++++ .../Implemented/TotalDeathsStatistic.swift | 21 ++++- .../Implemented/TotalGamesStatistic.swift | 18 +++- .../Implemented/TotalKillsStatistic.swift | 19 +++- .../Metrics/Statistics/Statistic.swift | 31 +++++-- .../StatisticUpdateLinkDatabase.swift | 39 +++++++- .../Metrics/Statistics/StatisticsEngine.swift | 3 +- .../Statistics/StatisticsFactory.swift | 13 ++- 22 files changed, 485 insertions(+), 54 deletions(-) delete mode 100644 TowerForge/TowerForge/Metrics/Achievements/AchievementStatsLinkDatabase.swift create mode 100644 TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift create mode 100644 TowerForge/TowerForge/Metrics/Missions/Mission.swift create mode 100644 TowerForge/TowerForge/Metrics/Missions/MissionTypeWrapper.swift create mode 100644 TowerForge/TowerForge/Metrics/Missions/MissionsDatabase.swift create mode 100644 TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift create mode 100644 TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift create mode 100644 TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 77b6bf0d..da5cfe77 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -202,7 +202,6 @@ BA82C74E2BC8A024000515A0 /* DeathEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C74D2BC8A024000515A0 /* DeathEvent.swift */; }; BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */; }; BA82C7532BC8A41B000515A0 /* AchievementsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */; }; - BA82C7552BC8A441000515A0 /* AchievementStatsLinkDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */; }; BA82C7582BCAB4C2000515A0 /* StatisticTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */; }; BA82C75A2BCB0DFD000515A0 /* AchievementTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */; }; BA82C75D2BCB1451000515A0 /* InferenceEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */; }; @@ -222,6 +221,13 @@ 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 */; }; + BA82C7882BCD2B51000515A0 /* MissionsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7872BCD2B51000515A0 /* MissionsDatabase.swift */; }; + BA82C78A2BCD2B5D000515A0 /* MissionsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7892BCD2B5D000515A0 /* MissionsEngine.swift */; }; + BA82C78C2BCD2B68000515A0 /* MissionsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78B2BCD2B68000515A0 /* MissionsFactory.swift */; }; + BA82C78E2BCD2D2B000515A0 /* GrandDamageMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78D2BCD2D2B000515A0 /* GrandDamageMission.swift */; }; + BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -449,7 +455,6 @@ BA82C74D2BC8A024000515A0 /* DeathEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeathEvent.swift; sourceTree = ""; }; BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDeathsStatistic.swift; sourceTree = ""; }; BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsEngine.swift; sourceTree = ""; }; - BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementStatsLinkDatabase.swift; sourceTree = ""; }; BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticTypeWrapper.swift; sourceTree = ""; }; BA82C7592BCB0DFD000515A0 /* AchievementTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementTypeWrapper.swift; sourceTree = ""; }; BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngine.swift; sourceTree = ""; }; @@ -469,6 +474,13 @@ 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 = ""; }; + BA82C7872BCD2B51000515A0 /* MissionsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionsDatabase.swift; sourceTree = ""; }; + BA82C7892BCD2B5D000515A0 /* MissionsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionsEngine.swift; sourceTree = ""; }; + BA82C78B2BCD2B68000515A0 /* MissionsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionsFactory.swift; sourceTree = ""; }; + BA82C78D2BCD2D2B000515A0 /* GrandDamageMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrandDamageMission.swift; sourceTree = ""; }; + BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDamageDealtStatistic.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -945,7 +957,6 @@ BA82C75E2BCB1528000515A0 /* AchievementsDatabase.swift */, BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */, BA82C7742BCC689F000515A0 /* AchievementsFactory.swift */, - BA82C7542BC8A441000515A0 /* AchievementStatsLinkDatabase.swift */, ); path = Achievements; sourceTree = ""; @@ -953,6 +964,7 @@ BA2F5ABF2BC80BD200CBD8E9 /* Metrics */ = { isa = PBXGroup; children = ( + BA82C7812BCD2B20000515A0 /* Missions */, BA82C77E2BCD2652000515A0 /* Inference */, BA82C7562BCAB482000515A0 /* Statistics */, BA2F5ABC2BC80A2300CBD8E9 /* Achievements */, @@ -989,6 +1001,7 @@ BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */, BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */, BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */, + BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */, ); path = Implemented; sourceTree = ""; @@ -1045,6 +1058,27 @@ path = Inference; sourceTree = ""; }; + BA82C7812BCD2B20000515A0 /* Missions */ = { + isa = PBXGroup; + children = ( + BA82C7842BCD2B35000515A0 /* Implemented */, + BA82C7822BCD2B30000515A0 /* Mission.swift */, + BA82C7852BCD2B44000515A0 /* MissionTypeWrapper.swift */, + BA82C7872BCD2B51000515A0 /* MissionsDatabase.swift */, + BA82C7892BCD2B5D000515A0 /* MissionsEngine.swift */, + BA82C78B2BCD2B68000515A0 /* MissionsFactory.swift */, + ); + path = Missions; + sourceTree = ""; + }; + BA82C7842BCD2B35000515A0 /* Implemented */ = { + isa = PBXGroup; + children = ( + BA82C78D2BCD2D2B000515A0 /* GrandDamageMission.swift */, + ); + path = Implemented; + sourceTree = ""; + }; BAFFB9272BB09E0E00D8301F /* Storyboards */ = { isa = PBXGroup; children = ( @@ -1499,6 +1533,7 @@ 5240D0A72BB33356004F1486 /* LifeProp.swift in Sources */, 3CA829C42BB70C5E00D8E72A /* ButtonComponent.swift in Sources */, 3CBECF892BBE9797005EF39B /* TFNetworkCoder.swift in Sources */, + BA82C78E2BCD2D2B000515A0 /* GrandDamageMission.swift in Sources */, 523E5C552BC63A16007444DA /* LeaderboardViewController.swift in Sources */, 5299D13E2BC36E61003EF746 /* RegisterViewController.swift in Sources */, 52DF5FFF2BA3656500135367 /* ShootingComponent.swift in Sources */, @@ -1547,6 +1582,7 @@ 527A077E2BB3F75700CD9D08 /* Timer.swift in Sources */, 52578B872BA6209700B4D76C /* DamageComponent.swift in Sources */, 9B0406182BB8A1E10026E903 /* PowerUpDelegateFactory.swift in Sources */, + BA82C78C2BCD2B68000515A0 /* MissionsFactory.swift in Sources */, 3CD37AA12BBEC00C00222D8A /* TFRemoteEventSubscriber.swift in Sources */, 52A794152BBC4EF50083C976 /* GameRoomDelegate.swift in Sources */, 52DF5FB02BA32B2300135367 /* GameViewController.swift in Sources */, @@ -1555,6 +1591,7 @@ 3CAC4A712BB6AB3100A5D22E /* TFLabelNode.swift in Sources */, 529F91882BA6D7A7009551D9 /* SoldierUnit.swift in Sources */, BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */, + BA82C78A2BCD2B5D000515A0 /* MissionsEngine.swift in Sources */, 3CAC4A732BB6B61C00A5D22E /* PlayerRenderStage.swift in Sources */, 523AA3BF2BB88FBF0041E60D /* WizardUnit.swift in Sources */, 3C9955C02BA57E5500D33FA5 /* EventTarget.swift in Sources */, @@ -1571,11 +1608,11 @@ 5299D1322BC31050003EF746 /* AuthenticationManager.swift in Sources */, 3C3CBE012BB870950001B8A9 /* CGVector+Extensions.swift in Sources */, 5299D13C2BC3670E003EF746 /* LoginViewController.swift in Sources */, - BA82C7552BC8A441000515A0 /* AchievementStatsLinkDatabase.swift in Sources */, BA2F5ACD2BC8313900CBD8E9 /* StatisticUpdateLinkDatabase.swift in Sources */, 52A794092BBC35F20083C976 /* Constant.swift in Sources */, 3C9955C82BA5865C00D33FA5 /* ConcurrentEvent.swift in Sources */, 52F268702BB4B319009599AD /* GameModeViewController.swift in Sources */, + BA82C7862BCD2B44000515A0 /* MissionTypeWrapper.swift in Sources */, BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */, 527A07762BB3E4CF00CD9D08 /* GameState.swift in Sources */, 3C9955AD2BA483B100D33FA5 /* TFSystem.swift in Sources */, @@ -1630,6 +1667,7 @@ 9BD669682BAFDE5E00DC8C4C /* GridDelegate.swift in Sources */, 52DF5FEB2BA3400C00135367 /* TFAnimatableNode.swift in Sources */, 3C3CBDFF2BB8708A0001B8A9 /* CGPoint+Extensions.swift in Sources */, + BA82C7882BCD2B51000515A0 /* MissionsDatabase.swift in Sources */, 52DF5FEF2BA34EA000135367 /* TFEntity.swift in Sources */, BAFFB95D2BB978E500D8301F /* StorageEnums.swift in Sources */, 52DF5FED2BA34D0300135367 /* TFComponent.swift in Sources */, @@ -1656,6 +1694,7 @@ 5240D0AF2BB3B415004F1486 /* LifeEvent.swift in Sources */, 5295A2072BAA02FD005018A8 /* TFButtonNode.swift in Sources */, BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */, + BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */, 3CD37AA32BBEC0F900222D8A /* FirebaseRemoteEventPublisher.swift in Sources */, 3CAC4A6B2BB6992F00A5D22E /* TFNode.swift in Sources */, BA82C7732BCBF657000515A0 /* LocalMetadataManager.swift in Sources */, @@ -1664,6 +1703,7 @@ 3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */, 529190E32BBFB59B001D8821 /* StatePopupNode.swift in Sources */, 3CE951692BAEB719008B2785 /* RequestSpawnEvent.swift in Sources */, + BA82C7832BCD2B30000515A0 /* Mission.swift in Sources */, 3CFA72E72BC0398E0081337F /* RemoteEventManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift b/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift index 47789fb1..0cc76393 100644 --- a/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift @@ -25,6 +25,13 @@ struct DamageEvent: TFEvent { if let healthSystem = target.system(ofType: HealthSystem.self) { healthSystem.modifyHealth(for: entityId, with: -damage) } + + if let statsSystem = target.system(ofType: StatisticSystem.self) { + if player != .ownPlayer { + statsSystem.notify(for: self) + } + } + return EventOutput() } } diff --git a/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift b/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift index d026510f..9d3cf711 100644 --- a/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/GameEvents/KillEvent.swift @@ -25,11 +25,7 @@ struct KillEvent: TFEvent { } if let statsSystem = target.system(ofType: StatisticSystem.self) { - if player != .ownPlayer { - statsSystem.notify(for: self) - } else { - statsSystem.notify(for: DeathEvent(entityId)) - } + statsSystem.notify(for: self) } if let homeSystem = target.system(ofType: HomeSystem.self) { diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementStatsLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementStatsLinkDatabase.swift deleted file mode 100644 index 2c11064b..00000000 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementStatsLinkDatabase.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// AchievementStatsLinkDatabase.swift -// TowerForge -// -// Created by Rubesh on 12/4/24. -// - -import Foundation - -/// All achievements must be linked to a specific statistic -class AchievementStatsLinkDatabase { - var achievementLinks: [AchievementTypeWrapper: [Statistic]] = [:] -} diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift index 58b4548a..b5b90946 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsDatabase.swift @@ -8,7 +8,7 @@ import Foundation class AchievementsDatabase { - weak var achievementsDataDelegate: AchievementsDataDelegate? + weak var achievementsDataDelegate: InferenceDataDelegate? var achievements: [AchievementTypeWrapper: Achievement] = [:] init(achievements: [AchievementTypeWrapper: Achievement] = [:]) { diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift index 4cf61af9..fa1af276 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift @@ -7,7 +7,7 @@ import Foundation -protocol AchievementsDataDelegate: AnyObject { +protocol InferenceDataDelegate: AnyObject { var statisticsDatabase: StatisticsDatabase { get } } @@ -16,7 +16,7 @@ protocol AchievementsDataDelegate: AnyObject { /// /// It contains a Database of Achievements, and when notified by the StatisticsEngine, /// will update all Achievements therein contained. -class AchievementsEngine: InferenceEngine, AchievementsDataDelegate { +class AchievementsEngine: InferenceEngine, InferenceDataDelegate { unowned var statisticsEngine: StatisticsEngine var achievementsDatabase: AchievementsDatabase var statisticsDatabase: StatisticsDatabase { @@ -30,7 +30,7 @@ class AchievementsEngine: InferenceEngine, AchievementsDataDelegate { } func updateOnReceive() { - achievementsDatabase.updateAll(with: statisticsEngine.statistics) + achievementsDatabase.updateAll(with: statisticsDatabase) } } diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift index 31f46e48..94d1b534 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsFactory.swift @@ -20,7 +20,7 @@ class AchievementsFactory { availableAchievementTypes[String(describing: T.self)] = T.self } - static func getDefaultAchievementsDatabase(_ data: AchievementsDataDelegate?) -> AchievementsDatabase { + static func getDefaultAchievementsDatabase(_ data: InferenceDataDelegate?) -> AchievementsDatabase { let achievementsDatabase = AchievementsDatabase() achievementsDatabase.achievementsDataDelegate = data availableAchievementTypes.values.forEach { achievementsDatabase.addAchievement(for: $0.asType) } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift index 196f200f..edede5d2 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -23,5 +23,4 @@ final class FiftyKillsAchievement: Achievement { dependentStatistics.forEach { stats[$0.statisticName] = $0 } self.currentParameters = stats } - } diff --git a/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift new file mode 100644 index 00000000..f17547aa --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift @@ -0,0 +1,28 @@ +// +// ThousandDamageMission.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +final class GrandDamageMission: Mission { + static var isDone = false + + var missionName: String = "50 Kills" + var missionDescription: String = "Attain 50 total kills in TowerForge" + var currentParameters: [StatisticTypeWrapper: any Statistic] + + static var definedParameters: [StatisticTypeWrapper: Double] { + [ + TotalKillsStatistic.asType: 50.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/Mission.swift b/TowerForge/TowerForge/Metrics/Missions/Mission.swift new file mode 100644 index 00000000..760e19be --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Missions/Mission.swift @@ -0,0 +1,93 @@ +// +// Mission.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +protocol Mission: AnyObject { + var missionName: String { get } + var missionDescription: String { get } + + static var definedParameters: [StatisticTypeWrapper: Double] { get } + var currentParameters: [StatisticTypeWrapper: Statistic] { get set } + + var currentValues: [StatisticTypeWrapper: Double] { get } + var requiredValues: [StatisticTypeWrapper: Double] { get } + + var currentProgressRates: [StatisticTypeWrapper: Double] { get } + var overallProgressRate: Double { get } + var isComplete: Bool { get } + + /// Missions will become "done" once and continue to persist across game instances + static var isDone: Bool { get set } + + func loadStatistic(_ stat: Statistic) + func update(with stats: StatisticsDatabase) + + init(dependentStatistics: [Statistic]) + +} + +extension Mission { + + var isComplete: Bool { + currentProgressRates.values.allSatisfy { !$0.isLess(than: .unit) } + } + + static var asType: MissionTypeWrapper { + MissionTypeWrapper(type: Self.self) + } + + var requiredValues: [StatisticTypeWrapper: Double] { + Self.definedParameters + } + + func loadStatistic(_ stat: any Statistic) { + currentParameters[stat.statisticName] = stat + } + + func update(with stats: StatisticsDatabase) { + currentParameters.keys.forEach { + self.currentParameters[$0] = stats.statistics[$0] + } + + if self.isComplete { + Self.isDone = true + } + } + + var currentValues: [StatisticTypeWrapper: Double] { + var values: [StatisticTypeWrapper: Double] = [:] + currentParameters.keys.forEach { key in + if let currentStatistic = currentParameters[key] { + values[key] = currentStatistic.currentValue + } + } + + return values + } + + var currentProgressRates: [StatisticTypeWrapper: Double] { + var rates: [StatisticTypeWrapper: Double] = [:] + requiredValues.keys.forEach { key in + if let requiredValue = requiredValues[key], let currentValue = currentValues[key] { + rates[key] = currentValue / requiredValue + } + } + + return rates + } + + var overallProgressRate: Double { + currentProgressRates.values.reduce(into: .zero) { $0 += $1 } + .divide(by: Double(currentProgressRates.values.count)) + } + + var overallProgressRateRounded: Double { + overallProgressRate.rounded() + } + +} diff --git a/TowerForge/TowerForge/Metrics/Missions/MissionTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Missions/MissionTypeWrapper.swift new file mode 100644 index 00000000..cd652a66 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Missions/MissionTypeWrapper.swift @@ -0,0 +1,24 @@ +// +// MissionTypeWrapper.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +struct MissionTypeWrapper: Equatable, Hashable { + let type: Mission.Type + + var asString: String { + String(describing: type) + } + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.type == rhs.type + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(type)) + } +} diff --git a/TowerForge/TowerForge/Metrics/Missions/MissionsDatabase.swift b/TowerForge/TowerForge/Metrics/Missions/MissionsDatabase.swift new file mode 100644 index 00000000..681448c1 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Missions/MissionsDatabase.swift @@ -0,0 +1,36 @@ +// +// MissionsDatabase.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +class MissionsDatabase { + weak var missionsDataDelegate: InferenceDataDelegate? + var missions: [MissionTypeWrapper: Mission] = [:] + + init(missions: [MissionTypeWrapper: Mission] = [:]) { + self.missions = missions + } + + func addMission(for name: MissionTypeWrapper) { + if let stats = missionsDataDelegate?.statisticsDatabase { + missions[name] = MissionsFactory.createDefaultInstance(of: name.asString, with: stats) + } + } + + func getMission(for name: MissionTypeWrapper) -> Mission? { + missions[name] + } + + func setToDefault() { + missions = MissionsFactory.getDefaultMissionsDatabase(missionsDataDelegate).missions + } + + func updateAll(with stats: StatisticsDatabase) { + missions.values.forEach { $0.update(with: stats) } + } + +} diff --git a/TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift b/TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift new file mode 100644 index 00000000..017eb69c --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Missions/MissionsEngine.swift @@ -0,0 +1,27 @@ +// +// MissionsEngine.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +class MissionsEngine: InferenceEngine, InferenceDataDelegate { + unowned var statisticsEngine: StatisticsEngine + var missionsDatabase: MissionsDatabase + var statisticsDatabase: StatisticsDatabase { + statisticsEngine.statistics + } + + init(_ statisticsEngine: StatisticsEngine) { + self.statisticsEngine = statisticsEngine + self.missionsDatabase = MissionsDatabase() + missionsDatabase.missionsDataDelegate = self + } + + func updateOnReceive() { + missionsDatabase.updateAll(with: statisticsDatabase) + } + +} diff --git a/TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift b/TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift new file mode 100644 index 00000000..7c38a8e2 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Missions/MissionsFactory.swift @@ -0,0 +1,41 @@ +// +// MissionsFactory.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +class MissionsFactory { + + static var availableMissionTypes: [String: Mission.Type] = + [ + String(describing: GrandDamageMission.self): GrandDamageMission.self + ] + + static func registerMissionType(_ stat: T) { + availableMissionTypes[String(describing: T.self)] = T.self + } + + static func getDefaultMissionsDatabase(_ data: InferenceDataDelegate?) -> MissionsDatabase { + let missionsDatabase = MissionsDatabase() + missionsDatabase.missionsDataDelegate = data + availableMissionTypes.values.forEach { missionsDatabase.addMission(for: $0.asType) } + return missionsDatabase + } + + static func createDefaultInstance(of typeName: String, with db: StatisticsDatabase) -> Mission? { + guard let type = availableMissionTypes[typeName] else { + return nil + } + + let stats: [Statistic] = db.statistics.values.filter { key in + type.definedParameters.keys.contains { + $0 == key.statisticName + } + } + + return type.init(dependentStatistics: stats) + } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift new file mode 100644 index 00000000..c9fb7b69 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift @@ -0,0 +1,61 @@ +// +// TotalDamageStatistic.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +/// Total Damage dealt by the player in the course of the game +final class TotalDamageDealtStatistic: Statistic { + var permanentValue: Double = .zero + var currentValue: Double = .zero + + var statisticUpdateLinks: StatisticUpdateLinkDatabase { + self.getStatisticUpdateLinks() + } + + init(permanentValue: Double = .zero, + currentValue: Double = .zero) { + self.permanentValue = permanentValue + self.currentValue = currentValue + } + + /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { + let eventType = TFEventTypeWrapper(type: DamageEvent.self) + let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: ) } + let eventUpdateDictionary = [eventType: updateActor] + let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) + + return statsLink + }*/ + + func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { + let eventType = TFEventTypeWrapper(type: DamageEvent.self) + let eventUpdateClosure: (Statistic, DamageEvent?) -> Void = { statistic, event in + guard let event = event, event.player != .ownPlayer else { + return + } + statistic.updateCurrentValue(by: Double(event.damage)) + Logger.log("Updating statistic with event detail: \(String(describing: event))", self) + } + + let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) + let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) + + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] + statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper + return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) + + } + + convenience init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: StatisticCodingKeys.self) + _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) + let value = try container.decode(Double.self, forKey: .permanentValue) + let current = try container.decode(Double.self, forKey: .currentValue) + + self.init(permanentValue: value, currentValue: current) + } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 0c9e3d7f..cf9cedb4 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -21,13 +21,32 @@ final class TotalDeathsStatistic: Statistic { self.currentValue = currentValue } - func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { + /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: DeathEvent.self) let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } let eventUpdateDictionary = [eventType: updateActor] let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) return statsLink + }*/ + + func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { + let eventType = TFEventTypeWrapper(type: KillEvent.self) + let eventUpdateClosure: (Statistic, KillEvent?) -> Void = { statistic, event in + guard let event = event, event.player == .ownPlayer else { + return + } + statistic.updateCurrentValue(by: 1.0) + Logger.log("Updating statistic with event detail: \(String(describing: event))", self) + } + + let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) + let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) + + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] + statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper + return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) + } convenience init(from decoder: any Decoder) throws { diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index 827f67ea..80b3ba45 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -21,13 +21,29 @@ final class TotalGamesStatistic: Statistic { self.currentValue = currentValue } - func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { + /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: GameStartEvent.self) let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } let eventUpdateDictionary = [eventType: updateActor] let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) return statsLink + }*/ + + func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { + let eventType = TFEventTypeWrapper(type: GameStartEvent.self) + let eventUpdateClosure: (Statistic, GameStartEvent?) -> Void = { statistic, event in + statistic.updateCurrentValue(by: 1.0) + Logger.log("Updating statistic with event detail: \(String(describing: event))", self) + } + + let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) + let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) + + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] + statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper + return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) + } convenience init(from decoder: any Decoder) throws { diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index 9eb165c5..e80406fb 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -23,11 +23,20 @@ final class TotalKillsStatistic: Statistic { func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: KillEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } - let eventUpdateDictionary = [eventType: updateActor] - let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) - - return statsLink + let eventUpdateClosure: (Statistic, KillEvent?) -> Void = { statistic, event in + guard let event = event, event.player != .ownPlayer else { + return + } + statistic.updateCurrentValue(by: 1.0) + Logger.log("Updating statistic with event detail: \(String(describing: event))", self) + } + + let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) + let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) + + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] + statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper + return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) } convenience init(from decoder: any Decoder) throws { diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 33f1d9e7..1423a9cc 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -21,7 +21,7 @@ protocol Statistic: AnyObject, Codable { var currentTotalValue: Double { get } /// The principal interface for modifying the Statistic - func update(for eventType: TFEventTypeWrapper) + func update(for event: T) /// Returns a StatisticUpdateLinkDatabase pertaining to this Statistic. /// Conforming Statistic types will have to implement their own links between event @@ -108,14 +108,29 @@ extension Statistic { self.getStatisticUpdateLinks().getAllEventTypes() } - /// Updates the statistic according to an UpdateActor that is retrieved from the - func update(for eventType: TFEventTypeWrapper) { - guard let updateLink = self.getStatisticUpdateLinks().getStatisticUpdateActor(for: eventType) else { - return + /// Updates the statistic according to an UpdateActor that is retrieved from the + /*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) { + let eventType = T.asType + if let actor = self.getStatisticUpdateLinks().getStatisticUpdateActor(for: eventType) { + Logger.log("Value update for eventType \(eventType)", self) + actor.updateStatistic(statistic: self, withEvent: event) + } else { + Logger.log("No actor registered for event type \(eventType)", self) } - - updateLink?(self) - Logger.log("Value update for eventType \(eventType)", self) } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift index f47f1c1c..43b56d9b 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift @@ -7,14 +7,43 @@ import Foundation +// typealias StatisticUpdateActor = ((Statistic, (any TFEvent)?) -> Void)? /// This struct contains pairs that each Statistic will refer to, /// to act accordingly when an EventType is executed elsewhere. -typealias StatisticUpdateActor = ((Statistic) -> Void)? +class StatisticUpdateActor { + var action: ((Statistic, T?) -> Void)? + + init(action: ((Statistic, T?) -> Void)? = nil) { + self.action = action + } +} + +protocol AnyStatisticUpdateActor { + func updateStatistic(statistic: Statistic, withEvent event: Any?) +} + +struct AnyStatisticUpdateActorWrapper: AnyStatisticUpdateActor { + private let _updateStatistic: (Statistic, T?) -> Void + + init>(_ actor: U) { + self._updateStatistic = actor.action! + } + + func updateStatistic(statistic: Statistic, withEvent event: Any?) { + if let event = event as? T { + _updateStatistic(statistic, event) + } else { + // Handle the case where event cannot be cast to T, possibly call with nil + Logger.log("Warning: Attempted to pass an event of the wrong type to a StatisticUpdateActor") + _updateStatistic(statistic, nil) + } + } +} class StatisticUpdateLinkDatabase { - var statisticUpdateLinks: [TFEventTypeWrapper: StatisticUpdateActor] + var statisticUpdateLinks: [TFEventTypeWrapper: AnyStatisticUpdateActor] - init(statisticUpdateLinks: [TFEventTypeWrapper: StatisticUpdateActor] = [:]) { + init(statisticUpdateLinks: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:]) { self.statisticUpdateLinks = statisticUpdateLinks } @@ -33,12 +62,12 @@ class StatisticUpdateLinkDatabase { } func addStatisticUpdateActor(for eventType: T.Type, - with statisticUpdateActor: StatisticUpdateActor) { + with statisticUpdateActor: AnyStatisticUpdateActor) { let wrappedEvent = TFEventTypeWrapper(type: eventType) statisticUpdateLinks[wrappedEvent] = statisticUpdateActor } - func getStatisticUpdateActor(for eventType: TFEventTypeWrapper) -> StatisticUpdateActor? { + func getStatisticUpdateActor(for eventType: TFEventTypeWrapper) -> AnyStatisticUpdateActor? { statisticUpdateLinks[eventType] } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 139492a3..f645c8a7 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -62,7 +62,8 @@ class StatisticsEngine { return } - stats.forEach { $0.update(for: eventType) } + // stats.forEach { $0.update(for: eventType) } + stats.forEach { $0.update(for: event) } saveStatistics() } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift index aac091a4..3024c66e 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift @@ -11,30 +11,33 @@ class StatisticsFactory { static var eventStatisticLinks: [TFEventTypeWrapper: [StatisticTypeWrapper]] = [ - KillEvent.asType: [TotalKillsStatistic.asType], + KillEvent.asType: [TotalKillsStatistic.asType, TotalDeathsStatistic.asType], GameStartEvent.asType: [TotalGamesStatistic.asType], - DeathEvent.asType: [TotalDeathsStatistic.asType] + DamageEvent.asType: [TotalDamageDealtStatistic.asType] ] static var availableStatisticsTypes: [String: Statistic.Type] = [ String(describing: TotalKillsStatistic.self): TotalKillsStatistic.self, String(describing: TotalGamesStatistic.self): TotalGamesStatistic.self, - String(describing: TotalDeathsStatistic.self): TotalDeathsStatistic.self + String(describing: TotalDeathsStatistic.self): TotalDeathsStatistic.self, + String(describing: TotalDamageDealtStatistic.self): TotalDamageDealtStatistic.self ] static var statisticDecoder: [String: (Decoder) throws -> Statistic] = [ TotalKillsStatistic.asType.asString: { decoder in try TotalKillsStatistic(from: decoder) }, TotalGamesStatistic.asType.asString: { decoder in try TotalGamesStatistic(from: decoder) }, - TotalDeathsStatistic.asType.asString: { decoder in try TotalDeathsStatistic(from: decoder) } + TotalDeathsStatistic.asType.asString: { decoder in try TotalDeathsStatistic(from: decoder) }, + TotalDamageDealtStatistic.asType.asString: { decoder in try TotalDamageDealtStatistic(from: decoder) } ] static var defaultStatisticGenerator: [StatisticTypeWrapper: () -> Statistic] = [ TotalKillsStatistic.asType: { TotalKillsStatistic() }, TotalGamesStatistic.asType: { TotalGamesStatistic() }, - TotalDeathsStatistic.asType: { TotalDeathsStatistic() } + TotalDeathsStatistic.asType: { TotalDeathsStatistic() }, + TotalDamageDealtStatistic.asType: { TotalDamageDealtStatistic() } ] static func registerStatisticType(_ stat: T) { From ad944eb517e724a15332bc54a92d1f6bd3753b91 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 01:35:51 +0800 Subject: [PATCH 37/54] Update metadata to modify properly --- .../TowerForge.xcodeproj/project.pbxproj | 8 +++- .../AppMain/Application/AppDelegate.swift | 23 ++-------- .../Implemented/TotalDeathsStatistic.swift | 5 ++- .../Statistics/StatisticUpdateActor.swift | 45 +++++++++++++++++++ .../StatisticUpdateLinkDatabase.swift | 33 -------------- .../Storage/LocalMetadataManager.swift | 2 +- .../Storage/RemoteMetadataManager.swift | 6 +-- .../Storage/RemoteStorageManager.swift | 2 + 8 files changed, 63 insertions(+), 61 deletions(-) create mode 100644 TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index da5cfe77..31efa7ec 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -228,6 +228,7 @@ BA82C78C2BCD2B68000515A0 /* MissionsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78B2BCD2B68000515A0 /* MissionsFactory.swift */; }; BA82C78E2BCD2D2B000515A0 /* GrandDamageMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78D2BCD2D2B000515A0 /* GrandDamageMission.swift */; }; BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */; }; + BA82C7922BCD6579000515A0 /* StatisticUpdateActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -481,6 +482,7 @@ BA82C78B2BCD2B68000515A0 /* MissionsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionsFactory.swift; sourceTree = ""; }; BA82C78D2BCD2D2B000515A0 /* GrandDamageMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrandDamageMission.swift; sourceTree = ""; }; BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDamageDealtStatistic.swift; sourceTree = ""; }; + BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticUpdateActor.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -1045,6 +1047,7 @@ BA82C73F2BC8674A000515A0 /* StatisticsFactory.swift */, BA2F5ACA2BC82CCC00CBD8E9 /* EventStatisticLinkDatabase.swift */, BA2F5ACC2BC8313900CBD8E9 /* StatisticUpdateLinkDatabase.swift */, + BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */, ); path = Statistics; sourceTree = ""; @@ -1323,10 +1326,10 @@ BA82C76E2BCBDE91000515A0 /* Metadata.swift */, BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */, BA82C76A2BCBD682000515A0 /* StorageManager.swift */, - BA82C7722BCBF657000515A0 /* LocalMetadataManager.swift */, - BA82C77C2BCD07F4000515A0 /* RemoteMetadataManager.swift */, BA82C7642BCBC868000515A0 /* LocalStorageManager.swift */, + BA82C7722BCBF657000515A0 /* LocalMetadataManager.swift */, BA82C7682BCBD21F000515A0 /* RemoteStorageManager.swift */, + BA82C77C2BCD07F4000515A0 /* RemoteMetadataManager.swift */, ); path = Storage; sourceTree = ""; @@ -1679,6 +1682,7 @@ 5240D0A22BB33183004F1486 /* DeathMatchMode.swift in Sources */, BA2F5AC52BC8143E00CBD8E9 /* TotalKillsStatistic.swift in Sources */, 3CAC4A6F2BB6A4F200A5D22E /* LabelRenderStage.swift in Sources */, + BA82C7922BCD6579000515A0 /* StatisticUpdateActor.swift in Sources */, BA82C75F2BCB1528000515A0 /* AchievementsDatabase.swift in Sources */, 3CD37A9F2BBEBFFB00222D8A /* TFRemoteEventPublisher.swift in Sources */, 3C0B608D2BB2B84000FFECB4 /* ContactComponent.swift in Sources */, diff --git a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift index 76d76ae0..33c48971 100644 --- a/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift +++ b/TowerForge/TowerForge/AppMain/Application/AppDelegate.swift @@ -16,32 +16,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - /// Initialize all local storage - StorageManager.initializeAllStorage() - /// Connect to Firebase FirebaseApp.configure() + /// Initialize all local storage + StorageManager.initializeAllStorage() + /// Prepare audio player to begin playing music AudioManager.shared.setupAllAudioPlayers() - /// Temporary tests - Logger.log("StatisticTypeWrapper: \(TotalGamesStatistic.asType)", self) - Logger.log("Wrapper.asString: \(TotalGamesStatistic.asType.asString)", self) - Logger.log("Wrapper.type: \(TotalGamesStatistic.asType.type)", self) - - let string = "TotalGamesStatistic" - - Logger.log("TotalGamesStatistics as represented by NSClass is " - + "\(String(describing: NSClassFromString("TowerForge.TotalGamesStatistic")))", self) - - guard let type = string.asTFClassFromString as? Statistic.Type else { - Logger.log("Failed", self) - return true - } - - Logger.log("Success: \(type)", self) - Logger.log(Bundle.main.projectName) return true } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index cf9cedb4..0da35917 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -30,6 +30,8 @@ final class TotalDeathsStatistic: Statistic { return statsLink }*/ + /// The total deaths statistic no longer requires the DeathEvent, it is able + /// to directly parse a KillEvent to determine whe func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: KillEvent.self) let eventUpdateClosure: (Statistic, KillEvent?) -> Void = { statistic, event in @@ -42,7 +44,8 @@ final class TotalDeathsStatistic: Statistic { let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) - + // let anyStatisticUpdateActorWrapper = + // AnyStatisticUpdateActorWrapper(updateStatistic: eventUpdateClosure) var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift new file mode 100644 index 00000000..2ac4107f --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift @@ -0,0 +1,45 @@ +// +// StatisticUpdateActor.swift +// TowerForge +// +// Created by Rubesh on 15/4/24. +// + +import Foundation + +protocol AnyStatisticUpdateActor { + func updateStatistic(statistic: Statistic, withEvent event: Any?) +} + +// typealias StatisticUpdateActor = ((Statistic, (any TFEvent)?) -> Void)? +/// This struct contains pairs that each Statistic will refer to, +/// to act accordingly when an EventType is executed elsewhere. +class StatisticUpdateActor { + var action: ((Statistic, T?) -> Void)? + + init(action: ((Statistic, T?) -> Void)? = nil) { + self.action = action + } +} + +struct AnyStatisticUpdateActorWrapper: AnyStatisticUpdateActor { + private let _updateStatistic: ((Statistic, T?) -> Void)? + + init>(_ actor: U) { + self._updateStatistic = actor.action + } + + init(updateStatistic: ((Statistic, T?) -> Void)?) { + self._updateStatistic = updateStatistic + } + + func updateStatistic(statistic: Statistic, withEvent event: Any?) { + if let event = event as? T { + _updateStatistic?(statistic, event) + } else { + // Handle the case where event cannot be cast to T, possibly call with nil + Logger.log("Warning: Attempted to pass an event of the wrong type to a StatisticUpdateActor") + _updateStatistic?(statistic, nil) + } + } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift index 43b56d9b..1fbeedde 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateLinkDatabase.swift @@ -7,39 +7,6 @@ import Foundation -// typealias StatisticUpdateActor = ((Statistic, (any TFEvent)?) -> Void)? -/// This struct contains pairs that each Statistic will refer to, -/// to act accordingly when an EventType is executed elsewhere. -class StatisticUpdateActor { - var action: ((Statistic, T?) -> Void)? - - init(action: ((Statistic, T?) -> Void)? = nil) { - self.action = action - } -} - -protocol AnyStatisticUpdateActor { - func updateStatistic(statistic: Statistic, withEvent event: Any?) -} - -struct AnyStatisticUpdateActorWrapper: AnyStatisticUpdateActor { - private let _updateStatistic: (Statistic, T?) -> Void - - init>(_ actor: U) { - self._updateStatistic = actor.action! - } - - func updateStatistic(statistic: Statistic, withEvent event: Any?) { - if let event = event as? T { - _updateStatistic(statistic, event) - } else { - // Handle the case where event cannot be cast to T, possibly call with nil - Logger.log("Warning: Attempted to pass an event of the wrong type to a StatisticUpdateActor") - _updateStatistic(statistic, nil) - } - } -} - class StatisticUpdateLinkDatabase { var statisticUpdateLinks: [TFEventTypeWrapper: AnyStatisticUpdateActor] diff --git a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift index 44e402f1..d5449fbd 100644 --- a/TowerForge/TowerForge/Storage/LocalMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/LocalMetadataManager.swift @@ -44,7 +44,7 @@ class LocalMetadataManager { metadata.lastUpdated = Date.now saveMetadataToLocalStorage(metadata) Logger.log("Metadata updated at: \(metadata.lastUpdated)", self) - RemoteMetadataManager.updateMetadataInFirebase() + // RemoteMetadataManager.updateMetadataInFirebase() } static func saveMetadataToLocalStorage(_ metadata: Metadata) { diff --git a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift index b487212b..f4f1be23 100644 --- a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift @@ -61,11 +61,10 @@ class RemoteMetadataManager { var existingRemoteMetadata: Metadata? Self.loadMetadataFromFirebase { metadata, error in - if error != nil { + if let error = error { Logger.log("Error occured while loading metadata from firebase for update", self) return } - existingRemoteMetadata = metadata } @@ -78,12 +77,11 @@ class RemoteMetadataManager { } Self.saveMetadataToFirebase(existingRemoteMetadata) { error in - if error != nil { + if let error = error { Logger.log("Error occured while saving metadata to firebase for update", self) return } } - } static func loadMetadataFromFirebase(completion: @escaping (Metadata?, Error?) -> Void) { diff --git a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift index a3556fab..062d0846 100644 --- a/TowerForge/TowerForge/Storage/RemoteStorageManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteStorageManager.swift @@ -110,6 +110,8 @@ class RemoteStorageManager { Logger.log("Error encoding StatisticsDatabase: \(error)", StatisticsDatabase.self) completion(error) } + + RemoteMetadataManager.updateMetadataInFirebase() } /// Deletes the player's statistics database from Firebase From 0264ca6a875d0ad4e7840fbe6b31c461b3fae0f9 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 01:50:16 +0800 Subject: [PATCH 38/54] Update metadata handling --- .../Storage/RemoteMetadataManager.swift | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift index f4f1be23..1a1feccb 100644 --- a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift @@ -38,7 +38,7 @@ class RemoteMetadataManager { return } - if metadata != nil { + if let metadata = metadata { Logger.log("Metadata database already initialized.", self) return } @@ -58,27 +58,26 @@ class RemoteMetadataManager { } static func updateMetadataInFirebase() { - var existingRemoteMetadata: Metadata? + 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", self) + Logger.log("Error occured while loading metadata from firebase for update --- \(error)", self) return } - existingRemoteMetadata = metadata - } - - existingRemoteMetadata?.lastUpdated = Date.now - Logger.log("Metadata updated at: \(String(describing: existingRemoteMetadata?.lastUpdated))", self) - guard let existingRemoteMetadata = existingRemoteMetadata else { - Logger.log("Error occured while updating metadata", 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", self) + Logger.log("Error occured while saving metadata to firebase for update --- \(error)", self) return } } From 5279d9c004c564d9efcfa7c3e272f570f8dfde7c Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 01:51:32 +0800 Subject: [PATCH 39/54] Fix style --- TowerForge/TowerForge/Storage/RemoteMetadataManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift index 1a1feccb..7f4dc835 100644 --- a/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift +++ b/TowerForge/TowerForge/Storage/RemoteMetadataManager.swift @@ -39,7 +39,7 @@ class RemoteMetadataManager { } if let metadata = metadata { - Logger.log("Metadata database already initialized.", self) + Logger.log("Metadata database already initialized : \(metadata)", self) return } From 9639e0ba9515604083524712d72f61d0be823df9 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 02:35:52 +0800 Subject: [PATCH 40/54] Update missions api --- .../TowerForge.xcodeproj/project.pbxproj | 6 ++++- .../Commons/Enums/StorageEnums.swift | 1 + .../Achievements/AchievementsEngine.swift | 4 ---- .../Inference/InferenceDataDelegate.swift | 12 ++++++++++ .../Implemented/GrandDamageMission.swift | 7 +++--- .../TowerForge/Metrics/Missions/Mission.swift | 9 +------- .../TotalDamageDealtStatistic.swift | 7 ++++-- .../Implemented/TotalDeathsStatistic.swift | 8 +++++-- .../Implemented/TotalGamesStatistic.swift | 9 ++++++-- .../Implemented/TotalKillsStatistic.swift | 11 ++++++--- .../Metrics/Statistics/Statistic.swift | 23 ++++++++++++++++--- .../StatisticsDatabase+Codable.swift | 4 +++- .../Statistics/StatisticsFactory.swift | 7 ++++-- 13 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 TowerForge/TowerForge/Metrics/Inference/InferenceDataDelegate.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 31efa7ec..9040e0a2 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -229,6 +229,7 @@ BA82C78E2BCD2D2B000515A0 /* GrandDamageMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78D2BCD2D2B000515A0 /* GrandDamageMission.swift */; }; BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */; }; BA82C7922BCD6579000515A0 /* StatisticUpdateActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */; }; + BA82C7942BCDAA83000515A0 /* InferenceDataDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -483,6 +484,7 @@ BA82C78D2BCD2D2B000515A0 /* GrandDamageMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrandDamageMission.swift; sourceTree = ""; }; BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDamageDealtStatistic.swift; sourceTree = ""; }; BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticUpdateActor.swift; sourceTree = ""; }; + BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceDataDelegate.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -966,8 +968,8 @@ BA2F5ABF2BC80BD200CBD8E9 /* Metrics */ = { isa = PBXGroup; children = ( - BA82C7812BCD2B20000515A0 /* Missions */, BA82C77E2BCD2652000515A0 /* Inference */, + BA82C7812BCD2B20000515A0 /* Missions */, BA82C7562BCAB482000515A0 /* Statistics */, BA2F5ABC2BC80A2300CBD8E9 /* Achievements */, ); @@ -1057,6 +1059,7 @@ children = ( BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */, BA82C77F2BCD284D000515A0 /* InferenceEngineFactory.swift */, + BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */, ); path = Inference; sourceTree = ""; @@ -1704,6 +1707,7 @@ BA82C7732BCBF657000515A0 /* LocalMetadataManager.swift in Sources */, 3CBE73012BC8D69A00CC446A /* RemoteLifeEvent.swift in Sources */, 3CAC4A6D2BB6A13B00A5D22E /* PositionRenderStage.swift in Sources */, + BA82C7942BCDAA83000515A0 /* InferenceDataDelegate.swift in Sources */, 3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */, 529190E32BBFB59B001D8821 /* StatePopupNode.swift in Sources */, 3CE951692BAEB719008B2785 /* RequestSpawnEvent.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift index fc8d453c..bf169c07 100644 --- a/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift +++ b/TowerForge/TowerForge/Commons/Enums/StorageEnums.swift @@ -23,6 +23,7 @@ class StorageEnums { case statisticName case permanentValue case currentValue + case maximumCurrentValue } enum StorageLocation: String, Codable { diff --git a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift index fa1af276..62534cb0 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/AchievementsEngine.swift @@ -7,10 +7,6 @@ import Foundation -protocol InferenceDataDelegate: AnyObject { - var statisticsDatabase: StatisticsDatabase { get } -} - /// The AchievementsEngine is an InferenceEngine that interprets permanent /// information received from the Statistics component. /// diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceDataDelegate.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceDataDelegate.swift new file mode 100644 index 00000000..080c16c9 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceDataDelegate.swift @@ -0,0 +1,12 @@ +// +// InferenceDataDelegate.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + +protocol InferenceDataDelegate: AnyObject { + var statisticsDatabase: StatisticsDatabase { get } +} diff --git a/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift index f17547aa..7c088f81 100644 --- a/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift +++ b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift @@ -8,15 +8,14 @@ import Foundation final class GrandDamageMission: Mission { - static var isDone = false - var missionName: String = "50 Kills" - var missionDescription: String = "Attain 50 total kills in TowerForge" + var missionName: String = "Mission: 1000 Damage" + var missionDescription: String = "Attain 1000 Damage in 1 game" var currentParameters: [StatisticTypeWrapper: any Statistic] static var definedParameters: [StatisticTypeWrapper: Double] { [ - TotalKillsStatistic.asType: 50.0 + TotalDamageDealtStatistic.asType: 1_000.0 ] } diff --git a/TowerForge/TowerForge/Metrics/Missions/Mission.swift b/TowerForge/TowerForge/Metrics/Missions/Mission.swift index 760e19be..fd39dca9 100644 --- a/TowerForge/TowerForge/Metrics/Missions/Mission.swift +++ b/TowerForge/TowerForge/Metrics/Missions/Mission.swift @@ -21,9 +21,6 @@ protocol Mission: AnyObject { var overallProgressRate: Double { get } var isComplete: Bool { get } - /// Missions will become "done" once and continue to persist across game instances - static var isDone: Bool { get set } - func loadStatistic(_ stat: Statistic) func update(with stats: StatisticsDatabase) @@ -53,17 +50,13 @@ extension Mission { currentParameters.keys.forEach { self.currentParameters[$0] = stats.statistics[$0] } - - if self.isComplete { - Self.isDone = true - } } var currentValues: [StatisticTypeWrapper: Double] { var values: [StatisticTypeWrapper: Double] = [:] currentParameters.keys.forEach { key in if let currentStatistic = currentParameters[key] { - values[key] = currentStatistic.currentValue + values[key] = currentStatistic.maximumCurrentValue } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift index c9fb7b69..615ccdc2 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift @@ -11,15 +11,18 @@ import Foundation final class TotalDamageDealtStatistic: Statistic { var permanentValue: Double = .zero var currentValue: Double = .zero + var maximumCurrentValue: Double = .zero var statisticUpdateLinks: StatisticUpdateLinkDatabase { self.getStatisticUpdateLinks() } init(permanentValue: Double = .zero, - currentValue: Double = .zero) { + currentValue: Double = .zero, + maxCurrentValue: Double = .zero) { self.permanentValue = permanentValue self.currentValue = currentValue + self.maximumCurrentValue = maxCurrentValue } /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { @@ -57,5 +60,5 @@ final class TotalDamageDealtStatistic: Statistic { let current = try container.decode(Double.self, forKey: .currentValue) self.init(permanentValue: value, currentValue: current) - } + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 0da35917..c9dec443 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -8,17 +8,21 @@ import Foundation final class TotalDeathsStatistic: Statistic { + var permanentValue: Double = .zero var currentValue: Double = .zero + var maximumCurrentValue: Double = .zero var statisticUpdateLinks: StatisticUpdateLinkDatabase { self.getStatisticUpdateLinks() } init(permanentValue: Double = .zero, - currentValue: Double = .zero) { + currentValue: Double = .zero, + maxCurrentValue: Double = .zero) { self.permanentValue = permanentValue self.currentValue = currentValue + self.maximumCurrentValue = maxCurrentValue } /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { @@ -59,6 +63,6 @@ final class TotalDeathsStatistic: Statistic { let current = try container.decode(Double.self, forKey: .currentValue) self.init(permanentValue: value, currentValue: current) - } + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index 80b3ba45..0a08696f 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -8,17 +8,21 @@ import Foundation final class TotalGamesStatistic: Statistic { + var permanentValue: Double = .zero var currentValue: Double = .zero + var maximumCurrentValue: Double var statisticUpdateLinks: StatisticUpdateLinkDatabase { self.getStatisticUpdateLinks() } init(permanentValue: Double = .zero, - currentValue: Double = .zero) { + currentValue: Double = .zero, + maxCurrentValue: Double = .zero) { self.permanentValue = permanentValue self.currentValue = currentValue + self.maximumCurrentValue = maxCurrentValue } /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { @@ -51,8 +55,9 @@ final class TotalGamesStatistic: Statistic { _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) let value = try container.decode(Double.self, forKey: .permanentValue) let current = try container.decode(Double.self, forKey: .currentValue) + let max = try container.decode(Double.self, forKey: .maximumCurrentValue) - self.init(permanentValue: value, currentValue: current) + self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index e80406fb..0626c9ff 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -8,17 +8,21 @@ import Foundation final class TotalKillsStatistic: Statistic { + var permanentValue: Double = .zero var currentValue: Double = .zero + var maximumCurrentValue: Double = .zero var statisticUpdateLinks: StatisticUpdateLinkDatabase { self.getStatisticUpdateLinks() } init(permanentValue: Double = .zero, - currentValue: Double = .zero) { + currentValue: Double = .zero, + maxCurrentValue: Double = .zero) { self.permanentValue = permanentValue self.currentValue = currentValue + self.maximumCurrentValue = maxCurrentValue } func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { @@ -44,7 +48,8 @@ final class TotalKillsStatistic: Statistic { _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) let value = try container.decode(Double.self, forKey: .permanentValue) let current = try container.decode(Double.self, forKey: .currentValue) + let max = try container.decode(Double.self, forKey: .maximumCurrentValue) - self.init(permanentValue: value, currentValue: current) - } + self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) + } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 1423a9cc..8aa8f8ba 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -17,6 +17,9 @@ protocol Statistic: AnyObject, Codable { /// The changes incurred to the statistic in the course of the current game sequence var currentValue: Double { get set } + /// Stores the maximum currentValue reached for this statistic in the course of the game + var maximumCurrentValue: Double { get set } + /// A computed sum of the permanent value and the current value of the statistic var currentTotalValue: Double { get } @@ -29,14 +32,14 @@ protocol Statistic: AnyObject, Codable { func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase func getEventLinksOnly() -> [TFEventTypeWrapper] - init(permanentValue: Double, currentValue: Double) + init(permanentValue: Double, currentValue: Double, maxCurrentValue: Double) } extension Statistic { init() { - self.init(permanentValue: .zero, currentValue: .zero) + self.init(permanentValue: .zero, currentValue: .zero, maxCurrentValue: .zero) } static func equals(lhs: Self, rhs: Self) -> Bool { @@ -83,6 +86,10 @@ extension Statistic { /// Increments the current value of the statistic by the given amount func updateCurrentValue(by value: Double) { currentValue += value + + if currentValue > maximumCurrentValue { + maximumCurrentValue = currentValue + } } /// Generic increment of the permanent value @@ -108,7 +115,6 @@ extension Statistic { self.getStatisticUpdateLinks().getAllEventTypes() } - /// Updates the statistic according to an UpdateActor that is retrieved from the /*func update(for event: T) { let eventType = T.asType guard let updateLink = self.getStatisticUpdateLinks().getStatisticUpdateActor(for: eventType) else { @@ -141,6 +147,17 @@ extension Statistic { try container.encode(statisticName, forKey: .statisticName) try container.encode(permanentValue, forKey: .permanentValue) try container.encode(currentValue, forKey: .currentValue) + try container.encode(maximumCurrentValue, forKey: .maximumCurrentValue) + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: StatisticCodingKeys.self) + _ = try container.decode(StatisticTypeWrapper.self, forKey: .statisticName) + let value = try container.decode(Double.self, forKey: .permanentValue) + let current = try container.decode(Double.self, forKey: .currentValue) + let max = try container.decode(Double.self, forKey: .maximumCurrentValue) + + self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) } /* TODO: Fix new implementation diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift index 2a1404e0..3aae3837 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Codable.swift @@ -69,10 +69,12 @@ extension StatisticsDatabase: Codable { let type = try statObjectDict.decode(String.self, forKey: .statisticName) let permanentValue = try statObjectDict.decode(Double.self, forKey: .permanentValue) let currentValue = try statObjectDict.decode(Double.self, forKey: .currentValue) + let maxValue = try statObjectDict.decode(Double.self, forKey: .maximumCurrentValue) guard let instance = StatisticsFactory.createInstance(of: type, permanentValue: permanentValue, - currentValue: currentValue) else { + currentValue: currentValue, + max: maxValue) else { throw DecodingError.dataCorruptedError(forKey: .statisticName, in: statObjectDict, diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift index 3024c66e..1458051b 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift @@ -58,10 +58,13 @@ class StatisticsFactory { return statsDatabase } - static func createInstance(of typeName: String, permanentValue: Double, currentValue: Double) -> Statistic? { + static func createInstance(of typeName: String, + permanentValue: Double, + currentValue: Double, + max: Double) -> Statistic? { guard let type = availableStatisticsTypes[typeName] else { return nil } - return type.init(permanentValue: permanentValue, currentValue: currentValue) + return type.init(permanentValue: permanentValue, currentValue: currentValue, maxCurrentValue: max) } } From 0d4493f062e6a6000fd8405afb79f64a1027f533 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 02:53:43 +0800 Subject: [PATCH 41/54] Clean up code --- .../Inference/InferenceEngineFactory.swift | 4 +++- .../Implemented/GrandDamageMission.swift | 1 - .../Metrics/Statistics/Statistic.swift | 22 +++++++++---------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift index 8edce498..9ba1caf9 100644 --- a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift @@ -10,6 +10,8 @@ import Foundation class InferenceEngineFactory { static var availableInferenceEngines: [(StatisticsEngine) -> any InferenceEngine] = - [ { stats in AchievementsEngine(stats) } + [ + { stats in AchievementsEngine(stats) }, + { stats in MissionsEngine(stats) } ] } diff --git a/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift index 7c088f81..c5bb4976 100644 --- a/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift +++ b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift @@ -8,7 +8,6 @@ import Foundation final class GrandDamageMission: Mission { - var missionName: String = "Mission: 1000 Damage" var missionDescription: String = "Attain 1000 Damage in 1 game" var currentParameters: [StatisticTypeWrapper: any Statistic] diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 8aa8f8ba..7c4a409f 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -109,6 +109,16 @@ extension Statistic { updatePermanentValue(by: currentValue) currentValue = .zero } + + func resetMaxValue() { + maximumCurrentValue = .zero + } + + func resetStatistic() { + permanentValue = .zero + maximumCurrentValue = .zero + currentValue = .zero + } /// Returns the event types for which this Statistic would be involved func getEventLinksOnly() -> [TFEventTypeWrapper] { @@ -159,16 +169,4 @@ extension Statistic { self.init(permanentValue: value, currentValue: current, maxCurrentValue: max) } - - /* TODO: Fix new implementation - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: StorageEnums.DynamicCodingKeys.self) - guard let statKey = DynamicCodingKeys(stringValue: statisticName.asString) else { - Logger.log("Encoding statistic failed", self) - return - } - var nestedContainer = container.nestedContainer(keyedBy: StatisticCodingKeys.self, forKey: statKey) - try nestedContainer.encode(permanentValue, forKey: .permanentValue) - try nestedContainer.encode(currentValue, forKey: .currentValue) - }*/ } From 0b32fd4de16036fe4fbd5815c0725650a4a21c6f Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 03:28:00 +0800 Subject: [PATCH 42/54] Add rank generator --- .../TowerForge.xcodeproj/project.pbxproj | 18 ++++++++++++++- .../Commons/Enums/RankingEnums.swift | 22 +++++++++++++++++++ .../Inference/InferenceEngineFactory.swift | 4 +--- .../Metrics/Ranking/RankGenerator.swift | 21 ++++++++++++++++++ .../TotalDamageDealtStatistic.swift | 1 + .../Implemented/TotalGamesStatistic.swift | 1 + .../Implemented/TotalKillsStatistic.swift | 1 + .../Metrics/Statistics/Statistic.swift | 15 +++++++++++-- .../Statistics/StatisticUpdateActor.swift | 1 + 9 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 TowerForge/TowerForge/Commons/Enums/RankingEnums.swift create mode 100644 TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 9040e0a2..8729d7e3 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -230,6 +230,8 @@ BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */; }; BA82C7922BCD6579000515A0 /* StatisticUpdateActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */; }; BA82C7942BCDAA83000515A0 /* InferenceDataDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */; }; + BA82C7972BCDAF8A000515A0 /* RankGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */; }; + BA82C7992BCDAFE3000515A0 /* RankingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7982BCDAFE3000515A0 /* RankingEnums.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -485,6 +487,8 @@ BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDamageDealtStatistic.swift; sourceTree = ""; }; BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticUpdateActor.swift; sourceTree = ""; }; BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceDataDelegate.swift; sourceTree = ""; }; + BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankGenerator.swift; sourceTree = ""; }; + BA82C7982BCDAFE3000515A0 /* RankingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingEnums.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -969,8 +973,9 @@ isa = PBXGroup; children = ( BA82C77E2BCD2652000515A0 /* Inference */, - BA82C7812BCD2B20000515A0 /* Missions */, BA82C7562BCAB482000515A0 /* Statistics */, + BA82C7952BCDAF6E000515A0 /* Ranking */, + BA82C7812BCD2B20000515A0 /* Missions */, BA2F5ABC2BC80A2300CBD8E9 /* Achievements */, ); path = Metrics; @@ -1085,6 +1090,14 @@ path = Implemented; sourceTree = ""; }; + BA82C7952BCDAF6E000515A0 /* Ranking */ = { + isa = PBXGroup; + children = ( + BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */, + ); + path = Ranking; + sourceTree = ""; + }; BAFFB9272BB09E0E00D8301F /* Storyboards */ = { isa = PBXGroup; children = ( @@ -1265,6 +1278,7 @@ BAFFB95C2BB978E500D8301F /* StorageEnums.swift */, BAFFB9842BBDBA7D00D8301F /* MediaEnums.swift */, BA2F5AC62BC8148C00CBD8E9 /* StatisticName.swift */, + BA82C7982BCDAFE3000515A0 /* RankingEnums.swift */, ); path = Enums; sourceTree = ""; @@ -1601,6 +1615,7 @@ 3CAC4A732BB6B61C00A5D22E /* PlayerRenderStage.swift in Sources */, 523AA3BF2BB88FBF0041E60D /* WizardUnit.swift in Sources */, 3C9955C02BA57E5500D33FA5 /* EventTarget.swift in Sources */, + BA82C7992BCDAFE3000515A0 /* RankingEnums.swift in Sources */, 3CCF9CB12BAB1BCE004D170E /* GameWorld.swift in Sources */, 9B0406162BB89E140026E903 /* InvulnerabilityPowerUpDelegate.swift in Sources */, 5295A2152BAAF335005018A8 /* UnitSelectionNode.swift in Sources */, @@ -1701,6 +1716,7 @@ 5240D0AF2BB3B415004F1486 /* LifeEvent.swift in Sources */, 5295A2072BAA02FD005018A8 /* TFButtonNode.swift in Sources */, BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */, + BA82C7972BCDAF8A000515A0 /* RankGenerator.swift in Sources */, BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */, 3CD37AA32BBEC0F900222D8A /* FirebaseRemoteEventPublisher.swift in Sources */, 3CAC4A6B2BB6992F00A5D22E /* TFNode.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Enums/RankingEnums.swift b/TowerForge/TowerForge/Commons/Enums/RankingEnums.swift new file mode 100644 index 00000000..d7d811f9 --- /dev/null +++ b/TowerForge/TowerForge/Commons/Enums/RankingEnums.swift @@ -0,0 +1,22 @@ +// +// RankingEnums.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + +class RankingEnums { + + enum Rank: String, CaseIterable { + case PRIVATE + case CORPORAL + case SERGEANT + case LIEUTENANT + case CAPTAIN + case MAJOR + case COLONEL + case GENERAL + } +} diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift index 9ba1caf9..7b9efda7 100644 --- a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift @@ -10,8 +10,6 @@ import Foundation class InferenceEngineFactory { static var availableInferenceEngines: [(StatisticsEngine) -> any InferenceEngine] = - [ - { stats in AchievementsEngine(stats) }, - { stats in MissionsEngine(stats) } + [ { stats in AchievementsEngine(stats) }, { stats in MissionsEngine(stats) } ] } diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift b/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift new file mode 100644 index 00000000..f4b8ab88 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift @@ -0,0 +1,21 @@ +// +// RankGenerator.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + +/// RankGenerator is a Utility class used to derive ranks from Statistics +class RankGenerator { + + /// Adds the rank value of all contained statistics + static var expFormula: ((StatisticsDatabase) -> Double) = { + $0.statistics.values.map { $0.rankValue }.reduce(into: .zero) { $0 += $1 } + } + + static func getExp(from stats: StatisticsDatabase) -> Double { + Self.expFormula(stats) + } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift index 615ccdc2..c73a5b0a 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift @@ -9,6 +9,7 @@ import Foundation /// Total Damage dealt by the player in the course of the game final class TotalDamageDealtStatistic: Statistic { + static let expMultiplier: Double = 10 var permanentValue: Double = .zero var currentValue: Double = .zero var maximumCurrentValue: Double = .zero diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index 0a08696f..25027521 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -8,6 +8,7 @@ import Foundation final class TotalGamesStatistic: Statistic { + static let expMultiplier: Double = 100 var permanentValue: Double = .zero var currentValue: Double = .zero diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index 0626c9ff..e3aaaeff 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -8,6 +8,7 @@ import Foundation final class TotalKillsStatistic: Statistic { + static let expMultiplier: Double = 10 var permanentValue: Double = .zero var currentValue: Double = .zero diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 7c4a409f..9b654ff6 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -26,6 +26,9 @@ protocol Statistic: AnyObject, Codable { /// The principal interface for modifying the Statistic func update(for event: T) + /// The significance value which this Statistic holds in generating XP + static var expMultiplier: Double { get } + /// 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. @@ -67,10 +70,18 @@ extension Statistic { StatisticTypeWrapper(type: Self.self) } + static var expMultiplier: Double { + 0.0 + } + var statisticName: StatisticTypeWrapper { StatisticTypeWrapper(type: Self.self) } + var rankValue: Double { + permanentValue * Self.expMultiplier + } + /// Returns the total value of the statistic taking into account the /// permanent value of the Statistic prior to changes and the current changes /// to value. @@ -109,11 +120,11 @@ extension Statistic { updatePermanentValue(by: currentValue) currentValue = .zero } - + func resetMaxValue() { maximumCurrentValue = .zero } - + func resetStatistic() { permanentValue = .zero maximumCurrentValue = .zero diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift index 2ac4107f..32f129bd 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift @@ -14,6 +14,7 @@ protocol AnyStatisticUpdateActor { // typealias StatisticUpdateActor = ((Statistic, (any TFEvent)?) -> Void)? /// This struct contains pairs that each Statistic will refer to, /// to act accordingly when an EventType is executed elsewhere. + class StatisticUpdateActor { var action: ((Statistic, T?) -> Void)? From 5fdce8060a30fe08b74b21e2b5289a8a51baae34 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 05:31:14 +0800 Subject: [PATCH 43/54] Add ranking formulae --- .../TowerForge.xcodeproj/project.pbxproj | 8 ++--- .../TowerForge/Commons/Enums/Rank.swift | 34 +++++++++++++++++++ .../Commons/Enums/RankingEnums.swift | 22 ------------ .../Metrics/Ranking/RankGenerator.swift | 6 +++- 4 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 TowerForge/TowerForge/Commons/Enums/Rank.swift delete mode 100644 TowerForge/TowerForge/Commons/Enums/RankingEnums.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 8729d7e3..ebaf8460 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -231,7 +231,7 @@ BA82C7922BCD6579000515A0 /* StatisticUpdateActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */; }; BA82C7942BCDAA83000515A0 /* InferenceDataDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */; }; BA82C7972BCDAF8A000515A0 /* RankGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */; }; - BA82C7992BCDAFE3000515A0 /* RankingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7982BCDAFE3000515A0 /* RankingEnums.swift */; }; + BA82C7992BCDAFE3000515A0 /* Rank.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7982BCDAFE3000515A0 /* Rank.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -488,7 +488,7 @@ BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticUpdateActor.swift; sourceTree = ""; }; BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceDataDelegate.swift; sourceTree = ""; }; BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankGenerator.swift; sourceTree = ""; }; - BA82C7982BCDAFE3000515A0 /* RankingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingEnums.swift; sourceTree = ""; }; + BA82C7982BCDAFE3000515A0 /* Rank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rank.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -1278,7 +1278,7 @@ BAFFB95C2BB978E500D8301F /* StorageEnums.swift */, BAFFB9842BBDBA7D00D8301F /* MediaEnums.swift */, BA2F5AC62BC8148C00CBD8E9 /* StatisticName.swift */, - BA82C7982BCDAFE3000515A0 /* RankingEnums.swift */, + BA82C7982BCDAFE3000515A0 /* Rank.swift */, ); path = Enums; sourceTree = ""; @@ -1615,7 +1615,7 @@ 3CAC4A732BB6B61C00A5D22E /* PlayerRenderStage.swift in Sources */, 523AA3BF2BB88FBF0041E60D /* WizardUnit.swift in Sources */, 3C9955C02BA57E5500D33FA5 /* EventTarget.swift in Sources */, - BA82C7992BCDAFE3000515A0 /* RankingEnums.swift in Sources */, + BA82C7992BCDAFE3000515A0 /* Rank.swift in Sources */, 3CCF9CB12BAB1BCE004D170E /* GameWorld.swift in Sources */, 9B0406162BB89E140026E903 /* InvulnerabilityPowerUpDelegate.swift in Sources */, 5295A2152BAAF335005018A8 /* UnitSelectionNode.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Enums/Rank.swift b/TowerForge/TowerForge/Commons/Enums/Rank.swift new file mode 100644 index 00000000..8c08f92e --- /dev/null +++ b/TowerForge/TowerForge/Commons/Enums/Rank.swift @@ -0,0 +1,34 @@ +// +// RankingEnums.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + + +enum Rank: String, CaseIterable { + case PRIVATE + case CORPORAL + case SERGEANT + case LIEUTENANT + case CAPTAIN + case MAJOR + case COLONEL + case GENERAL + + var valueRange: Range { + switch self { + case .PRIVATE: return 0..<1001 + case .CORPORAL: return 1001..<2001 + case .SERGEANT: return 2001..<3001 + case .LIEUTENANT: return 3001..<4001 + case .CAPTAIN: return 4001..<5001 + case .MAJOR: return 6001..<7001 + case .COLONEL: return 7001..<8001 + case .GENERAL: return 8001..<9001 + } + } +} + diff --git a/TowerForge/TowerForge/Commons/Enums/RankingEnums.swift b/TowerForge/TowerForge/Commons/Enums/RankingEnums.swift deleted file mode 100644 index d7d811f9..00000000 --- a/TowerForge/TowerForge/Commons/Enums/RankingEnums.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// RankingEnums.swift -// TowerForge -// -// Created by Rubesh on 16/4/24. -// - -import Foundation - -class RankingEnums { - - enum Rank: String, CaseIterable { - case PRIVATE - case CORPORAL - case SERGEANT - case LIEUTENANT - case CAPTAIN - case MAJOR - case COLONEL - case GENERAL - } -} diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift b/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift index f4b8ab88..d1e81f08 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift @@ -15,7 +15,11 @@ class RankGenerator { $0.statistics.values.map { $0.rankValue }.reduce(into: .zero) { $0 += $1 } } - static func getExp(from stats: StatisticsDatabase) -> Double { + static func getTotalExp(from stats: StatisticsDatabase) -> Double { Self.expFormula(stats) } + + static func rank(forValue value: Double) -> Rank? { + return Rank.allCases.first { $0.valueRange.contains(Int(value)) } + } } From 91c5cf9619d3f733d81509f325a6b781a5cdd827 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 05:33:42 +0800 Subject: [PATCH 44/54] Finalize RankingAPI --- .../TowerForge/Metrics/Statistics/StatisticsEngine.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index f645c8a7..be3979ea 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -12,6 +12,14 @@ class StatisticsEngine { var statistics = StatisticsDatabase() var eventStatisticLinks = EventStatisticLinkDatabase() var inferenceEngines: [InferenceEngine] = [] + + var currentExp: Double { + RankGenerator.getTotalExp(from: statistics) + } + + var currentRank: Rank { + RankGenerator.rank(forValue: currentExp) ?? .PRIVATE + } init() { self.initializeStatistics() From f490ba5e190afeee6c60cbac5ba670cc41af0f93 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 05:46:19 +0800 Subject: [PATCH 45/54] Add proper RankingEngine --- .../TowerForge.xcodeproj/project.pbxproj | 8 ++--- .../TowerForge/Commons/Enums/Rank.swift | 20 ++++++------- .../Inference/InferenceEngineFactory.swift | 2 +- .../Metrics/Ranking/RankGenerator.swift | 25 ---------------- .../Metrics/Ranking/RankingEngine.swift | 29 +++++++++++++++++++ .../Metrics/Statistics/StatisticsEngine.swift | 8 ----- 6 files changed, 43 insertions(+), 49 deletions(-) delete mode 100644 TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift create mode 100644 TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index ebaf8460..534d820d 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -230,8 +230,8 @@ BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */; }; BA82C7922BCD6579000515A0 /* StatisticUpdateActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */; }; BA82C7942BCDAA83000515A0 /* InferenceDataDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */; }; - BA82C7972BCDAF8A000515A0 /* RankGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */; }; BA82C7992BCDAFE3000515A0 /* Rank.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7982BCDAFE3000515A0 /* Rank.swift */; }; + BA82C79B2BCDD5C8000515A0 /* RankingEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -487,8 +487,8 @@ BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDamageDealtStatistic.swift; sourceTree = ""; }; BA82C7912BCD6579000515A0 /* StatisticUpdateActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticUpdateActor.swift; sourceTree = ""; }; BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceDataDelegate.swift; sourceTree = ""; }; - BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankGenerator.swift; sourceTree = ""; }; BA82C7982BCDAFE3000515A0 /* Rank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rank.swift; sourceTree = ""; }; + BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingEngine.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -1093,7 +1093,7 @@ BA82C7952BCDAF6E000515A0 /* Ranking */ = { isa = PBXGroup; children = ( - BA82C7962BCDAF8A000515A0 /* RankGenerator.swift */, + BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */, ); path = Ranking; sourceTree = ""; @@ -1704,6 +1704,7 @@ BA82C75F2BCB1528000515A0 /* AchievementsDatabase.swift in Sources */, 3CD37A9F2BBEBFFB00222D8A /* TFRemoteEventPublisher.swift in Sources */, 3C0B608D2BB2B84000FFECB4 /* ContactComponent.swift in Sources */, + BA82C79B2BCDD5C8000515A0 /* RankingEngine.swift in Sources */, 3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */, BAFFB9752BBD833400D8301F /* AudioManager.swift in Sources */, 52DF5FFB2BA3601400135367 /* HealthComponent.swift in Sources */, @@ -1716,7 +1717,6 @@ 5240D0AF2BB3B415004F1486 /* LifeEvent.swift in Sources */, 5295A2072BAA02FD005018A8 /* TFButtonNode.swift in Sources */, BAFFB96A2BB9A64000D8301F /* ObjectSet.swift in Sources */, - BA82C7972BCDAF8A000515A0 /* RankGenerator.swift in Sources */, BA82C7902BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift in Sources */, 3CD37AA32BBEC0F900222D8A /* FirebaseRemoteEventPublisher.swift in Sources */, 3CAC4A6B2BB6992F00A5D22E /* TFNode.swift in Sources */, diff --git a/TowerForge/TowerForge/Commons/Enums/Rank.swift b/TowerForge/TowerForge/Commons/Enums/Rank.swift index 8c08f92e..9c9e8674 100644 --- a/TowerForge/TowerForge/Commons/Enums/Rank.swift +++ b/TowerForge/TowerForge/Commons/Enums/Rank.swift @@ -7,7 +7,6 @@ import Foundation - enum Rank: String, CaseIterable { case PRIVATE case CORPORAL @@ -17,18 +16,17 @@ enum Rank: String, CaseIterable { case MAJOR case COLONEL case GENERAL - + var valueRange: Range { switch self { - case .PRIVATE: return 0..<1001 - case .CORPORAL: return 1001..<2001 - case .SERGEANT: return 2001..<3001 - case .LIEUTENANT: return 3001..<4001 - case .CAPTAIN: return 4001..<5001 - case .MAJOR: return 6001..<7001 - case .COLONEL: return 7001..<8001 - case .GENERAL: return 8001..<9001 + case .PRIVATE: return 0..<1_001 + case .CORPORAL: return 1_001..<2_001 + case .SERGEANT: return 2_001..<3_001 + case .LIEUTENANT: return 3_001..<4_001 + case .CAPTAIN: return 4_001..<5_001 + case .MAJOR: return 6_001..<7_001 + case .COLONEL: return 7_001..<8_001 + case .GENERAL: return 8_001..<9_001 } } } - diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift index 7b9efda7..18011069 100644 --- a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift @@ -10,6 +10,6 @@ import Foundation class InferenceEngineFactory { static var availableInferenceEngines: [(StatisticsEngine) -> any InferenceEngine] = - [ { stats in AchievementsEngine(stats) }, { stats in MissionsEngine(stats) } + [ { stats in AchievementsEngine(stats) }, { stats in MissionsEngine(stats) }, { stats in RankingEngine(stats) } ] } diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift b/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift deleted file mode 100644 index d1e81f08..00000000 --- a/TowerForge/TowerForge/Metrics/Ranking/RankGenerator.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// RankGenerator.swift -// TowerForge -// -// Created by Rubesh on 16/4/24. -// - -import Foundation - -/// RankGenerator is a Utility class used to derive ranks from Statistics -class RankGenerator { - - /// Adds the rank value of all contained statistics - static var expFormula: ((StatisticsDatabase) -> Double) = { - $0.statistics.values.map { $0.rankValue }.reduce(into: .zero) { $0 += $1 } - } - - static func getTotalExp(from stats: StatisticsDatabase) -> Double { - Self.expFormula(stats) - } - - static func rank(forValue value: Double) -> Rank? { - return Rank.allCases.first { $0.valueRange.contains(Int(value)) } - } -} diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift new file mode 100644 index 00000000..0df5f85d --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -0,0 +1,29 @@ +// +// RankingEngine.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + +class RankingEngine: InferenceEngine, InferenceDataDelegate { + /// Adds the rank value of all contained statistics + static var expFormula: ((StatisticsDatabase) -> Double) = { + $0.statistics.values.map { $0.rankValue }.reduce(into: .zero) { $0 += $1 } + } + + unowned var statisticsEngine: StatisticsEngine + + var statisticsDatabase: StatisticsDatabase { statisticsEngine.statistics } + var currentExp: Double { Self.expFormula(statisticsDatabase) } + var currentRank: Rank { + Rank.allCases.first { $0.valueRange.contains(Int(self.currentExp)) } ?? .PRIVATE + } + + init(_ statisticsEngine: StatisticsEngine) { + self.statisticsEngine = statisticsEngine + } + + func updateOnReceive() { } +} diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index be3979ea..f645c8a7 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -12,14 +12,6 @@ class StatisticsEngine { var statistics = StatisticsDatabase() var eventStatisticLinks = EventStatisticLinkDatabase() var inferenceEngines: [InferenceEngine] = [] - - var currentExp: Double { - RankGenerator.getTotalExp(from: statistics) - } - - var currentRank: Rank { - RankGenerator.rank(forValue: currentExp) ?? .PRIVATE - } init() { self.initializeStatistics() From 2774fc406b013fac5b3c87970dcb07a59063efbd Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 06:02:05 +0800 Subject: [PATCH 46/54] Upgrade RankingEngine --- .../TowerForge.xcodeproj/project.pbxproj | 6 ++++- .../Metrics/Inference/InferenceEngine.swift | 10 ++++++++ .../Inference/InferenceEngineFactory.swift | 3 +-- .../InferenceEngineTypeWrapper.swift | 24 +++++++++++++++++++ .../Enums => Metrics/Ranking}/Rank.swift | 0 .../Metrics/Ranking/RankingEngine.swift | 7 +++--- .../Implemented/TotalDeathsStatistic.swift | 1 - .../Implemented/TotalGamesStatistic.swift | 1 - .../Implemented/TotalKillsStatistic.swift | 1 - .../Metrics/Statistics/StatisticsEngine.swift | 20 +++++++++++++--- 10 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 TowerForge/TowerForge/Metrics/Inference/InferenceEngineTypeWrapper.swift rename TowerForge/TowerForge/{Commons/Enums => Metrics/Ranking}/Rank.swift (100%) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 534d820d..cf621a5c 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -232,6 +232,7 @@ BA82C7942BCDAA83000515A0 /* InferenceDataDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */; }; BA82C7992BCDAFE3000515A0 /* Rank.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7982BCDAFE3000515A0 /* Rank.swift */; }; BA82C79B2BCDD5C8000515A0 /* RankingEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */; }; + BA82C79D2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C79C2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -489,6 +490,7 @@ BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceDataDelegate.swift; sourceTree = ""; }; BA82C7982BCDAFE3000515A0 /* Rank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rank.swift; sourceTree = ""; }; BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingEngine.swift; sourceTree = ""; }; + BA82C79C2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngineTypeWrapper.swift; sourceTree = ""; }; BABB7C052BA9A41000D54DAE /* TowerForceTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TowerForceTestPlan.xctestplan; sourceTree = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -1063,6 +1065,7 @@ isa = PBXGroup; children = ( BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */, + BA82C79C2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift */, BA82C77F2BCD284D000515A0 /* InferenceEngineFactory.swift */, BA82C7932BCDAA83000515A0 /* InferenceDataDelegate.swift */, ); @@ -1093,6 +1096,7 @@ BA82C7952BCDAF6E000515A0 /* Ranking */ = { isa = PBXGroup; children = ( + BA82C7982BCDAFE3000515A0 /* Rank.swift */, BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */, ); path = Ranking; @@ -1278,7 +1282,6 @@ BAFFB95C2BB978E500D8301F /* StorageEnums.swift */, BAFFB9842BBDBA7D00D8301F /* MediaEnums.swift */, BA2F5AC62BC8148C00CBD8E9 /* StatisticName.swift */, - BA82C7982BCDAFE3000515A0 /* Rank.swift */, ); path = Enums; sourceTree = ""; @@ -1637,6 +1640,7 @@ BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */, 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 */, diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceEngine.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngine.swift index 10b19ef6..482ae77b 100644 --- a/TowerForge/TowerForge/Metrics/Inference/InferenceEngine.swift +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceEngine.swift @@ -11,3 +11,13 @@ protocol InferenceEngine: AnyObject { var statisticsEngine: StatisticsEngine { get set } func updateOnReceive() } + +extension InferenceEngine { + static var asType: InferenceEngineTypeWrapper { + InferenceEngineTypeWrapper(type: Self.self) + } + + var asType: InferenceEngineTypeWrapper { + InferenceEngineTypeWrapper(type: Self.self) + } +} diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift index 18011069..d82e6c1d 100644 --- a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineFactory.swift @@ -10,6 +10,5 @@ import Foundation class InferenceEngineFactory { static var availableInferenceEngines: [(StatisticsEngine) -> any InferenceEngine] = - [ { stats in AchievementsEngine(stats) }, { stats in MissionsEngine(stats) }, { stats in RankingEngine(stats) } - ] + [ { stats in AchievementsEngine(stats) }, { stats in MissionsEngine(stats) }, { stats in RankingEngine(stats) } ] } diff --git a/TowerForge/TowerForge/Metrics/Inference/InferenceEngineTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineTypeWrapper.swift new file mode 100644 index 00000000..cabe2d00 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Inference/InferenceEngineTypeWrapper.swift @@ -0,0 +1,24 @@ +// +// InferenceEngineType.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + +struct InferenceEngineTypeWrapper: Equatable, Hashable { + let type: InferenceEngine.Type + + var asString: String { + String(describing: type) + } + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.type == rhs.type + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(type)) + } +} diff --git a/TowerForge/TowerForge/Commons/Enums/Rank.swift b/TowerForge/TowerForge/Metrics/Ranking/Rank.swift similarity index 100% rename from TowerForge/TowerForge/Commons/Enums/Rank.swift rename to TowerForge/TowerForge/Metrics/Ranking/Rank.swift diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift index 0df5f85d..fdbc1013 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -8,15 +8,16 @@ import Foundation class RankingEngine: InferenceEngine, InferenceDataDelegate { - /// Adds the rank value of all contained statistics - static var expFormula: ((StatisticsDatabase) -> Double) = { + + // 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 var statisticsDatabase: StatisticsDatabase { statisticsEngine.statistics } - var currentExp: Double { Self.expFormula(statisticsDatabase) } + var currentExp: Double { Self.defaultExpFormula(statisticsDatabase) } var currentRank: Rank { Rank.allCases.first { $0.valueRange.contains(Int(self.currentExp)) } ?? .PRIVATE } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index c9dec443..1034beb1 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -8,7 +8,6 @@ import Foundation final class TotalDeathsStatistic: Statistic { - var permanentValue: Double = .zero var currentValue: Double = .zero var maximumCurrentValue: Double = .zero diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index 25027521..baa4d393 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -9,7 +9,6 @@ import Foundation final class TotalGamesStatistic: Statistic { static let expMultiplier: Double = 100 - var permanentValue: Double = .zero var currentValue: Double = .zero var maximumCurrentValue: Double diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift index e3aaaeff..df03efa8 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalKillsStatistic.swift @@ -9,7 +9,6 @@ import Foundation final class TotalKillsStatistic: Statistic { static let expMultiplier: Double = 10 - var permanentValue: Double = .zero var currentValue: Double = .zero var maximumCurrentValue: Double = .zero diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index f645c8a7..7cda14a8 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -11,7 +11,7 @@ class StatisticsEngine { /// Core storage of Statistics var statistics = StatisticsDatabase() var eventStatisticLinks = EventStatisticLinkDatabase() - var inferenceEngines: [InferenceEngine] = [] + var inferenceEngines: [InferenceEngineTypeWrapper: InferenceEngine] = [:] init() { self.initializeStatistics() @@ -40,7 +40,7 @@ class StatisticsEngine { } func addInferenceEngine(_ engine: InferenceEngine) { - inferenceEngines.append(engine) + inferenceEngines[engine.asType] } /// Main update function @@ -71,7 +71,21 @@ class StatisticsEngine { /// to follow delegate pattern and have unowned statsEngine/db variables inside /// InferenceEngines func notifyInferenceEngines() { - inferenceEngines.forEach { $0.updateOnReceive() } + 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() { From 2fe58d1f93d053107895cbc808dad11bf0259d26 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 17:54:03 +0800 Subject: [PATCH 47/54] Merge Achievements and Statistics into AbstractGoal protocol --- .../TowerForge.xcodeproj/project.pbxproj | 18 ++++- .../Metrics/Achievements/Achievement.swift | 64 +-------------- .../Implemented/CenturionAchievement.swift | 4 +- .../Implemented/FiftyKillsAchievement.swift | 4 +- .../Implemented/HundredKillsAchievement.swift | 4 +- .../Metrics/Goals/AbstractGoal.swift | 80 +++++++++++++++++++ .../Goals/AbstractGoalTypeWrapper.swift | 24 ++++++ .../Implemented/GrandDamageMission.swift | 4 +- .../TowerForge/Metrics/Missions/Mission.swift | 60 +------------- .../Metrics/Ranking/RankingEngine.swift | 1 + .../Metrics/Statistics/Statistic.swift | 4 +- .../Statistics/StatisticUpdateActor.swift | 3 +- .../Statistics/StatisticsDatabase+Merge.swift | 4 +- .../Metrics/Statistics/StatisticsEngine.swift | 2 +- 14 files changed, 138 insertions(+), 138 deletions(-) create mode 100644 TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift create mode 100644 TowerForge/TowerForge/Metrics/Goals/AbstractGoalTypeWrapper.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index cf621a5c..54b60edd 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -233,6 +233,8 @@ BA82C7992BCDAFE3000515A0 /* Rank.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7982BCDAFE3000515A0 /* Rank.swift */; }; BA82C79B2BCDD5C8000515A0 /* RankingEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */; }; 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 */; }; BAFFB9452BB0A8C800D8301F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9442BB0A8C800D8301F /* Constants.swift */; }; BAFFB9492BB0ABC400D8301F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9482BB0ABC400D8301F /* Logger.swift */; }; BAFFB94B2BB11F9800D8301F /* GameEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB94A2BB11F9800D8301F /* GameEngine.swift */; }; @@ -491,6 +493,8 @@ BA82C7982BCDAFE3000515A0 /* Rank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rank.swift; sourceTree = ""; }; BA82C79A2BCDD5C8000515A0 /* RankingEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingEngine.swift; sourceTree = ""; }; BA82C79C2BCDD9ED000515A0 /* InferenceEngineTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngineTypeWrapper.swift; sourceTree = ""; }; + 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 = ""; }; BAFFB9442BB0A8C800D8301F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; BAFFB9482BB0ABC400D8301F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -977,6 +981,7 @@ BA82C77E2BCD2652000515A0 /* Inference */, BA82C7562BCAB482000515A0 /* Statistics */, BA82C7952BCDAF6E000515A0 /* Ranking */, + BA82C7A02BCE80FB000515A0 /* Goals */, BA82C7812BCD2B20000515A0 /* Missions */, BA2F5ABC2BC80A2300CBD8E9 /* Achievements */, ); @@ -1010,8 +1015,8 @@ isa = PBXGroup; children = ( BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */, - BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */, BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */, + BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */, BA82C78F2BCD2FAF000515A0 /* TotalDamageDealtStatistic.swift */, ); path = Implemented; @@ -1102,6 +1107,15 @@ path = Ranking; sourceTree = ""; }; + BA82C7A02BCE80FB000515A0 /* Goals */ = { + isa = PBXGroup; + children = ( + BA82C79E2BCE7FBA000515A0 /* AbstractGoal.swift */, + BA82C7A12BCE8138000515A0 /* AbstractGoalTypeWrapper.swift */, + ); + path = Goals; + sourceTree = ""; + }; BAFFB9272BB09E0E00D8301F /* Storyboards */ = { isa = PBXGroup; children = ( @@ -1597,9 +1611,11 @@ 3CE951582BAD724D008B2785 /* TFContact.swift in Sources */, 5295A2132BAAEA16005018A8 /* UnitNode.swift in Sources */, 52DF5FF32BA351E100135367 /* SpriteComponent.swift in Sources */, + BA82C7A22BCE8138000515A0 /* AbstractGoalTypeWrapper.swift in Sources */, 3CE9514B2BAC83FA008B2785 /* SpawnableEntities.swift in Sources */, 5299D1302BC31002003EF746 /* AuthenticationProvider.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 */, diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index 80f9bcd0..403c2d48 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -7,30 +7,7 @@ import Foundation -/// The TFAchievement protocol specifies the requirements for all concrete -/// achievements to conform to. -/// -/// Each achievement will correspond to a collection of statistics. -protocol Achievement: AnyObject { - var achievementName: String { get } - var achievementDescription: String { get } - - static var definedParameters: [StatisticTypeWrapper: Double] { get } - var currentParameters: [StatisticTypeWrapper: Statistic] { get set } - - var currentValues: [StatisticTypeWrapper: Double] { get } - var requiredValues: [StatisticTypeWrapper: Double] { get } - - var currentProgressRates: [StatisticTypeWrapper: Double] { get } - var overallProgressRate: Double { get } - var isComplete: Bool { get } - - func loadStatistic(_ stat: Statistic) - func update(with stats: StatisticsDatabase) - - init(dependentStatistics: [Statistic]) - -} +protocol Achievement: AbstractGoal { } extension Achievement { @@ -38,20 +15,6 @@ extension Achievement { AchievementTypeWrapper(type: Self.self) } - var requiredValues: [StatisticTypeWrapper: Double] { - Self.definedParameters - } - - func loadStatistic(_ stat: any Statistic) { - currentParameters[stat.statisticName] = stat - } - - func update(with stats: StatisticsDatabase) { - currentParameters.keys.forEach { - self.currentParameters[$0] = stats.statistics[$0] - } - } - var currentValues: [StatisticTypeWrapper: Double] { var values: [StatisticTypeWrapper: Double] = [:] currentParameters.keys.forEach { key in @@ -62,29 +25,4 @@ extension Achievement { return values } - - var currentProgressRates: [StatisticTypeWrapper: Double] { - var rates: [StatisticTypeWrapper: Double] = [:] - requiredValues.keys.forEach { key in - if let requiredValue = requiredValues[key], let currentValue = currentValues[key] { - rates[key] = currentValue / requiredValue - } - } - - return rates - } - - var overallProgressRate: Double { - currentProgressRates.values.reduce(into: .zero) { $0 += $1 } - .divide(by: Double(currentProgressRates.values.count)) - } - - var overallProgressRateRounded: Double { - overallProgressRate.rounded() - } - - var isComplete: Bool { - currentProgressRates.values.allSatisfy { !$0.isLess(than: .unit) } - } - } diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift index 92125e4d..ea24174a 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/CenturionAchievement.swift @@ -8,8 +8,8 @@ import Foundation final class CenturionAchievement: Achievement { - var achievementName: String = "Centurion" - var achievementDescription: String = "Kill 100 enemies and die 100 times!" + var name: String = "Centurion" + var description: String = "Kill 100 enemies and die 100 times!" var currentParameters: [StatisticTypeWrapper: any Statistic] static var definedParameters: [StatisticTypeWrapper: Double] = diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift index edede5d2..8e25c365 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/FiftyKillsAchievement.swift @@ -8,8 +8,8 @@ import Foundation final class FiftyKillsAchievement: Achievement { - var achievementName: String = "50 Kills" - var achievementDescription: String = "Attain 50 total kills in TowerForge" + var name: String = "50 Kills" + var description: String = "Attain 50 total kills in TowerForge" var currentParameters: [StatisticTypeWrapper: any Statistic] static var definedParameters: [StatisticTypeWrapper: Double] { diff --git a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift index 4ca667a1..e83b1587 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Implemented/HundredKillsAchievement.swift @@ -8,8 +8,8 @@ import Foundation final class HundredKillsAchievement: Achievement { - var achievementName: String = "100 Kills" - var achievementDescription: String = "Attain 100 total kills in TowerForge" + var name: String = "100 Kills" + var description: String = "Attain 100 total kills in TowerForge" var currentParameters: [StatisticTypeWrapper: any Statistic] = [:] static var definedParameters: [StatisticTypeWrapper: Double] = diff --git a/TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift b/TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift new file mode 100644 index 00000000..01b63135 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift @@ -0,0 +1,80 @@ +// +// Goal.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + +/// The TFAchievement protocol specifies the requirements for all concrete +/// achievements to conform to. +/// +/// Each achievement will correspond to a collection of statistics. +protocol AbstractGoal: AnyObject { + static var goalType: AbstractGoalTypeWrapper { get } + var name: String { get } + var description: String { get } + + static var definedParameters: [StatisticTypeWrapper: Double] { get } + var currentParameters: [StatisticTypeWrapper: Statistic] { get set } + + var currentValues: [StatisticTypeWrapper: Double] { get } + var requiredValues: [StatisticTypeWrapper: Double] { get } + + var currentProgressRates: [StatisticTypeWrapper: Double] { get } + var overallProgressRate: Double { get } + var isComplete: Bool { get } + + func loadStatistic(_ stat: Statistic) + func update(with stats: StatisticsDatabase) + + init(dependentStatistics: [Statistic]) + +} + +extension AbstractGoal { + + static var goalType: AbstractGoalTypeWrapper { + AbstractGoalTypeWrapper(type: Self.self) + } + + var requiredValues: [StatisticTypeWrapper: Double] { + Self.definedParameters + } + + func loadStatistic(_ stat: any Statistic) { + currentParameters[stat.statisticName] = stat + } + + func update(with stats: StatisticsDatabase) { + currentParameters.keys.forEach { + self.currentParameters[$0] = stats.statistics[$0] + } + } + + var currentProgressRates: [StatisticTypeWrapper: Double] { + var rates: [StatisticTypeWrapper: Double] = [:] + requiredValues.keys.forEach { key in + if let requiredValue = requiredValues[key], let currentValue = currentValues[key] { + rates[key] = currentValue / requiredValue + } + } + + return rates + } + + var overallProgressRate: Double { + currentProgressRates.values.reduce(into: .zero) { $0 += $1 } + .divide(by: Double(currentProgressRates.values.count)) + } + + var overallProgressRateRounded: Double { + overallProgressRate.rounded() + } + + var isComplete: Bool { + currentProgressRates.values.allSatisfy { !$0.isLess(than: .unit) } + } + +} diff --git a/TowerForge/TowerForge/Metrics/Goals/AbstractGoalTypeWrapper.swift b/TowerForge/TowerForge/Metrics/Goals/AbstractGoalTypeWrapper.swift new file mode 100644 index 00000000..e9b38488 --- /dev/null +++ b/TowerForge/TowerForge/Metrics/Goals/AbstractGoalTypeWrapper.swift @@ -0,0 +1,24 @@ +// +// GoalTypeWrapper.swift +// TowerForge +// +// Created by Rubesh on 16/4/24. +// + +import Foundation + +struct AbstractGoalTypeWrapper: Equatable, Hashable { + let type: AbstractGoal.Type + + var asString: String { + String(describing: type) + } + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.type == rhs.type + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(type)) + } +} diff --git a/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift index c5bb4976..db44d08c 100644 --- a/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift +++ b/TowerForge/TowerForge/Metrics/Missions/Implemented/GrandDamageMission.swift @@ -8,8 +8,8 @@ import Foundation final class GrandDamageMission: Mission { - var missionName: String = "Mission: 1000 Damage" - var missionDescription: String = "Attain 1000 Damage in 1 game" + var name: String = "Mission: 1000 Damage" + var description: String = "Attain 1000 Damage in 1 game" var currentParameters: [StatisticTypeWrapper: any Statistic] static var definedParameters: [StatisticTypeWrapper: Double] { diff --git a/TowerForge/TowerForge/Metrics/Missions/Mission.swift b/TowerForge/TowerForge/Metrics/Missions/Mission.swift index fd39dca9..fca46862 100644 --- a/TowerForge/TowerForge/Metrics/Missions/Mission.swift +++ b/TowerForge/TowerForge/Metrics/Missions/Mission.swift @@ -7,51 +7,14 @@ import Foundation -protocol Mission: AnyObject { - var missionName: String { get } - var missionDescription: String { get } - - static var definedParameters: [StatisticTypeWrapper: Double] { get } - var currentParameters: [StatisticTypeWrapper: Statistic] { get set } - - var currentValues: [StatisticTypeWrapper: Double] { get } - var requiredValues: [StatisticTypeWrapper: Double] { get } - - var currentProgressRates: [StatisticTypeWrapper: Double] { get } - var overallProgressRate: Double { get } - var isComplete: Bool { get } - - func loadStatistic(_ stat: Statistic) - func update(with stats: StatisticsDatabase) - - init(dependentStatistics: [Statistic]) - -} +protocol Mission: AbstractGoal { } extension Mission { - var isComplete: Bool { - currentProgressRates.values.allSatisfy { !$0.isLess(than: .unit) } - } - static var asType: MissionTypeWrapper { MissionTypeWrapper(type: Self.self) } - var requiredValues: [StatisticTypeWrapper: Double] { - Self.definedParameters - } - - func loadStatistic(_ stat: any Statistic) { - currentParameters[stat.statisticName] = stat - } - - func update(with stats: StatisticsDatabase) { - currentParameters.keys.forEach { - self.currentParameters[$0] = stats.statistics[$0] - } - } - var currentValues: [StatisticTypeWrapper: Double] { var values: [StatisticTypeWrapper: Double] = [:] currentParameters.keys.forEach { key in @@ -62,25 +25,4 @@ extension Mission { return values } - - var currentProgressRates: [StatisticTypeWrapper: Double] { - var rates: [StatisticTypeWrapper: Double] = [:] - requiredValues.keys.forEach { key in - if let requiredValue = requiredValues[key], let currentValue = currentValues[key] { - rates[key] = currentValue / requiredValue - } - } - - return rates - } - - var overallProgressRate: Double { - currentProgressRates.values.reduce(into: .zero) { $0 += $1 } - .divide(by: Double(currentProgressRates.values.count)) - } - - var overallProgressRateRounded: Double { - overallProgressRate.rounded() - } - } diff --git a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift index fdbc1013..80a82daa 100644 --- a/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift +++ b/TowerForge/TowerForge/Metrics/Ranking/RankingEngine.swift @@ -7,6 +7,7 @@ import Foundation +/// The RankingEngine is responsible for generating rank and exp information. class RankingEngine: InferenceEngine, InferenceDataDelegate { // TODO: Consider expanding to more formula for .e.g double exp. diff --git a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift index 9b654ff6..4cf39042 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Statistic.swift @@ -70,9 +70,7 @@ extension Statistic { StatisticTypeWrapper(type: Self.self) } - static var expMultiplier: Double { - 0.0 - } + static var expMultiplier: Double { 0.0 } var statisticName: StatisticTypeWrapper { StatisticTypeWrapper(type: Self.self) diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift index 32f129bd..eb410965 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticUpdateActor.swift @@ -14,7 +14,6 @@ protocol AnyStatisticUpdateActor { // typealias StatisticUpdateActor = ((Statistic, (any TFEvent)?) -> Void)? /// This struct contains pairs that each Statistic will refer to, /// to act accordingly when an EventType is executed elsewhere. - class StatisticUpdateActor { var action: ((Statistic, T?) -> Void)? @@ -39,7 +38,7 @@ struct AnyStatisticUpdateActorWrapper: AnyStatisticUpdateActor { _updateStatistic?(statistic, event) } else { // Handle the case where event cannot be cast to T, possibly call with nil - Logger.log("Warning: Attempted to pass an event of the wrong type to a StatisticUpdateActor") + Logger.log("Warning: Attempted to pass an event of the wrong type to a StatisticUpdateActor", self) _updateStatistic?(statistic, nil) } } diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift index 30644a9a..d3ad43f1 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase+Merge.swift @@ -11,7 +11,6 @@ import Foundation /// /// Represen extension StatisticsDatabase: Equatable { - static func == (lhs: StatisticsDatabase, rhs: StatisticsDatabase) -> Bool { guard lhs.statistics.count == rhs.statistics.count else { return false @@ -66,6 +65,9 @@ extension StatisticsDatabase: Equatable { 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. } else { diff --git a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift index 7cda14a8..03836640 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift @@ -40,7 +40,7 @@ class StatisticsEngine { } func addInferenceEngine(_ engine: InferenceEngine) { - inferenceEngines[engine.asType] + inferenceEngines[engine.asType] = engine } /// Main update function From 2aa6624105f695467f67d825df00c1e4175ad2cf Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 20:16:07 +0800 Subject: [PATCH 48/54] Fix style --- TowerForge/TowerForge/Metrics/Achievements/Achievement.swift | 2 -- TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift | 4 ++-- TowerForge/TowerForge/Metrics/Missions/Mission.swift | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift index 403c2d48..9a6a9873 100644 --- a/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift +++ b/TowerForge/TowerForge/Metrics/Achievements/Achievement.swift @@ -10,7 +10,6 @@ import Foundation protocol Achievement: AbstractGoal { } extension Achievement { - static var asType: AchievementTypeWrapper { AchievementTypeWrapper(type: Self.self) } @@ -22,7 +21,6 @@ extension Achievement { values[key] = currentStatistic.permanentValue } } - return values } } diff --git a/TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift b/TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift index 01b63135..438407fe 100644 --- a/TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift +++ b/TowerForge/TowerForge/Metrics/Goals/AbstractGoal.swift @@ -60,12 +60,12 @@ extension AbstractGoal { rates[key] = currentValue / requiredValue } } - return rates } var overallProgressRate: Double { - currentProgressRates.values.reduce(into: .zero) { $0 += $1 } + currentProgressRates.values + .reduce(into: .zero) { $0 += $1 } .divide(by: Double(currentProgressRates.values.count)) } diff --git a/TowerForge/TowerForge/Metrics/Missions/Mission.swift b/TowerForge/TowerForge/Metrics/Missions/Mission.swift index fca46862..cb6e307c 100644 --- a/TowerForge/TowerForge/Metrics/Missions/Mission.swift +++ b/TowerForge/TowerForge/Metrics/Missions/Mission.swift @@ -10,7 +10,6 @@ import Foundation protocol Mission: AbstractGoal { } extension Mission { - static var asType: MissionTypeWrapper { MissionTypeWrapper(type: Self.self) } @@ -22,7 +21,6 @@ extension Mission { values[key] = currentStatistic.maximumCurrentValue } } - return values } } From ade694e976f437fc0cc082403463efdc547df0a5 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 20:23:35 +0800 Subject: [PATCH 49/54] Move extensions file --- .../Commons/{Protocols => Extensions}/Double+Extensions.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename TowerForge/TowerForge/Commons/{Protocols => Extensions}/Double+Extensions.swift (100%) diff --git a/TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift b/TowerForge/TowerForge/Commons/Extensions/Double+Extensions.swift similarity index 100% rename from TowerForge/TowerForge/Commons/Protocols/Double+Extensions.swift rename to TowerForge/TowerForge/Commons/Extensions/Double+Extensions.swift From 9f623ffc5e5d1bf80069930ee9a33802de13296c Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 20:28:01 +0800 Subject: [PATCH 50/54] Add pbxproj file --- TowerForge/TowerForge.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 54b60edd..41184fa3 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -467,7 +467,7 @@ BA82C75C2BCB1451000515A0 /* InferenceEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferenceEngine.swift; sourceTree = ""; }; 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/Protocols/Double+Extensions.swift"; sourceTree = SOURCE_ROOT; }; + 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 = ""; }; From b5bc89b976204f9fa335e2e33eea74019fb40db4 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 22:03:02 +0800 Subject: [PATCH 51/54] Fix style --- .../GameModule/Events/GameEvents/DamageEvent.swift | 4 +--- .../Implemented/TotalDamageDealtStatistic.swift | 9 --------- .../Implemented/TotalDeathsStatistic.swift | 12 +----------- .../Statistics/Implemented/TotalGamesStatistic.swift | 11 +---------- 4 files changed, 3 insertions(+), 33 deletions(-) diff --git a/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift b/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift index 0cc76393..24fe1e46 100644 --- a/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift @@ -27,9 +27,7 @@ struct DamageEvent: TFEvent { } if let statsSystem = target.system(ofType: StatisticSystem.self) { - if player != .ownPlayer { - statsSystem.notify(for: self) - } + statsSystem.notify(for: self) } return EventOutput() diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift index c73a5b0a..4630d41f 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDamageDealtStatistic.swift @@ -26,15 +26,6 @@ final class TotalDamageDealtStatistic: Statistic { self.maximumCurrentValue = maxCurrentValue } - /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { - let eventType = TFEventTypeWrapper(type: DamageEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: ) } - let eventUpdateDictionary = [eventType: updateActor] - let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) - - return statsLink - }*/ - func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: DamageEvent.self) let eventUpdateClosure: (Statistic, DamageEvent?) -> Void = { statistic, event in diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 1034beb1..0f6b30fa 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -24,15 +24,6 @@ final class TotalDeathsStatistic: Statistic { self.maximumCurrentValue = maxCurrentValue } - /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { - let eventType = TFEventTypeWrapper(type: DeathEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } - let eventUpdateDictionary = [eventType: updateActor] - let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) - - return statsLink - }*/ - /// The total deaths statistic no longer requires the DeathEvent, it is able /// to directly parse a KillEvent to determine whe func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { @@ -47,8 +38,7 @@ final class TotalDeathsStatistic: Statistic { let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) - // let anyStatisticUpdateActorWrapper = - // AnyStatisticUpdateActorWrapper(updateStatistic: eventUpdateClosure) + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index baa4d393..fd05c8f8 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -25,15 +25,6 @@ final class TotalGamesStatistic: Statistic { self.maximumCurrentValue = maxCurrentValue } - /*func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { - let eventType = TFEventTypeWrapper(type: GameStartEvent.self) - let updateActor: StatisticUpdateActor = { statistic in statistic.updateCurrentValue(by: 1.0) } - let eventUpdateDictionary = [eventType: updateActor] - let statsLink = StatisticUpdateLinkDatabase(statisticUpdateLinks: eventUpdateDictionary) - - return statsLink - }*/ - func getStatisticUpdateLinks() -> StatisticUpdateLinkDatabase { let eventType = TFEventTypeWrapper(type: GameStartEvent.self) let eventUpdateClosure: (Statistic, GameStartEvent?) -> Void = { statistic, event in @@ -43,7 +34,7 @@ final class TotalGamesStatistic: Statistic { let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) - + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) From 9933afa2d97601a4705b622d3a8aa2092675dd84 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 22:04:40 +0800 Subject: [PATCH 52/54] Remove DeathEvent --- .../TowerForge.xcodeproj/project.pbxproj | 4 ---- .../Events/StatsEvents/DeathEvent.swift | 21 ------------------- 2 files changed, 25 deletions(-) delete mode 100644 TowerForge/TowerForge/GameModule/Events/StatsEvents/DeathEvent.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 41184fa3..3cccdcd5 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -199,7 +199,6 @@ BA82C7442BC86FFE000515A0 /* GameStartEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7432BC86FFE000515A0 /* GameStartEvent.swift */; }; BA82C7462BC8797F000515A0 /* StatisticsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */; }; BA82C74A2BC88FE5000515A0 /* StatisticSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7492BC88FE5000515A0 /* StatisticSystem.swift */; }; - BA82C74E2BC8A024000515A0 /* DeathEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C74D2BC8A024000515A0 /* DeathEvent.swift */; }; BA82C7502BC8A20A000515A0 /* TotalDeathsStatistic.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */; }; BA82C7532BC8A41B000515A0 /* AchievementsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */; }; BA82C7582BCAB4C2000515A0 /* StatisticTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */; }; @@ -459,7 +458,6 @@ BA82C7432BC86FFE000515A0 /* GameStartEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStartEvent.swift; sourceTree = ""; }; BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsDatabase.swift; sourceTree = ""; }; BA82C7492BC88FE5000515A0 /* StatisticSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticSystem.swift; sourceTree = ""; }; - BA82C74D2BC8A024000515A0 /* DeathEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeathEvent.swift; sourceTree = ""; }; BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDeathsStatistic.swift; sourceTree = ""; }; BA82C7522BC8A41B000515A0 /* AchievementsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsEngine.swift; sourceTree = ""; }; BA82C7572BCAB4C2000515A0 /* StatisticTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticTypeWrapper.swift; sourceTree = ""; }; @@ -1025,7 +1023,6 @@ BA82C74B2BC89FDC000515A0 /* StatsEvents */ = { isa = PBXGroup; children = ( - BA82C74D2BC8A024000515A0 /* DeathEvent.swift */, ); path = StatsEvents; sourceTree = ""; @@ -1549,7 +1546,6 @@ files = ( 9B0406122BB889940026E903 /* PowerUpNode.swift in Sources */, 3CCF9CAF2BAB1A96004D170E /* SceneUpdateDelegate.swift in Sources */, - BA82C74E2BC8A024000515A0 /* DeathEvent.swift in Sources */, BA82C7402BC8674A000515A0 /* StatisticsFactory.swift in Sources */, 3CAC4A692BB697A400A5D22E /* SpriteRenderStage.swift in Sources */, 523C29302BBD0916004C6EAC /* GameWaitingRoomViewController.swift in Sources */, diff --git a/TowerForge/TowerForge/GameModule/Events/StatsEvents/DeathEvent.swift b/TowerForge/TowerForge/GameModule/Events/StatsEvents/DeathEvent.swift deleted file mode 100644 index ad9b867c..00000000 --- a/TowerForge/TowerForge/GameModule/Events/StatsEvents/DeathEvent.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// DeathEvent.swift -// TowerForge -// -// Created by Rubesh on 12/4/24. -// - -import Foundation - -struct DeathEvent: TFEvent { - let timestamp: TimeInterval = .zero - let entityId: UUID - - init(_ entityId: UUID) { - self.entityId = entityId - } - - func execute(in target: any EventTarget) -> EventOutput { - EventOutput() - } -} From 61eb76f11a20702bab21ae0649a20e89f3cb2355 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 22:05:48 +0800 Subject: [PATCH 53/54] Fix style --- TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift | 3 +-- .../Metrics/Statistics/Implemented/TotalDeathsStatistic.swift | 2 +- .../Metrics/Statistics/Implemented/TotalGamesStatistic.swift | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift index 41d4d5ba..530d5f51 100644 --- a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift +++ b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift @@ -38,8 +38,7 @@ class ObjectSet { DisabledEvent.self, RequestSpawnEvent.self, WaveSpawnEvent.self, - GameStartEvent.self, - DeathEvent.self + GameStartEvent.self ] } diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift index 0f6b30fa..237b4883 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalDeathsStatistic.swift @@ -38,7 +38,7 @@ final class TotalDeathsStatistic: Statistic { let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) - + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) diff --git a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift index fd05c8f8..adfbcafb 100644 --- a/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift +++ b/TowerForge/TowerForge/Metrics/Statistics/Implemented/TotalGamesStatistic.swift @@ -34,7 +34,7 @@ final class TotalGamesStatistic: Statistic { let statisticUpdateActor = StatisticUpdateActor(action: eventUpdateClosure) let anyStatisticUpdateActorWrapper = AnyStatisticUpdateActorWrapper(statisticUpdateActor) - + var statisticUpdateLinksMap: [TFEventTypeWrapper: AnyStatisticUpdateActor] = [:] statisticUpdateLinksMap[eventType] = anyStatisticUpdateActorWrapper return StatisticUpdateLinkDatabase(statisticUpdateLinks: statisticUpdateLinksMap) From 14e49617df8c3ef737986ccff025738db1f7c059 Mon Sep 17 00:00:00 2001 From: Rubesh Date: Tue, 16 Apr 2024 22:08:22 +0800 Subject: [PATCH 54/54] Update documentation --- .../Commons/Utilities/ObjectSet.swift | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift index 530d5f51..88b1fb78 100644 --- a/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift +++ b/TowerForge/TowerForge/Commons/Utilities/ObjectSet.swift @@ -9,22 +9,15 @@ import Foundation /// The ObjectSet utility class represents a compromise between reducing the cyclomatic /// complexity of enums by replacing enums with dictionaries, and increasing code safety -/// by using enums to limit input values. +/// by using hashable wrapped types to limit input values. /// /// Essentially, as opposed to having an enum that violates the open-closed principle when /// new objects are to be added, a dictionary is used instead. However, instead of having Strings -/// as keys, a specially defined hashable enum (i.e. rawRepresentable is String) is used to ensure -/// that arbitrary values cannot be used. +/// as keys, a specially defined hashable wrapped type is used to ensure that arbitrary values +/// cannot be used as keys. /// -/// The typical use case is a static dictionary whose keys are of the same type as the aforementioned -/// enums. The value is a closure that takes in a certain set of arguments and outputs an object that -/// is needed. For example, is the fullStorableCreation below, the key is a TFStorableType which is an -/// enum that has "registered" storable types, such as case .killAchievement. The value corresponding to -/// this is a closure that takes in a UUID, a TFStorableType, a Double and outputs a KillAchievement. -/// The benefit of this approach is that manual switch-cases can be entirely elimated (thus OCP not violated), -/// by simply calling the appropriate closure with the required key, while still maintaining -/// runtime uniqueness without the type erasure that would occur if a generic Storable initializer were to -/// be called. +/// This pattern is also applied across other Factory classes in TowerForge, according to specific +/// needs. class ObjectSet { static let availableEventTypes: [TFEvent.Type] =