Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add quick save stack experimental feature. #441

Open
wants to merge 1 commit into
base: 1.6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Delta.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
D5EB601B2C0E6190007C543C /* Stream+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EB601A2C0E6190007C543C /* Stream+Conveniences.swift */; };
D5F702FD2C24CE5300DCD271 /* UISceneSession+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F702FC2C24CE5300DCD271 /* UISceneSession+Delta.swift */; };
D5F82FB82981D3AC00B229AF /* LegacySearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */; };
DC1486CE2C604B130001E81C /* QuickSaveStackOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1486CD2C604B130001E81C /* QuickSaveStackOptions.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -546,6 +547,7 @@
D5EB601A2C0E6190007C543C /* Stream+Conveniences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stream+Conveniences.swift"; sourceTree = "<group>"; };
D5F702FC2C24CE5300DCD271 /* UISceneSession+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+Delta.swift"; sourceTree = "<group>"; };
D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySearchBar.swift; sourceTree = "<group>"; };
DC1486CD2C604B130001E81C /* QuickSaveStackOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSaveStackOptions.swift; sourceTree = "<group>"; };
DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -1193,6 +1195,8 @@
AC1C990F29F8B8C30020E6E4 /* ToastNotificationOptions.swift */,
D5147EC72A817B4A00D6CD64 /* ReviewSaveStatesOptions.swift */,
D5A287242C23A1AC009883C3 /* SkinDebugging.swift */,
AC1AE3092A69BD3A00956EB9 /* AlternateAppIcons.swift */,
DC1486CD2C604B130001E81C /* QuickSaveStackOptions.swift */,
);
path = Features;
sourceTree = "<group>";
Expand Down Expand Up @@ -1695,6 +1699,7 @@
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */,
D5AAF27729884F8600F21ACF /* CheatDevice.swift in Sources */,
DC1486CE2C604B130001E81C /* QuickSaveStackOptions.swift in Sources */,
BF1F45AD21AF57BA00EF9895 /* HarmonyMetadataKey+Keys.swift in Sources */,
BFD1EF402336BD8800D197CF /* UIDevice+Processor.swift in Sources */,
BF71CF871FE90006001F1613 /* AppIconShortcutsViewController.swift in Sources */,
Expand Down
30 changes: 23 additions & 7 deletions Delta/Emulation/GameViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1310,17 +1310,33 @@ extension GameViewController

do
{
if let quickSaveState = try fetchRequest.execute().first
{
self.update(quickSaveState)
}
else
{
if ExperimentalFeatures.shared.quickSaveStack.isEnabled {
let quickSaveStates = try fetchRequest.execute()
if let saveState = quickSaveStates.first, quickSaveStates.count >= ExperimentalFeatures.shared.quickSaveStack.size.rawValue {
DatabaseManager.shared.performBackgroundTask { (context) in
let temporarySaveState = context.object(with: saveState.objectID)
context.delete(temporarySaveState)
context.saveWithErrorLogging()
}
}
let saveState = SaveState(context: backgroundContext)
saveState.type = .quick
saveState.game = game

self.update(saveState)
} else {
if let quickSaveState = try fetchRequest.execute().first
{
self.update(quickSaveState)
}
else
{
let saveState = SaveState(context: backgroundContext)
saveState.type = .quick
saveState.game = game

self.update(saveState)
}
}
}
catch
Expand All @@ -1340,7 +1356,7 @@ extension GameViewController

do
{
if let quickSaveState = try DatabaseManager.shared.viewContext.fetch(fetchRequest).first
if let quickSaveState = try DatabaseManager.shared.viewContext.fetch(fetchRequest).last
{
self.load(quickSaveState)
}
Expand Down
5 changes: 5 additions & 0 deletions Delta/Experimental Features/ExperimentalFeatures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ struct ExperimentalFeatures: FeatureContainer
description: "Visually show touches. Useful for screen recordings and tutorials.")
var showTouches

@Feature(name: "Quick Save Stack",
description: "Maintain multiple quick saves in a stack.",
options: QuickSaveStatesOptions())
var quickSaveStack

private init()
{
self.prepareFeatures()
Expand Down
25 changes: 25 additions & 0 deletions Delta/Experimental Features/Features/QuickSaveStackOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// QuickSaveStackOptions.swift
// Delta
//
// Created by Cooper Knaak on 8/4/24.
// Copyright © 2024 Riley Testut. All rights reserved.
//

import Foundation

import DeltaFeatures

struct QuickSaveStatesOptions
{
enum Size: Int, CaseIterable, CustomStringConvertible, OptionValue, LocalizedOptionValue {
case two = 2
case four = 4
case eight = 8

var description: String { return "\(self.rawValue)" }
}

@Option(name: "Stack Size", values: Size.allCases)
var size = Size.two
}
67 changes: 66 additions & 1 deletion Delta/Settings/SettingsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class SettingsViewController: UITableViewController
super.init(coder: aDecoder)

NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.settingsDidChange(with:)), name: Settings.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.experimentalSettingsDidChange(with:)), name: Settings.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
}
Expand Down Expand Up @@ -594,7 +595,7 @@ private extension SettingsViewController
{
self.tableView.selectRow(at: selectedIndexPath, animated: true, scrollPosition: .none)
}

case .localControllerPlayerIndex, .preferredControllerSkin, .translucentControllerSkinOpacity, .respectSilentMode, .isButtonHapticFeedbackEnabled, .isThumbstickHapticFeedbackEnabled, .isAltJITEnabled: break
default: break
}
Expand Down Expand Up @@ -1030,3 +1031,67 @@ extension SettingsViewController: MFMailComposeViewControllerDelegate
controller.dismiss(animated: true, completion: nil)
}
}

// MARK: - ExperimentalFeatures.quickSaveStack

private extension SettingsViewController {

@objc func experimentalSettingsDidChange(with notification: Notification) {
guard let settingsName = notification.userInfo?[Settings.NotificationUserInfoKey.name] as? Settings.Name else { return }
guard let settingsValue = notification.userInfo?[Settings.NotificationUserInfoKey.value] else { return }

switch settingsName
{
case ExperimentalFeatures.shared.quickSaveStack.settingsKey:
guard let isOn = settingsValue as? Bool else {
return
}
if !isOn {
// reset to 1. If it the feature was off and was turned on, there
// is only one save state, by definition, so
deleteQuickSaveStack(count: 1)
}
break
case ExperimentalFeatures.shared.quickSaveStack.$size.settingsKey:
guard let newSize = settingsValue as? QuickSaveStatesOptions.Size else {
return
}
deleteQuickSaveStack(count: newSize.rawValue)
break

default: break
}
}

/// Deletes quick save database entries until there are no more than `count` remaining.
private func deleteQuickSaveStack(count:Int) {
let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait {
do {
let games = try Game.fetchRequest().execute()
for game in games {
let fetchRequest = SaveState.fetchRequest(for: game, type: .quick)
do {
let quickSaveStates = try fetchRequest.execute()
// quick saves are ordered earliest to latest. The top of the stack
// is the end. Delete the first ones so the most recently related
// ones remain.
let toDelete = max(0, quickSaveStates.count - count)
for i in 0..<toDelete {
DatabaseManager.shared.performBackgroundTask { (context) in
let temporarySaveState = context.object(with: quickSaveStates[i].objectID)
context.delete(temporarySaveState)
context.saveWithErrorLogging()
}
}
}
catch {
print(error)
}
}
} catch {
print(error)
}
}
}
}