Skip to content

Commit

Permalink
Update RemoteStorageManager with static load and store methods
Browse files Browse the repository at this point in the history
  • Loading branch information
sp4ce-cowboy committed Apr 14, 2024
1 parent 8004a00 commit edfaa27
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 156 deletions.
4 changes: 0 additions & 4 deletions TowerForge/TowerForge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -434,7 +433,6 @@
BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalGamesStatistic.swift; sourceTree = "<group>"; };
BA82C7432BC86FFE000515A0 /* GameStartEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStartEvent.swift; sourceTree = "<group>"; };
BA82C7452BC8797F000515A0 /* StatisticsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsDatabase.swift; sourceTree = "<group>"; };
BA82C7472BC87DB9000515A0 /* DefaultStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultStatistic.swift; sourceTree = "<group>"; };
BA82C7492BC88FE5000515A0 /* StatisticSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticSystem.swift; sourceTree = "<group>"; };
BA82C74D2BC8A024000515A0 /* DeathEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeathEvent.swift; sourceTree = "<group>"; };
BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalDeathsStatistic.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -966,7 +964,6 @@
BA82C73E2BC85D90000515A0 /* Implemented */ = {
isa = PBXGroup;
children = (
BA82C7472BC87DB9000515A0 /* DefaultStatistic.swift */,
BA2F5AC42BC8143E00CBD8E9 /* TotalKillsStatistic.swift */,
BA82C7412BC86FE1000515A0 /* TotalGamesStatistic.swift */,
BA82C74F2BC8A20A000515A0 /* TotalDeathsStatistic.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down
7 changes: 7 additions & 0 deletions TowerForge/TowerForge/Commons/Constants/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ class EventStatisticLinkDatabase {
}

func addStatisticLink<T: TFEvent>(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]? {
Expand Down

This file was deleted.

4 changes: 4 additions & 0 deletions TowerForge/TowerForge/Metrics/Statistics/Statistic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Foundation
struct StatisticTypeWrapper: Equatable, Hashable {
let type: Statistic.Type

var toString: String {
var asString: String {
String(describing: type)
}

Expand Down
86 changes: 3 additions & 83 deletions TowerForge/TowerForge/Metrics/Statistics/StatisticsDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

}
18 changes: 10 additions & 8 deletions TowerForge/TowerForge/Metrics/Statistics/StatisticsEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

class StatisticsEngine {
/// Core storage of Statistics
var statisticsDatabase = StatisticsDatabase()
var statistics = StatisticsDatabase()
var eventStatisticLinks = EventStatisticLinkDatabase()
var inferenceEngines: [InferenceEngine] = []

Expand All @@ -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()
}

Expand Down Expand Up @@ -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
}
}

}
22 changes: 21 additions & 1 deletion TowerForge/TowerForge/Metrics/Statistics/StatisticsFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Statistic>(_ 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) }
Expand Down
55 changes: 29 additions & 26 deletions TowerForge/TowerForge/Storage/LocalStorageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
}

}
Loading

0 comments on commit edfaa27

Please sign in to comment.